diff --git a/.agents/notes/README.i18n.yaml b/.agents/notes/README.i18n.yaml index 4b6ef324b9..57ff21c782 100644 --- a/.agents/notes/README.i18n.yaml +++ b/.agents/notes/README.i18n.yaml @@ -6,8 +6,8 @@ en: a10991a192c7b55d zh: c2e5b938ece4f366 /agent-notes/layout-and-naming: - en: 6b38cb0eb38c2baf - zh: 535234fb23fc8921 + en: 0a517dfc15d4af0a + zh: 324bd9e13a0295e3 /agent-notes/classification: en: 055a5e49f9af3dc2 zh: 9f7c78409cccfded @@ -18,8 +18,8 @@ en: 2beb1b94d8fb633c zh: 889b5542bb771205 /agent-notes/the-file-format: - en: 601f85df49f80fc7 - zh: acbd0955d6543b13 + en: c8efdd37e51c8746 + zh: 0080340005bd31db /agent-notes/the-file-format/the-header-block: en: 16fdaab0c74a5bb5 zh: 2553f145a1eb878f diff --git a/.agents/notes/README.md b/.agents/notes/README.md index 8cee939fcf..518851d672 100644 --- a/.agents/notes/README.md +++ b/.agents/notes/README.md @@ -16,7 +16,7 @@ Every Agent Note has two axes, both encoded in its **path** — `{lifecycle}/{cl The date in the filename is when the topic was **first proposed** (per git history). Cross-references between Agent Notes use relative markdown links (`[topic](../../implemented/architecture/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders. -The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`; the [no-index Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.md) owns the rationale. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below. +The active lifecycle tree is the working inventory: browse its lifecycle/class folders or search the repository. Do not add a centralized `INDEX.md`: it duplicates path metadata and makes unrelated note changes contend on one generated file. Implemented records with historical decision value but little future guidance move to the separate frozen [`archived/`](archived/AGENTS.md) tree described below. ## Classification @@ -53,7 +53,7 @@ A feature-addition note may be consolidated into the later removal note only whe ## The file format -Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [the uniform-format Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above. +Every active Agent Note follows one in-file format, enforced by `pnpm run verify-agent-note-format` ([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts), part of `doc-sync`); the rationale for the format — and the alternatives it rejected — is [archived the uniform-format Agent Note](archived/process/2026-07-05-uniform-agent-note-format.md). Archived notes retain the format they had when sealed plus the archive-date line above. ### The header block diff --git a/.agents/notes/README.zh.md b/.agents/notes/README.zh.md index 6339f56f7c..b2a6717098 100644 --- a/.agents/notes/README.zh.md +++ b/.agents/notes/README.zh.md @@ -16,7 +16,7 @@ 文件名中的日期是该主题**首次提出**的时间(以 git 历史为准)。Agent Note 之间的交叉引用使用相对 Markdown 链接(`[topic](../../implemented/architecture/2026-…-….md)`),从不使用纯文字或编号,这样既可机械检查,也能在文件夹间移动时保持有效。 -活跃生命周期目录树就是工作清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`;设计理由见[不设索引的 Agent Note](implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md)。具有历史决策价值但未来指导价值较低的已实施记录会移至下文所述、单独冻结的 [`archived/`](archived/AGENTS.md) 目录树。 +活跃生命周期目录树就是工作清单:浏览其生命周期/类别文件夹,或搜索仓库即可。请勿添加集中式 `INDEX.md`:它重复路径元数据,并让无关记录的变更争用同一个生成文件。具有历史决策价值但未来指导价值较低的已实施记录会移至下文所述、单独冻结的 [`archived/`](archived/AGENTS.md) 目录树。 @@ -59,7 +59,7 @@ ## 文件格式 -每份活跃 Agent Note 遵循统一的文件内格式,由 `pnpm run verify-agent-note-format`([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts),`doc-sync`(文档同步门禁)的一环)强制执行;该格式的设计动机及其否决的替代方案见[统一格式 Agent Note](implemented/process/2026-07-05-uniform-agent-note-format.zh.md)。归档记录保留封存时的格式,并增加上述归档日期行。 +每份活跃 Agent Note 遵循统一的文件内格式,由 `pnpm run verify-agent-note-format`([scripts/verify-agent-note-format.ts](../../scripts/verify-agent-note-format.ts),`doc-sync`(文档同步门禁)的一环)强制执行;该格式的设计动机及其否决的替代方案见[已归档的统一格式 Agent Note](archived/process/2026-07-05-uniform-agent-note-format.md)。归档记录保留封存时的格式,并增加上述归档日期行。 ### 头部块 diff --git a/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.i18n.yaml new file mode 100644 index 0000000000..051d5f8082 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.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/archived/architecture/2026-06-11-content-block-vocabulary.md +2026-06-11-content-block-vocabulary.md: ae60931f37b3c470d0a1d6ee5f074ee77f9b3c14 +2026-06-11-content-block-vocabulary.zh.md: 3feed6e3ac9e7836a6e36be47fb19f346978886c diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md rename to .agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.md index 917dd5225f..ae60931f37 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md +++ b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.md @@ -1,6 +1,7 @@ # Agent Note: Provider-neutral content-block vocabulary owned by dsh-llm Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-11-content-block-vocabulary.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md rename to .agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.zh.md index 9d228eb34d..3feed6e3ac 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md +++ b/.agents/notes/archived/architecture/2026-06-11-content-block-vocabulary.zh.md @@ -1,6 +1,7 @@ # Agent Note: 由 dsh-llm 拥有的提供方无关内容块词汇 Status: implemented +Archived: 2026-09-30 [English](2026-06-11-content-block-vocabulary.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml new file mode 100644 index 0000000000..72e631f43c --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.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/archived/architecture/2026-06-11-microkernel-event-taxonomy.md +2026-06-11-microkernel-event-taxonomy.md: 681ab6eed1dc4d8a361990e69f071b918ce297c2 +2026-06-11-microkernel-event-taxonomy.zh.md: 3b7504a21be7d83969c9cee77a77c33803a41e74 diff --git a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md rename to .agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.md index 6cacbc30f2..681ab6eed1 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md +++ b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.md @@ -1,6 +1,7 @@ # Agent Note: Microkernel — extension via Cordis event taxonomy, one concrete loop Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-11-microkernel-event-taxonomy.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md rename to .agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.zh.md index 1c7919392b..3b7504a21b 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md +++ b/.agents/notes/archived/architecture/2026-06-11-microkernel-event-taxonomy.zh.md @@ -1,6 +1,7 @@ # Agent Note: 微内核——通过 Cordis 事件分类体系实现扩展,唯一具体循环 Status: implemented +Archived: 2026-09-30 [English](2026-06-11-microkernel-event-taxonomy.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-06-14-session-persistence.i18n.yaml b/.agents/notes/archived/architecture/2026-06-14-session-persistence.i18n.yaml new file mode 100644 index 0000000000..b2a2b15256 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-14-session-persistence.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/archived/architecture/2026-06-14-session-persistence.md +2026-06-14-session-persistence.md: 4d067d2905d764a5c0d70b0a3eae5de68cb717f4 +2026-06-14-session-persistence.zh.md: 1f0f4af301a439c02fc23a1ffdc541d6bcbb24d2 diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md b/.agents/notes/archived/architecture/2026-06-14-session-persistence.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-14-session-persistence.md rename to .agents/notes/archived/architecture/2026-06-14-session-persistence.md index d79975e1fe..4d067d2905 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md +++ b/.agents/notes/archived/architecture/2026-06-14-session-persistence.md @@ -1,6 +1,7 @@ # Agent Note: Session persistence as an abstract service over the existing `SessionEvent` Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-14-session-persistence.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md b/.agents/notes/archived/architecture/2026-06-14-session-persistence.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md rename to .agents/notes/archived/architecture/2026-06-14-session-persistence.zh.md index f70b8005a2..1f0f4af301 100644 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md +++ b/.agents/notes/archived/architecture/2026-06-14-session-persistence.zh.md @@ -1,6 +1,7 @@ # Agent Note: 会话持久化作为基于现有 `SessionEvent` 的抽象服务 Status: implemented +Archived: 2026-09-30 [English](2026-06-14-session-persistence.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml new file mode 100644 index 0000000000..bec82919f0 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.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/archived/architecture/2026-06-20-generic-long-running-tool-runtime.md +2026-06-20-generic-long-running-tool-runtime.md: 7cdb72ece6ca5681ba83999dd5d95fc2c27bf318 +2026-06-20-generic-long-running-tool-runtime.zh.md: bf0a5d61c0e45aa6b39c6bcd0ac71cfb9b61d43f diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md rename to .agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.md index 5960eb2686..7cdb72ece6 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -1,6 +1,7 @@ # Agent Note: The background job runtime (`ctx.jobs`) and generic task control tools Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-20-generic-long-running-tool-runtime.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md rename to .agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md index ea63ae8296..bf0a5d61c0 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md +++ b/.agents/notes/archived/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md @@ -1,6 +1,7 @@ # Agent Note: 后台任务运行时(`ctx.jobs`)与通用任务控制工具 Status: implemented +Archived: 2026-09-30 [English](2026-06-20-generic-long-running-tool-runtime.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml new file mode 100644 index 0000000000..7874665937 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.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/archived/architecture/2026-06-21-mandatory-app-attribution-headers.md +2026-06-21-mandatory-app-attribution-headers.md: 680bd56301d80f87f13294bc7744a5de376425be +2026-06-21-mandatory-app-attribution-headers.zh.md: d9e19dbf8cf5a8f8594f25c0cd8565a7deb4b8a6 diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md rename to .agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.md index 9e0c029dc0..680bd56301 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md +++ b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.md @@ -1,6 +1,7 @@ # Agent Note: Mandatory `User-Agent` attribution for provider requests Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-21-mandatory-app-attribution-headers.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md rename to .agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md index 1427daf8f6..d9e19dbf8c 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md +++ b/.agents/notes/archived/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md @@ -1,6 +1,7 @@ # Agent Note: 对提供方请求强制携带 `User-Agent` 归属标识 Status: implemented +Archived: 2026-09-30 [English](2026-06-21-mandatory-app-attribution-headers.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.i18n.yaml b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.i18n.yaml new file mode 100644 index 0000000000..c88c672448 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.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/archived/architecture/2026-06-26-file-context-as-event-gate.md +2026-06-26-file-context-as-event-gate.md: 96962f6a7260cc4da393a90349528aece3c23ce1 +2026-06-26-file-context-as-event-gate.zh.md: 22fb85fc1246bb67bc0793f8cf7816b6134f11ae diff --git a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md rename to .agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.md index 2943f11190..96962f6a72 100644 --- a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md +++ b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.md @@ -1,6 +1,7 @@ # Agent Note: Make `dsh-fs-observation-policy` an event-gate plugin, not a method interface Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-26-file-context-as-event-gate.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.zh.md b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.zh.md rename to .agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.zh.md index 053f4269b1..22fb85fc12 100644 --- a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.zh.md +++ b/.agents/notes/archived/architecture/2026-06-26-file-context-as-event-gate.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将 `dsh-fs-observation-policy` 改为事件门禁插件,而非方法接口 Status: implemented +Archived: 2026-09-30 [English](2026-06-26-file-context-as-event-gate.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml new file mode 100644 index 0000000000..221279b3c7 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.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/archived/architecture/2026-07-05-windows-jsonl-durable-publish.md +2026-07-05-windows-jsonl-durable-publish.md: f6be0b602d00daabdffc152508b0f8658fcb47a5 +2026-07-05-windows-jsonl-durable-publish.zh.md: 136b4c92b44d53c6fbfcefc94b683c9cbbfc9597 diff --git a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md rename to .agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.md index 546cde6086..f6be0b602d 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md +++ b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.md @@ -1,6 +1,7 @@ # Agent Note: Windows-native durable JSONL publication Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-05-windows-jsonl-durable-publish.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md rename to .agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md index b99032e9d7..136b4c92b4 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md +++ b/.agents/notes/archived/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 原生持久 JSONL 发布 Status: implemented +Archived: 2026-09-30 [English](2026-07-05-windows-jsonl-durable-publish.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.i18n.yaml b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.i18n.yaml new file mode 100644 index 0000000000..66c509e818 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.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/archived/architecture/2026-07-08-agent-scope-contexts.md +2026-07-08-agent-scope-contexts.md: e6513e6dc5aab699ba12d028aa7985f8dc5e33af +2026-07-08-agent-scope-contexts.zh.md: bfb1dfdad882c51ea39fd701e5a6c549af9b2349 diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md rename to .agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.md index 45e635b7bc..e6513e6dc5 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md +++ b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.md @@ -1,6 +1,7 @@ # Agent Note: The agent is a registration scope Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-08-agent-scope-contexts.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md rename to .agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.zh.md index aac860734e..bfb1dfdad8 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md +++ b/.agents/notes/archived/architecture/2026-07-08-agent-scope-contexts.zh.md @@ -1,6 +1,7 @@ # Agent Note: agent 即注册作用域 Status: implemented +Archived: 2026-09-30 [English](2026-07-08-agent-scope-contexts.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml new file mode 100644 index 0000000000..add9b57b6c --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.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/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md +2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md: f5b739b80022c85c9009a37de0e8b3f147f920b1 +2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md: 303f266079e0738ed9b0c6d7a19fadce7dc77442 diff --git a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md rename to .agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md index 89c8917484..f5b739b800 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md +++ b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md @@ -1,6 +1,7 @@ # Agent Note: After-call compaction pressure and context-overflow recovery Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md rename to .agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md index 48f954f2c4..303f266079 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md +++ b/.agents/notes/archived/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: 调用后压缩压力与上下文溢出恢复 Status: implemented +Archived: 2026-09-30 [English](2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml new file mode 100644 index 0000000000..94b1ef391f --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.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/archived/architecture/2026-07-12-agent-scope-runtime-design.md +2026-07-12-agent-scope-runtime-design.md: dba700b99a622a2f38c964fcfe6a6fdcf877c45e +2026-07-12-agent-scope-runtime-design.zh.md: f5c12fce6b9829e4ada61eab44d49232fa5aa241 diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md rename to .agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.md index b9b2c837f5..dba700b99a 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md +++ b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.md @@ -1,6 +1,7 @@ # Agent Note: Agent-scope runtime design and correctness Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-12-agent-scope-runtime-design.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md rename to .agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.zh.md index 4b94d1b844..f5c12fce6b 100644 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md +++ b/.agents/notes/archived/architecture/2026-07-12-agent-scope-runtime-design.zh.md @@ -1,6 +1,7 @@ # Agent Note: Agent 作用域运行时设计与正确性 Status: implemented +Archived: 2026-09-30 [English](2026-07-12-agent-scope-runtime-design.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml new file mode 100644 index 0000000000..ff2be756e3 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.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/archived/architecture/2026-07-16-explicit-turn-cancellation.md +2026-07-16-explicit-turn-cancellation.md: 23aaaae2d1f57a546ccbae7986c163e50f89fe24 +2026-07-16-explicit-turn-cancellation.zh.md: 4ce379c84640a214be1fa9708426e15ecc25c1d4 diff --git a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md rename to .agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.md index d59accf2dc..23aaaae2d1 100644 --- a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md +++ b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.md @@ -1,6 +1,7 @@ # Agent Note: Explicit turn cancellation capability Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-16-explicit-turn-cancellation.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.zh.md b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.zh.md rename to .agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.zh.md index 8fb0728eb2..4ce379c846 100644 --- a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.zh.md +++ b/.agents/notes/archived/architecture/2026-07-16-explicit-turn-cancellation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 显式轮次取消能力 Status: implemented +Archived: 2026-09-30 [English](2026-07-16-explicit-turn-cancellation.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml new file mode 100644 index 0000000000..c79a5315c3 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.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/archived/architecture/2026-07-19-gui-web-client-architecture.md +2026-07-19-gui-web-client-architecture.md: f54f13869d748b68fd00bdfff23c2da49b8fdc81 +2026-07-19-gui-web-client-architecture.zh.md: 4ab3639dac9d7e8916a0d3affb14bd965c11d783 diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md rename to .agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.md index 4d62d7ddbe..f54f13869d 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md +++ b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.md @@ -1,6 +1,7 @@ # Agent Note: Web client architecture — the client cordis plugin tree, the slot system, and the React-free object layer Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-gui-web-client-architecture.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md rename to .agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.zh.md index aeb6c89e67..4ab3639dac 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md +++ b/.agents/notes/archived/architecture/2026-07-19-gui-web-client-architecture.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 客户端架构——client cordis 插件树、slot 体系与 React-free 对象层 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-gui-web-client-architecture.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml new file mode 100644 index 0000000000..93ae94beb7 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.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/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md +2026-07-19-package-invariant-runtime-contracts.md: d0559b0379ce05aa9a5a0c30df4808e3088c840b +2026-07-19-package-invariant-runtime-contracts.zh.md: 0cfe61ee6344a4f6da73ffff22bb8a1b30f9f3a9 diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md rename to .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md index a1b635cc40..d0559b0379 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md @@ -1,6 +1,7 @@ # Agent Note: Meaningful package invariant contracts Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-package-invariant-runtime-contracts.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md rename to .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md index d315c440f1..0cfe61ee63 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md +++ b/.agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md @@ -1,6 +1,7 @@ # Agent Note: 有意义的包不变量约定 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-package-invariant-runtime-contracts.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml new file mode 100644 index 0000000000..5547922c17 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.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/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.md +2026-07-19-zstandard-jsonl-session-logs.md: 8ab55279bc94224b8b048c972bfda5c4a91a2fc8 +2026-07-19-zstandard-jsonl-session-logs.zh.md: c8f1b1599c415aaec37af3f238a2a979461f855b diff --git a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md rename to .agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.md index 96b76febee..8ab55279bc 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md +++ b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.md @@ -1,6 +1,7 @@ # Agent Note: Zstandard JSONL session logs Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-zstandard-jsonl-session-logs.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md rename to .agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md index 4cf184ce10..c8f1b1599c 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md +++ b/.agents/notes/archived/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md @@ -1,6 +1,7 @@ # Agent Note: Zstandard JSONL 会话日志 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-zstandard-jsonl-session-logs.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml new file mode 100644 index 0000000000..c4ddbb7bf2 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.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/archived/architecture/2026-07-20-canonical-tool-output-contract.md +2026-07-20-canonical-tool-output-contract.md: 1e2cdacdfa9100ca89840bb6ff0c98f07cecf8be +2026-07-20-canonical-tool-output-contract.zh.md: 6fa16a682a03a79d98278e2ffa543ec330550b9b diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md rename to .agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.md index 4dbcac3da8..1e2cdacdfa 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md +++ b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.md @@ -1,6 +1,7 @@ # Agent Note: Canonical tool output contract Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-20-canonical-tool-output-contract.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md rename to .agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.zh.md index c2f4ebdfc7..6fa16a682a 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md +++ b/.agents/notes/archived/architecture/2026-07-20-canonical-tool-output-contract.zh.md @@ -1,6 +1,7 @@ # Agent Note: 规范工具输出约定 Status: implemented +Archived: 2026-09-30 [English](2026-07-20-canonical-tool-output-contract.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml new file mode 100644 index 0000000000..819e30d978 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.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/archived/architecture/2026-07-20-unified-json-value-schema-dsl.md +2026-07-20-unified-json-value-schema-dsl.md: c1cc5c16f3d0e8ac65cff881a9f7b2ff51259e2a +2026-07-20-unified-json-value-schema-dsl.zh.md: 7cd7be62c47ba96914c802b208fd87640849b3eb diff --git a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md rename to .agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.md index 5de3523eab..c1cc5c16f3 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md +++ b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.md @@ -1,6 +1,7 @@ # Agent Note: Unified JSON-value schema DSL Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-20-unified-json-value-schema-dsl.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md rename to .agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md index 0dc38dc289..7cd7be62c4 100644 --- a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md +++ b/.agents/notes/archived/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md @@ -1,6 +1,7 @@ # Agent Note: 统一 JSON 值 schema DSL Status: implemented +Archived: 2026-09-30 [English](2026-07-20-unified-json-value-schema-dsl.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-24-project-session-directories.i18n.yaml b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.i18n.yaml new file mode 100644 index 0000000000..95b06b698d --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.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/archived/architecture/2026-07-24-project-session-directories.md +2026-07-24-project-session-directories.md: 92a8ea11b8150b7c60fe76ae1354c5fadddd13c9 +2026-07-24-project-session-directories.zh.md: 1850f04366deb79709b2bc51db194098eab9a95e diff --git a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.md b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-24-project-session-directories.md rename to .agents/notes/archived/architecture/2026-07-24-project-session-directories.md index 0074c8b313..92a8ea11b8 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.md +++ b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.md @@ -1,6 +1,7 @@ # Agent Note: Project-grouped session directories Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-24-project-session-directories.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.zh.md b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-24-project-session-directories.zh.md rename to .agents/notes/archived/architecture/2026-07-24-project-session-directories.zh.md index 3a1d348255..1850f04366 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.zh.md +++ b/.agents/notes/archived/architecture/2026-07-24-project-session-directories.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按项目分组的会话目录 Status: implemented +Archived: 2026-09-30 [English](2026-07-24-project-session-directories.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml new file mode 100644 index 0000000000..f72915a6e7 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.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/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md +2026-07-24-web-config-tree-boot-and-transport-layering.md: 7f1a619796158be987e58fa718af119421e477fa +2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 6f0dc590944f3994752ef1de06de46a843fdbc94 diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md rename to .agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md index c8a1db3cd7..7f1a619796 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md +++ b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md @@ -1,6 +1,7 @@ # Agent Note: dsh web config-tree boot and the web transport layering Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md rename to .agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md index 41fcf65b06..6f0dc59094 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md +++ b/.agents/notes/archived/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md @@ -1,6 +1,7 @@ # Agent Note: dsh web 的 config-tree boot 与 web 传输分层 Status: implemented +Archived: 2026-09-30 [English](2026-07-24-web-config-tree-boot-and-transport-layering.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml new file mode 100644 index 0000000000..3652679718 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.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/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md +2026-07-25-web-client-session-scope-and-provide-channel.md: 9afd2419c24f21b903c6e2c692cc6ee943244e07 +2026-07-25-web-client-session-scope-and-provide-channel.zh.md: 2385cfc8edde2dd65291037ea6d0d18c90f720d4 diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md rename to .agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md index 76561bc0e3..9afd2419c2 100644 --- a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md +++ b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md @@ -1,6 +1,7 @@ # Agent Note: Web client Agent-scope parity model and the provisioning channel (agents/scope / blank reuse / provide) Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-25-web-client-session-scope-and-provide-channel.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md rename to .agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md index d5d92b2ca5..2385cfc8ed 100644 --- a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md +++ b/.agents/notes/archived/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web client Agent-scope 对等模型与供数通道(agents/scope / blank 复用 / provide) Status: implemented +Archived: 2026-09-30 [English](2026-07-25-web-client-session-scope-and-provide-channel.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml new file mode 100644 index 0000000000..ee29fbe1a2 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.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/archived/architecture/2026-07-30-session-end-seed-log-boundary.md +2026-07-30-session-end-seed-log-boundary.md: d7025aac7a69ffafd4f64a2f73d202e7107452b2 +2026-07-30-session-end-seed-log-boundary.zh.md: 5c6227c5866b8a12506709726a1a0f93a8a6dcdc diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md rename to .agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.md index 9cf7c7f207..d7025aac7a 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md +++ b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.md @@ -1,6 +1,7 @@ # Agent Note: the end-seed log boundary Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-30-session-end-seed-log-boundary.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md rename to .agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.zh.md index fdf4ec9de6..5c6227c586 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md +++ b/.agents/notes/archived/architecture/2026-07-30-session-end-seed-log-boundary.zh.md @@ -1,6 +1,7 @@ # Agent Note: 种子结束日志边界 Status: implemented +Archived: 2026-09-30 [English](2026-07-30-session-end-seed-log-boundary.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml new file mode 100644 index 0000000000..f8074a4444 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.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/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md +2026-07-31-claimed-pre-step-inbox-lifecycle.md: 1f5d06b7314e1433880e17cc370a842b153fe287 +2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md: f31790826ea3464ab519a5b21084834390075526 diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md rename to .agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md index 3ebb73279e..1f5d06b731 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md +++ b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md @@ -1,6 +1,7 @@ # Agent Note: Claim inbox input before one pre-step decision Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md rename to .agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md index 809470e5b5..f31790826e 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md +++ b/.agents/notes/archived/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在单一 pre-step 决策前领取 inbox 输入 Status: implemented +Archived: 2026-09-30 [English](2026-07-31-claimed-pre-step-inbox-lifecycle.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml new file mode 100644 index 0000000000..3dc7899665 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.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/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.md +2026-08-04-draft-provider-endpoint-interrogation.md: d6de95d91b4c045d4eca793ca7190f6d148888d4 +2026-08-04-draft-provider-endpoint-interrogation.zh.md: eb72884a7119db36549d64a613a5f9d927a1d30a diff --git a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md rename to .agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.md index 469ab38b0f..d6de95d91b 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md +++ b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.md @@ -1,6 +1,7 @@ # Agent Note: Interrogating a draft provider endpoint Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-04-draft-provider-endpoint-interrogation.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md rename to .agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md index 74c6bc7ed7..eb72884a71 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md +++ b/.agents/notes/archived/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 询问草稿中的提供方端点 Status: implemented +Archived: 2026-09-30 [English](2026-08-04-draft-provider-endpoint-interrogation.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.i18n.yaml b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.i18n.yaml new file mode 100644 index 0000000000..81e4d917f1 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.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/archived/architecture/2026-08-10-remote-event-delivery.md +2026-08-10-remote-event-delivery.md: c5c68db6e280dcd10344d019d7f961bd0530f965 +2026-08-10-remote-event-delivery.zh.md: 86c1321fd153ea0e062193f95e31aafbf0dd6c46 diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md rename to .agents/notes/archived/architecture/2026-08-10-remote-event-delivery.md index 3eaa26fb92..c5c68db6e2 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md +++ b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.md @@ -1,6 +1,7 @@ # Agent Note: Remote event delivery (`ctx.remote.$on`) Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-10-remote-event-delivery.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md rename to .agents/notes/archived/architecture/2026-08-10-remote-event-delivery.zh.md index d41589f1b8..86c1321fd1 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.zh.md +++ b/.agents/notes/archived/architecture/2026-08-10-remote-event-delivery.zh.md @@ -1,6 +1,7 @@ # Agent Note: Remote 事件投递(ctx.remote.$on) Status: implemented +Archived: 2026-09-30 [English](2026-08-10-remote-event-delivery.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml new file mode 100644 index 0000000000..7185f190e5 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.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/archived/architecture/2026-08-18-experimental-agent-teams-packages.md +2026-08-18-experimental-agent-teams-packages.md: 358eec1f0d51286fe1bfb8b6b7ab71a858c26bef +2026-08-18-experimental-agent-teams-packages.zh.md: f511a2b5ae5560d8cae0f5925e67b7ea00da49c3 diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md rename to .agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.md index 92eba148b3..358eec1f0d 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md +++ b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.md @@ -1,6 +1,7 @@ # Agent Note: Publish Agent Teams under experimental package names Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-18-experimental-agent-teams-packages.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md rename to .agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.zh.md index 6f4d004a78..f511a2b5ae 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.zh.md +++ b/.agents/notes/archived/architecture/2026-08-18-experimental-agent-teams-packages.zh.md @@ -1,6 +1,7 @@ # Agent Note: 以实验性包名发布 Agent Teams Status: implemented +Archived: 2026-09-30 [English](2026-08-18-experimental-agent-teams-packages.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.i18n.yaml b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.i18n.yaml new file mode 100644 index 0000000000..4ad71c6c07 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.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/archived/architecture/2026-08-28-subprocess-native-containment.md +2026-08-28-subprocess-native-containment.md: ea47118fa7da79bc56bfdba9fe08eb91d04c6a51 +2026-08-28-subprocess-native-containment.zh.md: 750315d4b406cd0a802b91314e4cd4a77a19a99e diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md rename to .agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.md index 88acee2d41..ea47118fa7 100644 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md +++ b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.md @@ -1,6 +1,7 @@ # Agent Note: Native owners contain escaped subprocess descendants Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-28-subprocess-native-containment.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md rename to .agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.zh.md index 53406f9953..750315d4b4 100644 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.zh.md +++ b/.agents/notes/archived/architecture/2026-08-28-subprocess-native-containment.zh.md @@ -1,6 +1,7 @@ # Agent Note: 原生 owner 收容逃逸的 subprocess 后代 Status: implemented +Archived: 2026-09-30 [English](2026-08-28-subprocess-native-containment.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml new file mode 100644 index 0000000000..47ff4f1425 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.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/archived/architecture/2026-09-01-jobs-absorb-activity-record.md +2026-09-01-jobs-absorb-activity-record.md: e5543737155cc75f62485d8b125a7fa178e63ca0 +2026-09-01-jobs-absorb-activity-record.zh.md: 1f6e283c7924e2a30cc6f617396e4e0c9852e7e0 diff --git a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.md b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.md rename to .agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.md index 508608f7eb..e554373715 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.md +++ b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.md @@ -1,6 +1,7 @@ # Agent Note: The job registry absorbs the observation record; the standalone activity seam is removed Status: implemented +Archived: 2026-09-30 Superseded: the record declaration (`JobStart.record`, `RecordingJob`, `readRecord`, `pumpJobOutput`) and the split wire (`SessionJob.record` on the session control stream, `ctx.jobOutput`) described below were consolidated into one output ring and direct operations on `ctx.jobs` — see [the jobs seam consolidation](2026-09-03-jobs-seam-consolidation.md). The argument for absorbing the standalone activity seam into `ctx.jobs` still holds. diff --git a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.zh.md b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.zh.md rename to .agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.zh.md index 2c157d9510..1f6e283c79 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.zh.md +++ b/.agents/notes/archived/architecture/2026-09-01-jobs-absorb-activity-record.zh.md @@ -1,6 +1,7 @@ # Agent Note:job 注册表吸收观测 record;独立的 activity seam 被移除 Status: implemented +Archived: 2026-09-30 Superseded:下文描述的 record 声明(`JobStart.record`、`RecordingJob`、`readRecord`、`pumpJobOutput`)与拆分的线路(会话控制流上的 `SessionJob.record`、`ctx.jobOutput`)已收敛为一个输出环与 `ctx.jobs` 上的直接操作——见 [jobs seam 收敛](2026-09-03-jobs-seam-consolidation.zh.md)。把独立 activity seam 吸收进 `ctx.jobs` 的论证仍然成立。 diff --git a/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml b/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml new file mode 100644 index 0000000000..fa83ba82e7 --- /dev/null +++ b/.agents/notes/archived/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/archived/architecture/2026-09-02-projcache-cross-version-read-compat.md +2026-09-02-projcache-cross-version-read-compat.md: 22722e7bb41c2d06ffbdf433aad10a9647abce3f +2026-09-02-projcache-cross-version-read-compat.zh.md: a928c40bfa6d294b2ed927a381528ccc260692ea diff --git a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md b/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md rename to .agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.md index 160c5c796b..22722e7bb4 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md +++ b/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.md @@ -1,6 +1,7 @@ # Agent Note: Projection-cache predecessor recovery and Session-format binding (session_projcache v3-v6 → v7) Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-02-projcache-cross-version-read-compat.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md b/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md rename to .agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md index cff7742f6c..a928c40bfa 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md +++ b/.agents/notes/archived/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md @@ -1,6 +1,7 @@ # Agent Note: 投影缓存前代恢复与 Session 格式绑定(session_projcache v3-v6 → v7) Status: implemented +Archived: 2026-09-30 [English](2026-09-02-projcache-cross-version-read-compat.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.i18n.yaml b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.i18n.yaml new file mode 100644 index 0000000000..1dcf3b2d34 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.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/archived/architecture/2026-09-05-package-manifest-types.md +2026-09-05-package-manifest-types.md: 63c2dd833508d2246ba818b584eabe5c02846c07 +2026-09-05-package-manifest-types.zh.md: 794983e05b914705eb431d204470cd67d512fab4 diff --git a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md rename to .agents/notes/archived/architecture/2026-09-05-package-manifest-types.md index dc018ba019..63c2dd8335 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md +++ b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.md @@ -1,6 +1,7 @@ # Agent Note: Package manifest declaration ownership Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-package-manifest-types.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md rename to .agents/notes/archived/architecture/2026-09-05-package-manifest-types.zh.md index 8fd323bfe6..794983e05b 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.zh.md +++ b/.agents/notes/archived/architecture/2026-09-05-package-manifest-types.zh.md @@ -1,6 +1,7 @@ # Agent Note: Package manifest 声明归属 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-package-manifest-types.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml new file mode 100644 index 0000000000..765ea074b9 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.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/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.md +2026-09-05-sidebar-tab-types-and-navigation.md: a2eaed465332d1dc9adc4a770554ecac69535920 +2026-09-05-sidebar-tab-types-and-navigation.zh.md: 60d0fc9ae20e6e234939a2cf45233a5c6bd602e9 diff --git a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.md b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.md rename to .agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.md index 4e7fcb8cb1..a2eaed4653 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.md +++ b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.md @@ -1,6 +1,7 @@ # Agent Note: Right Sidebar tab types and navigation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-sidebar-tab-types-and-navigation.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md rename to .agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md index ca10161a62..60d0fc9ae2 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md +++ b/.agents/notes/archived/architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 右侧 Sidebar 的 tab 类型与导航 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-sidebar-tab-types-and-navigation.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml new file mode 100644 index 0000000000..79b5865cfc --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.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/archived/architecture/2026-09-06-embedded-stream-record-readers.md +2026-09-06-embedded-stream-record-readers.md: 88a15f50f8d122676b1b5d2c7df39aa7b7e7b331 +2026-09-06-embedded-stream-record-readers.zh.md: a66dbc69c788a05569859de7de4bc43b985183ff diff --git a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md rename to .agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.md index efad86a4f9..88a15f50f8 100644 --- a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md +++ b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.md @@ -1,6 +1,7 @@ # Agent Note: Embedded Assistant stream consumers read compact records Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-embedded-stream-record-readers.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.zh.md b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.zh.md rename to .agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.zh.md index f7131a2717..a66dbc69c7 100644 --- a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.zh.md +++ b/.agents/notes/archived/architecture/2026-09-06-embedded-stream-record-readers.zh.md @@ -1,6 +1,7 @@ # Agent Note: 内嵌 Assistant 流的消费方直接读取紧凑记录 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-embedded-stream-record-readers.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml new file mode 100644 index 0000000000..1e98674ef4 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.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/archived/architecture/2026-09-07-sidebar-responsive-tab-info.md +2026-09-07-sidebar-responsive-tab-info.md: e47ad9cd1a9b24b783d5acec780f0a2af6303727 +2026-09-07-sidebar-responsive-tab-info.zh.md: d2e3c84b76ffad6f52f7db1d13158ed51e3d5624 diff --git a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.md b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.md rename to .agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.md index 96f7f047d0..e47ad9cd1a 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.md +++ b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.md @@ -1,6 +1,7 @@ # Agent Note: Responsive Sidebar and injected tab information Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-sidebar-responsive-tab-info.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md rename to .agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md index f020068e71..d2e3c84b76 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md +++ b/.agents/notes/archived/architecture/2026-09-07-sidebar-responsive-tab-info.zh.md @@ -1,6 +1,7 @@ # Agent Note: 响应式 Sidebar 与注入的标签信息 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-sidebar-responsive-tab-info.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml new file mode 100644 index 0000000000..a7346e77f3 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.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/archived/architecture/2026-09-07-workspace-files-dual-face-package.md +2026-09-07-workspace-files-dual-face-package.md: dbefb894291ac7c0b3e7847c75a7bd4d19c19d3e +2026-09-07-workspace-files-dual-face-package.zh.md: c10a46736982a2b326bcedae11723feef6871476 diff --git a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.md b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.md rename to .agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.md index adc900865c..dbefb89429 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.md +++ b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.md @@ -1,6 +1,7 @@ # Agent Note: Workspace files as one dual-face API package Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-workspace-files-dual-face-package.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.zh.md b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.zh.md rename to .agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.zh.md index ccfa604172..c10a467369 100644 --- a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.zh.md +++ b/.agents/notes/archived/architecture/2026-09-07-workspace-files-dual-face-package.zh.md @@ -1,6 +1,7 @@ # Agent Note: 工作区文件统一为 API 双面包 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-workspace-files-dual-face-package.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-08-global-main-panels.i18n.yaml b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.i18n.yaml new file mode 100644 index 0000000000..f3b0953a4d --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.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/archived/architecture/2026-09-08-global-main-panels.md +2026-09-08-global-main-panels.md: 86336e694ea89bcb219b1fb927f92a5c72ee2630 +2026-09-08-global-main-panels.zh.md: 89743b456c7aad101705c2d9264e63449222b29a diff --git a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.md b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-08-global-main-panels.md rename to .agents/notes/archived/architecture/2026-09-08-global-main-panels.md index 63530a6b49..86336e694e 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.md +++ b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.md @@ -1,6 +1,7 @@ # Agent Note: Global main panels without default UI additions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-global-main-panels.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.zh.md b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-08-global-main-panels.zh.md rename to .agents/notes/archived/architecture/2026-09-08-global-main-panels.zh.md index 776787c3de..89743b456c 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.zh.md +++ b/.agents/notes/archived/architecture/2026-09-08-global-main-panels.zh.md @@ -1,6 +1,7 @@ # Agent Note: 不增加默认界面的全局主面板 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-global-main-panels.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.i18n.yaml b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.i18n.yaml new file mode 100644 index 0000000000..7e7b1757ef --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.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/archived/architecture/2026-09-09-desktop-build-release-validation.md +2026-09-09-desktop-build-release-validation.md: a267cd21fe3cbc9d008aac226a873fd9624e69a5 +2026-09-09-desktop-build-release-validation.zh.md: e66dd0863589c23e479521543c5a2afd11659408 diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.md b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.md rename to .agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.md index 23aeeaa98d..a267cd21fe 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.md +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.md @@ -1,6 +1,7 @@ # Agent Note: Verify Desktop release compatibility during packaging Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-desktop-build-release-validation.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.zh.md b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.zh.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.zh.md rename to .agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.zh.md index 266bca5bbd..e66dd08635 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.zh.md +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-build-release-validation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在打包时验证 Desktop 发布兼容性 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-desktop-build-release-validation.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml new file mode 100644 index 0000000000..db75b0640a --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.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/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md +2026-09-09-desktop-immediate-window-and-direct-start.md: 0219a441dc4967cbfbd68b8902c93ba1b50b9b6b +2026-09-09-desktop-immediate-window-and-direct-start.zh.md: 85bbfc7ee484d7ea9da9153bec844cc276a8682a diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md rename to .agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md index e7ccf9b745..0219a441dc 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md @@ -1,6 +1,7 @@ # Agent Note: Show the Desktop window before starting the Host Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-desktop-immediate-window-and-direct-start.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md rename to .agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md index c59cc91f00..85bbfc7ee4 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md +++ b/.agents/notes/archived/architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md @@ -1,6 +1,7 @@ # Agent Note: Show the Desktop window before starting the Host Status: implemented +Archived: 2026-09-30 [English](2026-09-09-desktop-immediate-window-and-direct-start.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml new file mode 100644 index 0000000000..a9e0c9b57d --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.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/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md +2026-09-09-plugin-management-in-the-web-sidebar.md: cc501473734b1b99fef33a42e76124fcbeb16bf5 +2026-09-09-plugin-management-in-the-web-sidebar.zh.md: c09a1c762d25a0a07e70a86a1baef0c9333e7087 diff --git a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md rename to .agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md index ba359aeefe..cc50147373 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md +++ b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md @@ -1,6 +1,7 @@ # Agent Note: Plugin management moves to the Web sidebar Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-plugin-management-in-the-web-sidebar.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md rename to .agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md index 91664deeb4..c09a1c762d 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md +++ b/.agents/notes/archived/architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md @@ -1,6 +1,7 @@ # Agent Note: 插件管理移到 Web 侧栏 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-plugin-management-in-the-web-sidebar.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml new file mode 100644 index 0000000000..d48e6efb3f --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.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/archived/architecture/2026-09-10-command-identities-and-composer-file-action.md +2026-09-10-command-identities-and-composer-file-action.md: 516fcb38753026b6318e5bea008897ef4313c778 +2026-09-10-command-identities-and-composer-file-action.zh.md: cf75610a507d8256344017b77b4af8c435a36f96 diff --git a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.md b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.md rename to .agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.md index 696c3486c5..516fcb3875 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.md +++ b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.md @@ -1,6 +1,7 @@ # Agent Note: Command identities and composer-owned File action Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-command-identities-and-composer-file-action.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md rename to .agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md index 2ce803bad5..cf75610a50 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md +++ b/.agents/notes/archived/architecture/2026-09-10-command-identities-and-composer-file-action.zh.md @@ -1,6 +1,7 @@ # Agent Note: 命令标识与输入框拥有的文件动作 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-command-identities-and-composer-file-action.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.i18n.yaml b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.i18n.yaml new file mode 100644 index 0000000000..d796ff3482 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.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/archived/architecture/2026-09-10-windows-native-installer-pages.md +2026-09-10-windows-native-installer-pages.md: b801c512c8b2603f82c613bcdcbdfbe569daf303 +2026-09-10-windows-native-installer-pages.zh.md: 926665e83ea1c5187d1808d4281cc5291e320f7b diff --git a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.md b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.md rename to .agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.md index 93aa483962..b801c512c8 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.md +++ b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.md @@ -1,6 +1,7 @@ # Agent Note: Native Windows installer pages Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-windows-native-installer-pages.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.zh.md b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.zh.md rename to .agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.zh.md index da28e22f82..926665e83e 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.zh.md +++ b/.agents/notes/archived/architecture/2026-09-10-windows-native-installer-pages.zh.md @@ -1,6 +1,7 @@ # Agent Note: 原生 Windows 安装页面 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-windows-native-installer-pages.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml new file mode 100644 index 0000000000..5016e89423 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.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/archived/architecture/2026-09-11-desktop-electron-node-runtime.md +2026-09-11-desktop-electron-node-runtime.md: 1cfb07676db8e1a56c1290d4156c922dc33a53b8 +2026-09-11-desktop-electron-node-runtime.zh.md: 02f9e493c439b9c3a3c698cb909b44fc98af5ef9 diff --git a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.md b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.md rename to .agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.md index 96851a00e5..1cfb07676d 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.md +++ b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.md @@ -1,6 +1,7 @@ # Agent Note: Use Electron as the Desktop Node runtime Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-desktop-electron-node-runtime.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.zh.md b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.zh.md rename to .agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.zh.md index ac1d6416e5..02f9e493c4 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.zh.md +++ b/.agents/notes/archived/architecture/2026-09-11-desktop-electron-node-runtime.zh.md @@ -1,6 +1,7 @@ # Agent Note: 使用 Electron 作为 Desktop 的 Node 运行时 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-desktop-electron-node-runtime.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-11-node-office-kit.i18n.yaml b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.i18n.yaml new file mode 100644 index 0000000000..1ba8cd1f2d --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.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/archived/architecture/2026-09-11-node-office-kit.md +2026-09-11-node-office-kit.md: 222f6836ae121ff5882dcd83dca8135dd06625bd +2026-09-11-node-office-kit.zh.md: bff5f2916a4c150e2d2af0709d62afc0969e73f7 diff --git a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.md b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-node-office-kit.md rename to .agents/notes/archived/architecture/2026-09-11-node-office-kit.md index 2c95b7e27e..222f6836ae 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.md +++ b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.md @@ -1,6 +1,7 @@ # Agent Note: Node Office conversion with independently packaged engines Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-node-office-kit.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.zh.md b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-node-office-kit.zh.md rename to .agents/notes/archived/architecture/2026-09-11-node-office-kit.zh.md index bec5d6bb59..bff5f2916a 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.zh.md +++ b/.agents/notes/archived/architecture/2026-09-11-node-office-kit.zh.md @@ -1,6 +1,7 @@ # Agent Note: 独立打包引擎的 Node Office 转换 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-node-office-kit.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.i18n.yaml b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.i18n.yaml new file mode 100644 index 0000000000..647453d662 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.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/archived/architecture/2026-09-11-windows-directory-installation.md +2026-09-11-windows-directory-installation.md: d59df214a2b84e31d94de1d99c41c74859d64d32 +2026-09-11-windows-directory-installation.zh.md: 0a0976a2e3ca2a5cbcf8e8f4be152e91942a6488 diff --git a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.md b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.md rename to .agents/notes/archived/architecture/2026-09-11-windows-directory-installation.md index 8208c6cc4b..d59df214a2 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.md +++ b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.md @@ -1,6 +1,7 @@ # Agent Note: Replace Windows application directories after staging Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-windows-directory-installation.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.zh.md b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.zh.md rename to .agents/notes/archived/architecture/2026-09-11-windows-directory-installation.zh.md index d337397e8e..0a0976a2e3 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.zh.md +++ b/.agents/notes/archived/architecture/2026-09-11-windows-directory-installation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 暂存完成后替换 Windows 应用目录 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-windows-directory-installation.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml new file mode 100644 index 0000000000..820342cde5 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.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/archived/architecture/2026-09-12-ptc-runtime-vocabulary.md +2026-09-12-ptc-runtime-vocabulary.md: 9698ab8471b50744ffc9c237e9a9d3eec0c928d7 +2026-09-12-ptc-runtime-vocabulary.zh.md: 6be726990b0c86833e6f8cec3778e5834b91942a diff --git a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.md b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.md rename to .agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.md index 86d2132b73..9698ab8471 100644 --- a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.md +++ b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.md @@ -1,6 +1,7 @@ # Agent Note: PTC runtime vocabulary Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-ptc-runtime-vocabulary.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md rename to .agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md index 5951c43b95..6be726990b 100644 --- a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md +++ b/.agents/notes/archived/architecture/2026-09-12-ptc-runtime-vocabulary.zh.md @@ -1,6 +1,7 @@ # Agent Note: PTC 运行时词汇 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-ptc-runtime-vocabulary.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml new file mode 100644 index 0000000000..4db5e573c8 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.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/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.md +2026-09-14-sidebar-layout-provider-recovery.md: b66111f95f5230b0fd4be31a616650002b8cb45e +2026-09-14-sidebar-layout-provider-recovery.zh.md: 989cc496b967b092d9e15c6ca8952865a8c1afa4 diff --git a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.md b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.md rename to .agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.md index f7ec0324f9..b66111f95f 100644 --- a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.md +++ b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.md @@ -1,6 +1,7 @@ # Agent Note: Sidebar layout persistence and provider recovery Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-14-sidebar-layout-provider-recovery.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md rename to .agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md index 02440391e5..989cc496b9 100644 --- a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md +++ b/.agents/notes/archived/architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: 侧栏布局持久化与 provider 恢复 Status: implemented +Archived: 2026-09-30 [English](2026-09-14-sidebar-layout-provider-recovery.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml new file mode 100644 index 0000000000..9182899e33 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.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/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md +2026-09-15-narrow-pi-ai-runtime-imports.md: d042b3099c2955d7239192bb72ae5a5b58cb7f05 +2026-09-15-narrow-pi-ai-runtime-imports.zh.md: ed4f51d326a07107f0e1d49172f07aabb868f016 diff --git a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md rename to .agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md index 307d880f3f..d042b3099c 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md +++ b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md @@ -1,6 +1,7 @@ # Agent Note: Narrow pi-ai runtime imports Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-narrow-pi-ai-runtime-imports.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md rename to .agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md index 139983446a..ed4f51d326 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md +++ b/.agents/notes/archived/architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md @@ -1,6 +1,7 @@ # Agent Note:收窄 pi-ai 运行时 import Status: implemented +Archived: 2026-09-30 [English](2026-09-15-narrow-pi-ai-runtime-imports.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.i18n.yaml b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.i18n.yaml new file mode 100644 index 0000000000..10437b3435 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.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/archived/architecture/2026-09-15-platform-office-engines.md +2026-09-15-platform-office-engines.md: 420a65772e8237803eaf537276ff0c84d667a223 +2026-09-15-platform-office-engines.zh.md: 4ba90ea17a847d313d6c8dc6f9cdc4f84a7cb9da diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md rename to .agents/notes/archived/architecture/2026-09-15-platform-office-engines.md index 4272054672..420a65772e 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md +++ b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.md @@ -1,6 +1,7 @@ # Agent Note: One Office engine per platform Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-platform-office-engines.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md rename to .agents/notes/archived/architecture/2026-09-15-platform-office-engines.zh.md index d0a3173330..4ba90ea17a 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md +++ b/.agents/notes/archived/architecture/2026-09-15-platform-office-engines.zh.md @@ -1,6 +1,7 @@ # Agent Note: 每个平台使用一个 Office 引擎 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-platform-office-engines.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml new file mode 100644 index 0000000000..6b303f36c1 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.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/archived/architecture/2026-09-16-desktop-cos-upload-transport.md +2026-09-16-desktop-cos-upload-transport.md: e1760522b8a6862673b256d8edbd1f42399d4e80 +2026-09-16-desktop-cos-upload-transport.zh.md: 9a0725d492bd3b81f2c2423abc120c8ee59ebfe1 diff --git a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.md b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.md rename to .agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.md index 84504b32fd..e1760522b8 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.md +++ b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.md @@ -1,6 +1,7 @@ # Agent Note: Upload Desktop releases through the Tencent COS SDK Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-desktop-cos-upload-transport.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.zh.md b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.zh.md rename to .agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.zh.md index 2a2080f95b..9a0725d492 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.zh.md +++ b/.agents/notes/archived/architecture/2026-09-16-desktop-cos-upload-transport.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过腾讯 COS SDK 上传 Desktop 发布产物 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-desktop-cos-upload-transport.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml new file mode 100644 index 0000000000..09a11f6b3c --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.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/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md +2026-09-16-plugin-configuration-on-the-plugins-page.md: ebb38a80f84c81233b6c9736581dcefb5fb77da1 +2026-09-16-plugin-configuration-on-the-plugins-page.zh.md: d84571d673b8839bc99fd423f11fdd7ff8ec246e diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md rename to .agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md index 35aaefd028..ebb38a80f8 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md +++ b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md @@ -1,6 +1,7 @@ # Agent Note: Plugin configuration on the Plugins page Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-plugin-configuration-on-the-plugins-page.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md rename to .agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md index ccdaf5629a..d84571d673 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md +++ b/.agents/notes/archived/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md @@ -1,6 +1,7 @@ # Agent Note: 插件页上的插件配置 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-plugin-configuration-on-the-plugins-page.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml new file mode 100644 index 0000000000..5eec9e3c13 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.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/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.md +2026-09-17-plugin-detail-page-extension-slots.md: 8d552de48b99f6d76afd9d5798892f29073a3120 +2026-09-17-plugin-detail-page-extension-slots.zh.md: f16544e1dceff4ad3ee0737fbf3fae9fee68ebd2 diff --git a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.md b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.md rename to .agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.md index d4bbca0c44..8d552de48b 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.md +++ b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.md @@ -1,6 +1,7 @@ # Agent Note: Plugin detail pages as an extension point Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-plugin-detail-page-extension-slots.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md rename to .agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md index f07efc3d3c..f16544e1dc 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md +++ b/.agents/notes/archived/architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md @@ -1,6 +1,7 @@ # Agent Note:插件详情页作为扩展点 -状态:已实现 +Status: implemented +Archived: 2026-09-30 [English](2026-09-17-plugin-detail-page-extension-slots.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml new file mode 100644 index 0000000000..c9c100b61f --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.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/archived/architecture/2026-09-17-session-row-menu-actions-slot.md +2026-09-17-session-row-menu-actions-slot.md: 4d1d192c54fc06baf15e58d51550fa3822f3fda5 +2026-09-17-session-row-menu-actions-slot.zh.md: e5bd054ae2dfd53592a650f378ea9b34f535e727 diff --git a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.md b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.md rename to .agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.md index 034144b2b7..4d1d192c54 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.md +++ b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.md @@ -1,6 +1,7 @@ # Agent Note: Session row actions as slot lists Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-session-row-menu-actions-slot.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.zh.md b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.zh.md rename to .agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.zh.md index 6ad15d263f..e5bd054ae2 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.zh.md +++ b/.agents/notes/archived/architecture/2026-09-17-session-row-menu-actions-slot.zh.md @@ -1,6 +1,7 @@ # Agent Note: Session 行 action 即 slot 列表 Status: implemented +Archived: 2026-09-30 [English](2026-09-17-session-row-menu-actions-slot.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml new file mode 100644 index 0000000000..0dcdea37a4 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.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/archived/architecture/2026-09-17-settings-pages-as-companion-packages.md +2026-09-17-settings-pages-as-companion-packages.md: b9a2144254d86d668b81b65f7fe3e4d6cce02f07 +2026-09-17-settings-pages-as-companion-packages.zh.md: 881bca17c00264e63e03a26a33078672d020c354 diff --git a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.md b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.md rename to .agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.md index de2f289793..b9a2144254 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.md +++ b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.md @@ -1,6 +1,7 @@ # Agent Note: Settings pages as companion packages Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-settings-pages-as-companion-packages.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md rename to .agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md index 27d00faa15..881bca17c0 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md +++ b/.agents/notes/archived/architecture/2026-09-17-settings-pages-as-companion-packages.zh.md @@ -1,6 +1,7 @@ # Agent Note:设置页作为伴生包 -状态:已实现 +Status: implemented +Archived: 2026-09-30 [English](2026-09-17-settings-pages-as-companion-packages.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.i18n.yaml b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.i18n.yaml new file mode 100644 index 0000000000..c6fa7571da --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.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/archived/architecture/2026-09-17-shared-office-runtime.md +2026-09-17-shared-office-runtime.md: 92ef99a76fd20e51479ab8a354bcd2b77be4f675 +2026-09-17-shared-office-runtime.zh.md: ce1188847dab99eacbaf25ee94d652be85defb97 diff --git a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md rename to .agents/notes/archived/architecture/2026-09-17-shared-office-runtime.md index 25b6ddccbb..92ef99a76f 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md +++ b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.md @@ -1,6 +1,7 @@ # Agent Note: Shared Office runtime for Desktop and SDK Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-shared-office-runtime.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.zh.md b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.zh.md rename to .agents/notes/archived/architecture/2026-09-17-shared-office-runtime.zh.md index 99dffe8d02..ce1188847d 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.zh.md +++ b/.agents/notes/archived/architecture/2026-09-17-shared-office-runtime.zh.md @@ -1,6 +1,7 @@ # Agent Note: Desktop 与 SDK 共享 Office 运行时 Status: implemented +Archived: 2026-09-30 [English](2026-09-17-shared-office-runtime.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml new file mode 100644 index 0000000000..c45939984f --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.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/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.md +2026-09-17-web-feature-routes-and-route-gate.md: eb9e6d9c19010c08143501f63135981e33fad28f +2026-09-17-web-feature-routes-and-route-gate.zh.md: 27951c475b043781cdb52c682fd539dca56db681 diff --git a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.md b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.md rename to .agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.md index 3939bdcd0c..eb9e6d9c19 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.md +++ b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.md @@ -1,6 +1,7 @@ # Agent Note: Feature routes follow the document base, and a gate keeps them there Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-web-feature-routes-and-route-gate.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md rename to .agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md index fb329c8fc1..27951c475b 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md +++ b/.agents/notes/archived/architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md @@ -1,6 +1,7 @@ # Agent Note: 功能路由遵循文档 base,并由门禁维持 Status: implemented +Archived: 2026-09-30 [English](2026-09-17-web-feature-routes-and-route-gate.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml new file mode 100644 index 0000000000..7017db1f5e --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.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/archived/architecture/2026-09-18-agent-teams-single-bundle.md +2026-09-18-agent-teams-single-bundle.md: 99f51cd6075af74acfe7c52f121b03dd7c3a5cbb +2026-09-18-agent-teams-single-bundle.zh.md: a386029227a2f07ebb1a880bfeb192dc72ced855 diff --git a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.md b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.md rename to .agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.md index b417061d24..99f51cd607 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.md +++ b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.md @@ -1,6 +1,7 @@ # Agent Note: Enable Agent Teams with one bundle Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-agent-teams-single-bundle.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.zh.md b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.zh.md rename to .agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.zh.md index ed20dac79f..a386029227 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.zh.md +++ b/.agents/notes/archived/architecture/2026-09-18-agent-teams-single-bundle.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过一个组合包启用 Agent Teams Status: implemented +Archived: 2026-09-30 [English](2026-09-18-agent-teams-single-bundle.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.i18n.yaml b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.i18n.yaml new file mode 100644 index 0000000000..61b2c3c460 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.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/archived/architecture/2026-09-18-chat-navigation-performance.md +2026-09-18-chat-navigation-performance.md: c9f7ab74bd730b5caca7b7a8b49b6c2d7f874f93 +2026-09-18-chat-navigation-performance.zh.md: f410999e461e4f2bdfa6f996400d9fea8811abac diff --git a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.md b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.md rename to .agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.md index d82c68098f..c9f7ab74bd 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.md +++ b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.md @@ -1,6 +1,7 @@ # Agent Note: Bound turn navigation work and batch historical jumps Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-chat-navigation-performance.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.zh.md b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.zh.md rename to .agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.zh.md index 9c2f81a51c..f410999e46 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.zh.md +++ b/.agents/notes/archived/architecture/2026-09-18-chat-navigation-performance.zh.md @@ -1,6 +1,7 @@ # Agent Note: 限制轮次导航工作量并聚合历史跳转 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-chat-navigation-performance.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.i18n.yaml b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.i18n.yaml new file mode 100644 index 0000000000..5c4560e79e --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.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/archived/architecture/2026-09-18-localized-package-metadata.md +2026-09-18-localized-package-metadata.md: 7d57a1c7b290021dbddde66d7623fbe31c7dfa04 +2026-09-18-localized-package-metadata.zh.md: 7bbea03d4cfe6b887026331a298d3849c592407b diff --git a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.md b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.md similarity index 87% rename from .agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.md rename to .agents/notes/archived/architecture/2026-09-18-localized-package-metadata.md index a7e41cccbb..7d57a1c7b2 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.md +++ b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.md @@ -1,6 +1,7 @@ # Agent Note: Plugin-owned localized display metadata Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-localized-package-metadata.zh.md) @@ -37,12 +38,14 @@ The Host reads installed bundles and their declared plugin rows without importin Each field falls back independently before view-specific name formatting: -| Field | Existing locale fallback chain | Same-address package fallback | Final fallback | +| Field | Existing locale fallback chain | Package-root fallback | Final fallback | |---|---|---|---| -| Title | `meta.title` | Non-empty `name` from `/package.json` | Complete configured Cordis plugin name | -| Description | `meta.description` | Non-empty `description` from `/package.json` | No package description | +| Title | `meta.title` | Non-empty `name` from `/package.json` | Complete configured Cordis plugin name | +| Description | `meta.description` | Non-empty `description` from `/package.json` | No package description | -The package fallback obeys resource exports and belongs to the same plugin address; a subpath does not inherit its owning package's introduction. The Host supplies the package or final value as the English fallback for a field that has translations but no English value, preserving the existing Client locale API. Missing or unexported resources and missing fields use these fallbacks; invalid locale fields or malformed files report a diagnostic instead of silently falling back, while retaining management operations. +The package fallback obeys resource exports and applies only when the configured plugin name is the package itself. A subpath plugin is not a package: it never reads a `package.json`, even one exported at its address, and does not inherit its owning package's introduction. The Host supplies the package or final value as the English fallback for a field that has translations but no English value, preserving the existing Client locale API. Missing or unexported resources and missing fields use these fallbacks; invalid locale fields or malformed files report a diagnostic instead of silently falling back, while retaining management operations. + +Icons follow the same address rule. A package root uses its manifest `icon`, or the exported `/icon` when that field is omitted. A subpath uses only `/icon`, resolved through Node exports like its locale resources and confined to its package. Plugin Manager uses this metadata on installed bundle cards and details, component lists, and component configuration details, preserving complete technical-name fallbacks. Settings uses it in the plugin inventory, including preset plugins, but shortens literal package-name and module-name fallbacks by removing their npm scope and Cordis/DSH prefixes. Translated titles remain verbatim in both views; full module names, entry ids, search identities, and operation targets stay unchanged. The [Settings inventory README](../../../../packages/client/ui-settings-plugin-inventory/README.md#use-this-package) owns the prefix rules. @@ -62,6 +65,8 @@ Built-in bundle introductions live in their exported language files. Beta marker **Declare references in package.json.** This duplicates the declaration between a manifest and locale files, and one package-level declaration cannot distinguish its exported plugins. +**Read `/package.json` for subpath plugins.** A package has one manifest; exporting another at a subpath suggests that subpaths own manifests. Locale files and `/icon` already give each subpath independent text and artwork. + **Read locale beside the resolved JavaScript file.** Build tools can put several plugin entries in the same directory. Resource exports preserve each plugin's identity independently of its compiled layout. **Register copy when the displayed plugin activates.** Disabled packages, packages without a Client half, and packages that fail to load still need readable introductions. @@ -77,7 +82,7 @@ Built-in bundle introductions live in their exported language files. Beta marker - Direct metadata, unrelated locale content, absent resources, malformed JSON, missing display fields, and conflicting language identifiers have focused coverage. - Separate exports in one package and same-named packages under different parents retain independent metadata without executing plugin entries. - Windows drive paths, UNC paths, relative and absolute file paths, and file URLs return no metadata without invoking resource resolution. -- Missing locale resources and individual fields fall back independently to address-local package fields, then the complete module specifier without a description. +- Missing locale resources and individual fields fall back independently to package-root manifest fields, then the complete module specifier without a description; subpath plugins ignore exported manifests and read only `/icon`. - Publication checks require readable resource exports and included language files, without duplicate ownership of built-in copy in the manager and plugins. - Management tools preserve existing model output, and pre-installation inspection adds no package-content lookup. - Focused tests and the complete `pnpm run build` succeed. diff --git a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.zh.md b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.zh.md similarity index 82% rename from .agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.zh.md rename to .agents/notes/archived/architecture/2026-09-18-localized-package-metadata.zh.md index d70aed2a57..7bbea03d4c 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.zh.md +++ b/.agents/notes/archived/architecture/2026-09-18-localized-package-metadata.zh.md @@ -1,6 +1,7 @@ # Agent Note: 插件自有的多语言展示元信息 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-localized-package-metadata.md) | 中文 @@ -37,12 +38,14 @@ Host 读取已安装 bundle 及其声明的插件行,不 import 或激活它 每个字段先独立回退,再按页面规则格式化技术名称: -| 字段 | 现有 locale 语言回退链 | 同地址包字段回退 | 最终回退 | +| 字段 | 现有 locale 语言回退链 | 包根字段回退 | 最终回退 | |---|---|---|---| -| 标题 | `meta.title` | `<插件模块名>/package.json` 中非空的 `name` | Cordis 配置中的完整插件名 | -| 描述 | `meta.description` | `<插件模块名>/package.json` 中非空的 `description` | 不提供包描述 | +| 标题 | `meta.title` | `<包名>/package.json` 中非空的 `name` | Cordis 配置中的完整插件名 | +| 描述 | `meta.description` | `<包名>/package.json` 中非空的 `description` | 不提供包描述 | -包字段回退遵守资源 exports,并且仍属于同一插件地址,子路径不继承所属包的介绍。字段有其他语言译文但没有英文值时,Host 将包字段或最终值放入英文回退位,保持现有 Client locale API 不变。资源不存在、未导出或缺少字段时使用上述回退;locale 字段无效或文件格式损坏时明确诊断,不静默回退,同时保留管理操作。 +包字段回退遵守资源 exports,仅在配置的插件名就是包本身时生效。子路径插件不是包:它从不读取 `package.json`,即使该地址导出了一个,也不继承所属包的介绍。字段有其他语言译文但没有英文值时,Host 将包字段或最终值放入英文回退位,保持现有 Client locale API 不变。资源不存在、未导出或缺少字段时使用上述回退;locale 字段无效或文件格式损坏时明确诊断,不静默回退,同时保留管理操作。 + +图标遵循相同的地址规则。包根使用清单 `icon`,省略该字段时使用导出的 `<包名>/icon`。子路径只使用 `<插件模块名>/icon`,像其 locale 资源一样经 Node exports 解析,并限制在所属包内。 插件管理页在已安装 bundle 的卡片和详情、组件列表、组件配置详情中使用这些元信息,保留完整技术名回退。设置中的插件清单也使用它,包括预设内插件,但会移除字面包名和模块名回退值的 npm scope 与 Cordis/DSH 前缀。两个页面都原样展示翻译标题;完整模块名、条目 id、搜索身份与操作目标保持不变。具体前缀规则由[设置插件清单 README](../../../../packages/client/ui-settings-plugin-inventory/README.zh.md#use-this-package)维护。 @@ -62,6 +65,8 @@ Host 读取已安装 bundle 及其声明的插件行,不 import 或激活它 **在 package.json 中声明引用。** 将声明重复放在 manifest 与 locale 文件中,而且一份包级声明无法区分包内导出的插件。 +**为子路径插件读取 `<子路径>/package.json`。** 一个包只有一个清单;在子路径导出另一个清单会让人以为子路径拥有自己的清单。locale 文件和 `<子路径>/icon` 已经为每个子路径提供独立的文本和图片。 + **从解析后的 JavaScript 文件旁读取 locale。** 构建工具可能把多个插件入口放在同一目录。资源 exports 让各插件的身份独立于编译产物布局。 **由被展示插件激活时注册文案。** 未启用包、无 Client 半边的包和加载失败的包仍然需要可读的介绍。 @@ -77,7 +82,7 @@ Host 读取已安装 bundle 及其声明的插件行,不 import 或激活它 - 直接元信息、无关 locale 内容、缺失资源、损坏 JSON、缺失展示字段及语言标识冲突均有针对性覆盖。 - 同包的不同 exports,以及不同父目录中的同名包,各自保持独立元信息,读取不执行插件入口。 - Windows 盘符路径、UNC 路径、相对与绝对文件路径及文件 URL 均不调用资源解析,直接返回无元信息。 -- locale 资源或个别字段缺失时,各字段独立回退到该插件地址的包字段,再回退到完整模块名且不显示描述。 +- locale 资源或个别字段缺失时,各字段独立回退到包根清单字段,再回退到完整模块名且不显示描述;子路径插件忽略导出的清单,只读取 `<插件模块名>/icon`。 - 发布检查要求资源 exports 可解析且包含语言文件,内置文案不在管理器和插件中双份维护。 - 管理工具保留现有模型输出,安装前查询不增加包内容探查。 - 针对性测试以及完整 `pnpm run build` 通过。 diff --git a/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml new file mode 100644 index 0000000000..5f94dbf6cc --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.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/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.md +2026-09-18-profile-plugin-host-runtime-instances.md: 2f1ae23fc73d1d1f6b82677d18cb6a4df282b5b1 +2026-09-18-profile-plugin-host-runtime-instances.zh.md: 3365d2b93b32e99c00448a8324cecfd0af6e7b9a diff --git a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.md b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.md rename to .agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.md index 76135318b3..2f1ae23fc7 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.md +++ b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.md @@ -1,6 +1,7 @@ # Agent Note: Profile plugins share the installation's host runtime instances Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-profile-plugin-host-runtime-instances.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md rename to .agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md index b222957487..3365d2b93b 100644 --- a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md +++ b/.agents/notes/archived/architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md @@ -1,6 +1,7 @@ # Agent Note: Profile 插件共享安装自带的宿主运行时实例 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-profile-plugin-host-runtime-instances.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml new file mode 100644 index 0000000000..35395f25de --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.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/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.md +2026-09-19-lossless-projection-checkpoint-json.md: 3375cd580de3d30f0b493dc9e2ebfafae671cce2 +2026-09-19-lossless-projection-checkpoint-json.zh.md: 4ce4d370aa53bcf4fc002a320f282bbc0a4ba948 diff --git a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.md b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.md rename to .agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.md index 9c7d15dbd2..3375cd580d 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.md +++ b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.md @@ -1,6 +1,7 @@ # Agent Note: Lossless projection checkpoint JSON Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-lossless-projection-checkpoint-json.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md rename to .agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md index 8c2b720358..4ce4d370aa 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md +++ b/.agents/notes/archived/architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md @@ -1,6 +1,7 @@ # Agent Note: 无损投影检查点 JSON Status: implemented +Archived: 2026-09-30 [English](2026-09-19-lossless-projection-checkpoint-json.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml new file mode 100644 index 0000000000..42d7fe5802 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.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/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.md +2026-09-21-creator-skills-progressive-disclosure.md: 8b0de4a3de6d43e5529f35ad7803df5c582d2417 +2026-09-21-creator-skills-progressive-disclosure.zh.md: c91164dd822008e06ff619526b902b9fb7df5231 diff --git a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.md b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.md rename to .agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.md index bdd70beda7..8b0de4a3de 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.md +++ b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.md @@ -1,6 +1,7 @@ # Agent Note: Creator skills disclose progressively and may read sources Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-21-creator-skills-progressive-disclosure.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md rename to .agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md index 265d9db21a..c91164dd82 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md +++ b/.agents/notes/archived/architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md @@ -1,6 +1,7 @@ # Agent Note: 创造模式 skill 渐进式披露并允许读源码 Status: implemented +Archived: 2026-09-30 [English](2026-09-21-creator-skills-progressive-disclosure.md) | 中文 diff --git a/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.i18n.yaml b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.i18n.yaml new file mode 100644 index 0000000000..60d7414481 --- /dev/null +++ b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.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/archived/architecture/2026-09-21-live-config-inspect-provider.md +2026-09-21-live-config-inspect-provider.md: b7a71d3b1d346bcb7038a815935c4b6d8e7b6c4b +2026-09-21-live-config-inspect-provider.zh.md: c8c90f252b5fa7d101959979e811dc51c06a1869 diff --git a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.md b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.md similarity index 99% rename from .agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.md rename to .agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.md index b4be508526..b7a71d3b1d 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.md +++ b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.md @@ -1,6 +1,7 @@ # Agent Note: Live plugin Config inspection replaces the Host Builtin provider Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-21-live-config-inspect-provider.zh.md) diff --git a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.zh.md b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.zh.md similarity index 98% rename from .agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.zh.md rename to .agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.zh.md index e1eead7b68..c8c90f252b 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.zh.md +++ b/.agents/notes/archived/architecture/2026-09-21-live-config-inspect-provider.zh.md @@ -1,6 +1,7 @@ # Agent Note: 运行中插件 Config 检查取代 Host Builtin provider Status: implemented +Archived: 2026-09-30 [English](2026-09-21-live-config-inspect-provider.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml new file mode 100644 index 0000000000..4e6a13ef64 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-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/archived/bug-fix/2026-07-20-jsonl-storage-identity.md +2026-07-20-jsonl-storage-identity.md: b62ac9cc1648c980079e26f95680ac31ca89fee1 +2026-07-20-jsonl-storage-identity.zh.md: 82bffc9693cc6b747ef9d993d518ae789bef451a diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.md b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.md rename to .agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.md index 5224ebf6be..b62ac9cc16 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.md +++ b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.md @@ -1,6 +1,7 @@ # Agent Note: Bind JSONL session identity before mutation Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-20-jsonl-storage-identity.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.zh.md b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.zh.md rename to .agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.zh.md index 05b7150edb..82bffc9693 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.zh.md +++ b/.agents/notes/archived/bug-fix/2026-07-20-jsonl-storage-identity.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在变更前绑定 JSONL 会话身份 Status: implemented +Archived: 2026-09-30 [English](2026-07-20-jsonl-storage-identity.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml new file mode 100644 index 0000000000..d17f432a4f --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.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/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md +2026-07-31-fail-loud-releases-the-terminal.md: 770b5e4a07c6fbcdb9430c7df529ba82600fc72b +2026-07-31-fail-loud-releases-the-terminal.zh.md: af3eb559ad61be47b4900822d138b7fa86af7365 diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md rename to .agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md index 9c8aa41e96..770b5e4a07 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md +++ b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md @@ -1,6 +1,7 @@ # Agent Note: fail-loud releases the terminal before exiting Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-31-fail-loud-releases-the-terminal.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md rename to .agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md index e44d4672dc..af3eb559ad 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md +++ b/.agents/notes/archived/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md @@ -1,6 +1,7 @@ # Agent Note: fail-loud 在退出前释放终端 Status: implemented +Archived: 2026-09-30 [English](2026-07-31-fail-loud-releases-the-terminal.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml new file mode 100644 index 0000000000..2d8afe66ae --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.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/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md +2026-08-03-cli-signal-shutdown-escalation.md: 8f8dce5f4d09c71a4a93b719138c995e79e82f8b +2026-08-03-cli-signal-shutdown-escalation.zh.md: 1b3a56ed3741bf08f54f12e18d1482edcf1d08b2 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md rename to .agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md index 2f8bdce548..8f8dce5f4d 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md +++ b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md @@ -1,6 +1,7 @@ # Agent Note: Bounded, escalating signal shutdown for Web and headless Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-03-cli-signal-shutdown-escalation.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md rename to .agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md index 8e3b90ff15..1b3a56ed37 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 与 headless 的有界信号关闭和重复信号强制退出 Status: implemented +Archived: 2026-09-30 [English](2026-08-03-cli-signal-shutdown-escalation.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml new file mode 100644 index 0000000000..8d7be151fb --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.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/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.md +2026-08-06-bwrap-private-pid-namespace.md: eb5818a9292d7c96d7d1623865058182befd891b +2026-08-06-bwrap-private-pid-namespace.zh.md: 396ee74b1b2377e31a4b8753d9a165081c6dac82 diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md rename to .agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.md index c7c83fd274..eb5818a929 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md +++ b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.md @@ -1,6 +1,7 @@ # Agent Note: isolate bwrap from the host PID namespace Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-06-bwrap-private-pid-namespace.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md rename to .agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md index fa53d4eb66..396ee74b1b 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将 bwrap 与宿主 PID 命名空间隔离 Status: implemented +Archived: 2026-09-30 [English](2026-08-06-bwrap-private-pid-namespace.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml new file mode 100644 index 0000000000..f71f5cd74c --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.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/archived/bug-fix/2026-08-06-host-backed-web-preferences.md +2026-08-06-host-backed-web-preferences.md: fcbcb0405162a70cbface43ffcbe2b08d32f3e10 +2026-08-06-host-backed-web-preferences.zh.md: 45ece0f982d67f1f08bec9fdaeb2887a92db3f0f diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md rename to .agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.md index 4b8d528875..fcbcb04051 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md +++ b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.md @@ -1,6 +1,7 @@ # Agent Note: Persist Web user preferences through Host settings Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-06-host-backed-web-preferences.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md rename to .agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.zh.md index 9c61de7198..45ece0f982 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过 Host settings 持久化 Web 用户偏好 Status: implemented +Archived: 2026-09-30 [English](2026-08-06-host-backed-web-preferences.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml new file mode 100644 index 0000000000..90cbd0bf23 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.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/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.md +2026-08-07-cancel-convergence-wake-latch.md: ad42de0440dd9903ada478094e7841d1227247b4 +2026-08-07-cancel-convergence-wake-latch.zh.md: 2c2973fff2d6345141369a1359d7fd5d123f84f1 diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md rename to .agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.md index a6540a7b46..ad42de0440 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md +++ b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.md @@ -1,6 +1,7 @@ # Agent Note: Latch wake-ups that land in the cancel-convergence window Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-07-cancel-convergence-wake-latch.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md rename to .agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md index 2c4f368fea..2c2973fff2 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md @@ -1,6 +1,7 @@ # Agent Note: 锁存取消收敛窗口内到达的唤醒请求 Status: implemented +Archived: 2026-09-30 [English](2026-08-07-cancel-convergence-wake-latch.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml new file mode 100644 index 0000000000..18fabd73d3 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.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/archived/bug-fix/2026-08-07-ptc-executor-collapse.md +2026-08-07-ptc-executor-collapse.md: de8047625f3638aab594aed992162d2548193c20 +2026-08-07-ptc-executor-collapse.zh.md: a8bc6b1f12734625879ac2de78c830123cacb263 diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md rename to .agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.md index e034d30ff5..de8047625f 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md +++ b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.md @@ -1,6 +1,7 @@ # Agent Note: PTC mode collapses the executor, not just the wire Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-07-ptc-executor-collapse.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md rename to .agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.zh.md index 03f93a3235..a8bc6b1f12 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-07-ptc-executor-collapse.zh.md @@ -1,6 +1,7 @@ # Agent Note: PTC mode 塌缩执行器而非仅通告面 Status: implemented +Archived: 2026-09-30 [English](2026-08-07-ptc-executor-collapse.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml new file mode 100644 index 0000000000..5d143ca75b --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.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/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md +2026-08-10-child-agents-join-their-parent-preset.md: cae95345f0ea3abb997ea10a36627f5a94603042 +2026-08-10-child-agents-join-their-parent-preset.zh.md: e93f42c85c778120448c43998ce10aa926b30583 diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md rename to .agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md index 7de247a7a3..cae95345f0 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md +++ b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md @@ -1,6 +1,7 @@ # Agent Note: Child agents join their parent's preset composition Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-10-child-agents-join-their-parent-preset.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md rename to .agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md index c9d392cd75..e93f42c85c 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md @@ -1,6 +1,7 @@ # Agent Note: Child agents join their parent's preset composition Status: implemented +Archived: 2026-09-30 [English](2026-08-10-child-agents-join-their-parent-preset.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml new file mode 100644 index 0000000000..a1bf462cc7 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.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/archived/bug-fix/2026-08-17-durable-web-queue-recovery.md +2026-08-17-durable-web-queue-recovery.md: bf04d65c1d834507c47271db68487b3e969db821 +2026-08-17-durable-web-queue-recovery.zh.md: c1ef9c77891a739314ca510af2debc824cf1b178 diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.md b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.md rename to .agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.md index 0f18cd7a2b..bf04d65c1d 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.md +++ b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.md @@ -1,6 +1,7 @@ # Agent Note: Recover the Web queue from durable Inbox state Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-17-durable-web-queue-recovery.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md rename to .agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md index 476c1b2fe8..c1ef9c7789 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-17-durable-web-queue-recovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: 从持久 Inbox 状态恢复 Web Queue Status: implemented +Archived: 2026-09-30 [English](2026-08-17-durable-web-queue-recovery.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml new file mode 100644 index 0000000000..0c4062dd01 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.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/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.md +2026-08-17-subagent-message-settlement-ordering.md: 4e269b3a822825db88e18e870e0b27d4789e1a35 +2026-08-17-subagent-message-settlement-ordering.zh.md: 8e54128075eb6b03b6f8cc32e87847091e49f39c diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.md b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.md rename to .agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.md index b383dc2909..4e269b3a82 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.md +++ b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.md @@ -1,6 +1,7 @@ # Agent Note: Child Agent messages precede their settlement notices Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-17-subagent-message-settlement-ordering.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md rename to .agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md index 330c19b20b..8e54128075 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md @@ -1,6 +1,7 @@ # Agent Note: Child Agent 消息先于其结算通知 Status: implemented +Archived: 2026-09-30 [English](2026-08-17-subagent-message-settlement-ordering.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml new file mode 100644 index 0000000000..24aaf81bac --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.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/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md +2026-08-24-alpha-routed-image-quality-ladders.md: 8a66d392e79c7c6cd1f447488021170c4953c9a2 +2026-08-24-alpha-routed-image-quality-ladders.zh.md: e43fc1de4098de4f0de6add7bd633c2c7863f66a diff --git a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md rename to .agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md index cdf5e7bd90..8a66d392e7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md +++ b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md @@ -1,6 +1,7 @@ # Agent Note: Alpha-routed image quality ladders replace colour-count codec routing Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-24-alpha-routed-image-quality-ladders.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md rename to .agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md index fbe8215b0c..e43fc1de40 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按 alpha 路由的图片质量阶梯取代按色数分类的编码路由 Status: implemented +Archived: 2026-09-30 [English](2026-08-24-alpha-routed-image-quality-ladders.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml new file mode 100644 index 0000000000..2281c8df26 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.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/archived/bug-fix/2026-08-31-win32-picker-path-string-read.md +2026-08-31-win32-picker-path-string-read.md: b6ac8babafb35df0618c5da99d2c1d48f6df75fa +2026-08-31-win32-picker-path-string-read.zh.md: 344ecf91d7a6332ffa5e449d5041d21a6a036b7b diff --git a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.md b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.md rename to .agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.md index 63b2a2ea5e..b6ac8babaf 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.md +++ b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.md @@ -1,6 +1,7 @@ # Agent Note: Read the Win32 picker path without a fixed-size unmanaged view Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-31-win32-picker-path-string-read.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md rename to .agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md index 8afc6610a0..344ecf91d7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md +++ b/.agents/notes/archived/bug-fix/2026-08-31-win32-picker-path-string-read.zh.md @@ -1,6 +1,7 @@ # Agent Note: 不用固定长度的非托管视图读取 Win32 选择器路径 Status: implemented +Archived: 2026-09-30 [English](2026-08-31-win32-picker-path-string-read.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml new file mode 100644 index 0000000000..70d4833c10 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.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/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md +2026-09-03-normalized-unread-fs-tool-diagnostic.md: 67d3dca2b9944303cf1ae4cdb12f07b44f0d4d04 +2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md: 21a9e338c78db8aa338970dd03046de0cb41172c diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md rename to .agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md index 01287a0806..67d3dca2b9 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md @@ -1,6 +1,7 @@ # Agent Note: Normalized unread filesystem tool diagnostic Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md rename to .agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md index 5aba96ce76..21a9e338c7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md @@ -1,6 +1,7 @@ # Agent Note: 统一未读取文件系统工具诊断 Status: implemented +Archived: 2026-09-30 [English](2026-09-03-normalized-unread-fs-tool-diagnostic.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml new file mode 100644 index 0000000000..ea240bc230 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.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/archived/bug-fix/2026-09-03-root-marker-metadata-failures.md +2026-09-03-root-marker-metadata-failures.md: 04d54ecd7006683e080a71fc7c88946e8e7a99fd +2026-09-03-root-marker-metadata-failures.zh.md: 7e515d56a2968ba6607463bbac2cc888030066d0 diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md rename to .agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.md index e31ef646d7..04d54ecd70 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.md @@ -1,6 +1,7 @@ # Agent Note: Root marker metadata failures Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-03-root-marker-metadata-failures.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md rename to .agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md index 4b62f4e01b..7e515d56a2 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-root-marker-metadata-failures.zh.md @@ -1,6 +1,7 @@ # Agent Note: 根标记元数据故障 Status: implemented +Archived: 2026-09-30 [English](2026-09-03-root-marker-metadata-failures.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml new file mode 100644 index 0000000000..9291f62844 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.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/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.md +2026-09-03-user-owned-goal-pause-activation.md: a38011025b0c18a55e5ca2358c409331eb892136 +2026-09-03-user-owned-goal-pause-activation.zh.md: 36930d351da768eb93afd1868f4336af53909cf8 diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.md b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.md rename to .agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.md index d58566938e..a38011025b 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.md @@ -1,6 +1,7 @@ # Agent Note: User-owned goal pause exposes live activation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-03-user-owned-goal-pause-activation.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md rename to .agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md index 675d61009f..36930d351d 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 用户独占的 goal 暂停并暴露实时激活态 Status: implemented +Archived: 2026-09-30 [English](2026-09-03-user-owned-goal-pause-activation.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml new file mode 100644 index 0000000000..c674eae532 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.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/archived/bug-fix/2026-09-05-continuous-client-recovery.md +2026-09-05-continuous-client-recovery.md: 132d94f741165a30fc96477aefb78cdd68ffeb37 +2026-09-05-continuous-client-recovery.zh.md: 658d731e0689d0a34e2548cc87916c19738f8e38 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.md b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.md rename to .agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.md index 2eacaca4f8..132d94f741 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.md @@ -1,6 +1,7 @@ # Agent Note: Continuous Client connection recovery Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-continuous-client-recovery.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.zh.md index 96908b1217..658d731e06 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-continuous-client-recovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: Client 连接持续恢复 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-continuous-client-recovery.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml new file mode 100644 index 0000000000..419268eb13 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.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/archived/bug-fix/2026-09-05-nested-terminal-cards.md +2026-09-05-nested-terminal-cards.md: 5ad15750b94e57728d1160c0e98b830a6978a946 +2026-09-05-nested-terminal-cards.zh.md: f0c00a5070ef8f553eaebff1858116da42801711 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md rename to .agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.md index c5946a332f..5ad15750b9 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.md @@ -1,6 +1,7 @@ # Agent Note: Nested terminal cards Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-nested-terminal-cards.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.zh.md index 74056014ce..f0c00a5070 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-nested-terminal-cards.zh.md @@ -1,6 +1,7 @@ # Agent Note: 嵌套 terminal 卡片 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-nested-terminal-cards.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml new file mode 100644 index 0000000000..817b22c234 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.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/archived/bug-fix/2026-09-05-patch-plugin-file-urls.md +2026-09-05-patch-plugin-file-urls.md: d43cb730ceee688e59200a7016eb5b8da912ab0d +2026-09-05-patch-plugin-file-urls.zh.md: 346940728af827e50217049efed3699fcc2b692d diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md rename to .agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.md index bad63355ce..d43cb730ce 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.md @@ -1,6 +1,7 @@ # Agent Note: File URLs for inserted patch plugins Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-patch-plugin-file-urls.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md index 775a576324..346940728a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-patch-plugin-file-urls.zh.md @@ -1,6 +1,7 @@ # Agent Note: 插入补丁插件的文件 URL Status: implemented +Archived: 2026-09-30 [English](2026-09-05-patch-plugin-file-urls.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml new file mode 100644 index 0000000000..d817561114 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.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/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md +2026-09-05-pi-ai-upgrade-compatibility.md: 51f7f861015cacc45ccb3b2752fbe6f9b93ad6b4 +2026-09-05-pi-ai-upgrade-compatibility.zh.md: 993e118d21b1b96280ad4c0be9903d6c6d42ec13 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md rename to .agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md index e28d911fac..51f7f86101 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md @@ -1,6 +1,7 @@ # Agent Note: pi-ai upgrade compatibility Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-pi-ai-upgrade-compatibility.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md index 408669ddc9..993e118d21 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md @@ -1,6 +1,7 @@ # Agent Note: pi-ai 升级兼容性 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-pi-ai-upgrade-compatibility.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml new file mode 100644 index 0000000000..a07ec6b255 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.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/archived/bug-fix/2026-09-05-session-reference-model-budget.md +2026-09-05-session-reference-model-budget.md: 679073552021106be7294f2c8b40108384af4f8c +2026-09-05-session-reference-model-budget.zh.md: 3ceb13e2c7b67fb36376032b3615fb46e11c7db9 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.md b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.md rename to .agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.md index 0654f2b891..6790735520 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.md @@ -1,6 +1,7 @@ # Agent Note: Model-relative session-reference budgets Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-session-reference-model-budget.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.zh.md index 84f4fee76c..3ceb13e2c7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-model-budget.zh.md @@ -1,6 +1,7 @@ # Agent Note: 模型相对会话引用预算 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-session-reference-model-budget.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml new file mode 100644 index 0000000000..9df0b074b1 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.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/archived/bug-fix/2026-09-05-session-reference-spill-reuse.md +2026-09-05-session-reference-spill-reuse.md: e77c9761d20fe3170cece635faffa2097d5bdb34 +2026-09-05-session-reference-spill-reuse.zh.md: c3ff525e546294c017a482f15023e7b6a1af1951 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md rename to .agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.md index 2a24e3adf2..e77c9761d2 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.md @@ -1,6 +1,7 @@ # Agent Note: Reuse spill storage for truncated session references Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-session-reference-spill-reuse.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md rename to .agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md index 346ed1a39e..c3ff525e54 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-05-session-reference-spill-reuse.zh.md @@ -1,6 +1,7 @@ # Agent Note: 为截断的会话引用复用 spill 存储 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-session-reference-spill-reuse.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml new file mode 100644 index 0000000000..f0d07bf604 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.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/archived/bug-fix/2026-09-06-environment-prompt-suffix.md +2026-09-06-environment-prompt-suffix.md: fba3bcb1fb32c1b49feeebeb92bce18f331995bf +2026-09-06-environment-prompt-suffix.zh.md: 27c9d9057c36c040a2b947bc5ad4bfd82ff916a9 diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.md b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.md rename to .agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.md index 3438df0fec..fba3bcb1fb 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.md +++ b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.md @@ -1,6 +1,7 @@ # Agent Note: Environment facts follow reusable prompt instructions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-environment-prompt-suffix.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.zh.md b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.zh.md rename to .agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.zh.md index 102d119663..27c9d9057c 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-06-environment-prompt-suffix.zh.md @@ -1,6 +1,7 @@ # Agent Note: 环境事实位于可复用提示词指令之后 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-environment-prompt-suffix.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml new file mode 100644 index 0000000000..1374aa68df --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.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/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.md +2026-09-06-windows-python-console-spawn-wait.md: d980c1fd771ec22e90b6fed2727b6eb5a0a89b54 +2026-09-06-windows-python-console-spawn-wait.zh.md: 7a19fa9813033ae2404a2e92b7f9fbf67dc8a403 diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.md b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.md rename to .agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.md index 036062bdf4..d980c1fd77 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.md +++ b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.md @@ -1,6 +1,7 @@ # Agent Note: Wait for the Windows Python console runtime Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-windows-python-console-spawn-wait.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md rename to .agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md index d0870be840..7a19fa9813 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md @@ -1,6 +1,7 @@ # Agent Note: 等待 Windows Python 控制台运行时 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-windows-python-console-spawn-wait.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml new file mode 100644 index 0000000000..952b469cb3 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.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/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md +2026-09-07-pi-ai-settings-catalog-recovery.md: 1fe975a7c74924a7676f24da32895ad20a841f4e +2026-09-07-pi-ai-settings-catalog-recovery.zh.md: fde8ee7de35eac1eb4749d1439c71fd0941ea283 diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md rename to .agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md index 21fe532a49..1fe975a7c7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md @@ -1,6 +1,7 @@ # Agent Note: Repairable pi-ai settings after catalog changes Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-pi-ai-settings-catalog-recovery.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md rename to .agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md index 80bd0758e1..fde8ee7de3 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: pi-ai 目录变化后可修复的设置 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-pi-ai-settings-catalog-recovery.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml new file mode 100644 index 0000000000..5982f964ef --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.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/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md +2026-09-07-typert-package-local-forwarding-imports.md: dd51274ed94f06dc0977130cf13ee97fc2cbf0ed +2026-09-07-typert-package-local-forwarding-imports.zh.md: 57ec6345ee399431806e7b3d19297e375301e9ee diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md rename to .agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md index 33e4334ede..dd51274ed9 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md @@ -1,6 +1,7 @@ # Agent Note: Follow package-local forwarding modules in Typert references Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-typert-package-local-forwarding-imports.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md rename to .agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md index ea4babdb4b..57ec6345ee 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md @@ -1,6 +1,7 @@ # Agent Note: Typert 引用追踪包内转发模块 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-typert-package-local-forwarding-imports.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml new file mode 100644 index 0000000000..28a4fb7f8c --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.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/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md +2026-09-07-win32-picker-foreground-alt-key.md: 52c2dacc1ec95f78100734ac155ae8afa369c996 +2026-09-07-win32-picker-foreground-alt-key.zh.md: ba407c41126c21f5e84e89f3488cddc1218300c4 diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md rename to .agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md index cd1bae5684..52c2dacc1e 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md @@ -1,6 +1,7 @@ # Agent Note: Foreground activation for the Win32 picker via a synthesized Alt press Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-win32-picker-foreground-alt-key.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md rename to .agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md index 8cbacaee97..ba407c4112 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过合成的 Alt 按键让 Win32 选择器获得前台激活 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-win32-picker-foreground-alt-key.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml new file mode 100644 index 0000000000..f4fe7de090 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.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/archived/bug-fix/2026-09-10-built-bundle-css-exemption.md +2026-09-10-built-bundle-css-exemption.md: c090c9112abbcf4973dadc55eb23288beed3a697 +2026-09-10-built-bundle-css-exemption.zh.md: f6e6ec85a16e47366f72005074b04ec9e073caa2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.md similarity index 90% rename from .agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md rename to .agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.md index e5a5568969..c090c9112a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.md @@ -1,6 +1,7 @@ # Agent Note: Built-bundle exemption for a failing stylesheet Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-built-bundle-css-exemption.zh.md) @@ -23,5 +24,3 @@ The Dockkit exemption admits Node's unknown-`.css`-extension refusal for any sty ## Consequences The sweep reports the Dockkit bundle when it stops importing for any reason other than Node's unknown-`.css`-extension refusal, and the entry no longer asserts which stylesheet fails. [Scoped resolve/load hooks](../../../../packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts) exercise an admitted Dockkit stylesheet, the dependency's source stylesheet, another extension, an arbitrary message, another error code, and a stale exemption without modifying shared build artifacts. - -The [CI observation decision](../testing/2026-09-08-ci-completion-observations.md) keeps the fixture completion and isolation decisions it owns; its built-client classification paragraph keeps the sweep summary and links here for the admitted evidence. diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md similarity index 90% rename from .agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md rename to .agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md index 1c1aab35df..f6e6ec85a1 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md @@ -1,6 +1,7 @@ # Agent Note: 构建产物豁免以失败的样式表为准 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-built-bundle-css-exemption.md) | 中文 @@ -23,5 +24,3 @@ Dockkit 豁免接受 Node 对任意样式表因未知 `.css` 扩展名而拒绝 ## 后果 只要 Dockkit bundle 因 Node 未知 `.css` 扩展名拒绝之外的任何原因停止导入,sweep 就会报告它,该条目也不再断言失败的是哪个样式表。[限定范围的 resolve/load hook](../../../../packages/experimental/webworker-runtime/tests/compile/transform-corpus.spec.ts)覆盖被接受的 Dockkit 样式表、依赖的源样式表、其他扩展名、任意消息、其他错误码和陈旧豁免,不修改共享构建产物。 - -[CI 观察决策](../testing/2026-09-08-ci-completion-observations.zh.md)保留其拥有的 fixture 完成与隔离决策;其已构建 Client 导入分类段落保留 sweep 摘要,并就被接受的证据链接到本文。 diff --git a/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml new file mode 100644 index 0000000000..0a88a83d25 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.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/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md +2026-09-10-deepseek-image-token-calculator-v41.md: 0b77ca48578b8d5c0b5a2b4003c6d8d145eb7a37 +2026-09-10-deepseek-image-token-calculator-v41.zh.md: 72324596990fa4a6e3eb4a44d9289b1d532294d5 diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md rename to .agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md index 668738221e..0b77ca4857 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek image-token estimator on the v41 calculator Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-deepseek-image-token-calculator-v41.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md rename to .agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md index a483282c6d..7232459699 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek 图片 token 估算器改用 v41 计算器 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-deepseek-image-token-calculator-v41.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml new file mode 100644 index 0000000000..6040791756 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.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/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md +2026-09-10-deepseek-v41-request-image-projection.md: 053e4c10620e82e05279abf1adcb1567d9f87301 +2026-09-10-deepseek-v41-request-image-projection.zh.md: 8a45fc18543a0ca139f9c696649c8d92cc6e09a1 diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md rename to .agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md index 2bf7ac3f7f..053e4c1062 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek request images on the published token grid Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-deepseek-v41-request-image-projection.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md rename to .agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md index 4f0a8cfd3a..8a45fc1854 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek 请求图片按官方 token 网格投影 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-deepseek-v41-request-image-projection.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml new file mode 100644 index 0000000000..cd3100f2fa --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.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/archived/bug-fix/2026-09-11-incremental-terminal-retention.md +2026-09-11-incremental-terminal-retention.md: a54f90094823b5df710b480086b89fb076af3af0 +2026-09-11-incremental-terminal-retention.zh.md: fb4655155bdd0d93b760a43d7952e7ca2ed9e303 diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.md b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.md rename to .agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.md index 7e2507775b..a54f900948 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.md +++ b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.md @@ -1,6 +1,7 @@ # Agent Note: incremental terminal retention Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-incremental-terminal-retention.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.zh.md b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.zh.md rename to .agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.zh.md index 6c6fb95fa0..fb4655155b 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-11-incremental-terminal-retention.zh.md @@ -1,6 +1,7 @@ # Agent Note: 增量终端保留策略 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-incremental-terminal-retention.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml new file mode 100644 index 0000000000..0ab009e644 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.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/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md +2026-09-12-linux-scope-direct-kill-settlement.md: 7d69a7cba82addeb625c6e5c9e5180235d7b36e2 +2026-09-12-linux-scope-direct-kill-settlement.zh.md: 3ba81259bb806ac1847f500b590d1ce08f9f33b6 diff --git a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md rename to .agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md index e89d8f801d..7d69a7cba8 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md +++ b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md @@ -1,6 +1,7 @@ # Agent Note: Join direct termination before rejecting Linux scope cleanup Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-linux-scope-direct-kill-settlement.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md rename to .agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md index 7ab4310749..3ba81259bb 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md @@ -1,6 +1,7 @@ # Agent Note: 等待直接进程终止后再判定 Linux scope 清理失败 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-linux-scope-direct-kill-settlement.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml new file mode 100644 index 0000000000..fa0f41a794 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.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/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md +2026-09-13-partitioned-coverage-location-canonicalization.md: a09ec18fdd1b79bc7b534f4e35ec0b702dfe4f3e +2026-09-13-partitioned-coverage-location-canonicalization.zh.md: 162cb422ab000ab46ca1ea6c0f7a9018bb8cb9f2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md rename to .agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md index 54e5aaefe9..a09ec18fdd 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md +++ b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md @@ -1,6 +1,7 @@ # Agent Note: Canonicalize partition coverage locations before the blob merge Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-13-partitioned-coverage-location-canonicalization.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md rename to .agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md index 1aa4b8de84..162cb422ab 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在 blob 合并前规范化分区覆盖率位置 Status: implemented +Archived: 2026-09-30 [English](2026-09-13-partitioned-coverage-location-canonicalization.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.i18n.yaml new file mode 100644 index 0000000000..25c97f7d3d --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.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/archived/bug-fix/2026-09-14-web-diff-context.md +2026-09-14-web-diff-context.md: 49773ac47a1ec84446cb27106f06aea393f4c5b9 +2026-09-14-web-diff-context.zh.md: f20789ee5d414ba3979d29666fa7276e227b80f2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.md b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.md rename to .agents/notes/archived/bug-fix/2026-09-14-web-diff-context.md index 072af09666..49773ac47a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.md +++ b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.md @@ -1,6 +1,7 @@ # Agent Note: Web diff cards compare contextual content Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-14-web-diff-context.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.zh.md b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.zh.md rename to .agents/notes/archived/bug-fix/2026-09-14-web-diff-context.zh.md index cd165a4434..f20789ee5d 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-14-web-diff-context.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web diff 卡片比较含上下文的内容 Status: implemented +Archived: 2026-09-30 [English](2026-09-14-web-diff-context.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml new file mode 100644 index 0000000000..32ab33b992 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.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/archived/bug-fix/2026-09-14-windows-lock-release-probe.md +2026-09-14-windows-lock-release-probe.md: 139807bef71311123b58a5db9b330f03d7f96036 +2026-09-14-windows-lock-release-probe.zh.md: 138b1b6c82a25bf02f4f02c89f671c64978cd155 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.md b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.md rename to .agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.md index 76a21f655f..139807bef7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.md +++ b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.md @@ -1,6 +1,7 @@ # Agent Note: Windows lock release before contention probing Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-14-windows-lock-release-probe.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.zh.md b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.zh.md rename to .agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.zh.md index 292203d393..138b1b6c82 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-14-windows-lock-release-probe.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 锁在竞争探测前释放 Status: implemented +Archived: 2026-09-30 [English](2026-09-14-windows-lock-release-probe.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml new file mode 100644 index 0000000000..25c1a21649 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml @@ -0,0 +1,2 @@ +2026-09-15-linear-session-list-cache.md: f2e35b926ccee591546fe0a338c6c38f785286ff +2026-09-15-linear-session-list-cache.zh.md: 17025750754f50770b3169fbfeecfe0115d0f1a1 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.md b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.md rename to .agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.md index fb09950f77..f2e35b926c 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.md +++ b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.md @@ -1,6 +1,7 @@ # Agent Note: Linear Session list cache reconciliation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-linear-session-list-cache.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.zh.md b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.zh.md rename to .agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.zh.md index b877f0659f..1702575075 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-15-linear-session-list-cache.zh.md @@ -1,6 +1,7 @@ # Agent Note: Session 列表缓存的线性对账 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-linear-session-list-cache.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml new file mode 100644 index 0000000000..87d9606f68 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.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/archived/bug-fix/2026-09-15-messages-v1-base-url.md +2026-09-15-messages-v1-base-url.md: 61427924670a21ffadb2fc9a6b36f16a7f69b02c +2026-09-15-messages-v1-base-url.zh.md: d688751a270797d784c529c862dc09bdfd8c7084 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.md b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.md rename to .agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.md index d9014756ea..6142792467 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.md +++ b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.md @@ -1,6 +1,7 @@ # Agent Note: Exact v1 recognition for Messages base URLs Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-messages-v1-base-url.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.zh.md b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.zh.md rename to .agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.zh.md index 8f5803e4bd..d688751a27 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-15-messages-v1-base-url.zh.md @@ -1,6 +1,7 @@ # Agent Note: Messages 基址严格识别 v1 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-messages-v1-base-url.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml new file mode 100644 index 0000000000..ecbe31b5d0 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.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/archived/bug-fix/2026-09-16-messages-historical-tool-input.md +2026-09-16-messages-historical-tool-input.md: 9c4a3e7b6657636cefa0cf206279eff7ece7a9b4 +2026-09-16-messages-historical-tool-input.zh.md: 5367f0379627fd6f7f1d699adb7b550c7929d98e diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.md b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.md rename to .agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.md index 85e4047bb5..9c4a3e7b66 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.md @@ -1,6 +1,7 @@ # Agent Note: Replay malformed historical tool input through Messages Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-messages-historical-tool-input.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.zh.md b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.zh.md rename to .agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.zh.md index 5a8c31dbee..5367f03796 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-messages-historical-tool-input.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过 Messages 回放非法历史工具输入 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-messages-historical-tool-input.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml new file mode 100644 index 0000000000..73eb6546a4 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.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/archived/bug-fix/2026-09-16-session-writer-held-feedback.md +2026-09-16-session-writer-held-feedback.md: 2865201a843bf9eceebd514008cd6727ca8bd772 +2026-09-16-session-writer-held-feedback.zh.md: 4a34c8e0d7f02e683e1f57af0e3b042ff923433a diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.md b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.md rename to .agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.md index 922987996c..2865201a84 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.md @@ -1,6 +1,7 @@ # Agent Note: Session writer contention feedback Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-session-writer-held-feedback.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.zh.md b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.zh.md rename to .agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.zh.md index 6a1aa7993a..4a34c8e0d7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-session-writer-held-feedback.zh.md @@ -1,6 +1,7 @@ # Agent Note: 会话写句柄占用反馈 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-session-writer-held-feedback.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml new file mode 100644 index 0000000000..41fafe8e39 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.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/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.md +2026-09-16-windows-subprocess-console-visibility.md: 27bafcd6b8c95a3ccce6e0e181ad55a3c3bc8e42 +2026-09-16-windows-subprocess-console-visibility.zh.md: 7ebacaf773a9487cd5eb095f70def3beb6c18f77 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.md b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.md rename to .agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.md index 281525e960..27bafcd6b8 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.md @@ -1,6 +1,7 @@ # Agent Note: Hide Windows subprocess console windows at creation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-windows-subprocess-console-visibility.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md rename to .agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md index 04abf7154b..7ebacaf773 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在创建时隐藏 Windows 子进程控制台窗口 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-windows-subprocess-console-visibility.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml new file mode 100644 index 0000000000..c13b6b9e13 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.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/archived/bug-fix/2026-09-18-messages-input-history-compatibility.md +2026-09-18-messages-input-history-compatibility.md: 5813d315acf16c743a1e00d5c7772e22b719c298 +2026-09-18-messages-input-history-compatibility.zh.md: 0534b4a533e652c5f9366e98ed4c478e3d9f00b5 diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.md b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.md rename to .agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.md index dd974fb1ae..5813d315ac 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.md @@ -1,6 +1,7 @@ # Agent Note: Replay assistant blocks in Messages user input Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-messages-input-history-compatibility.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md rename to .agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md index 6e850328db..0534b4a533 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-messages-input-history-compatibility.zh.md @@ -1,6 +1,7 @@ # Agent Note: 回放 Messages 用户输入中的助手块 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-messages-input-history-compatibility.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml new file mode 100644 index 0000000000..d6ac45dca0 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.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/archived/bug-fix/2026-09-18-schedule-task-tab-restore.md +2026-09-18-schedule-task-tab-restore.md: 591cd058b394db1d02399127a726f788f3571a71 +2026-09-18-schedule-task-tab-restore.zh.md: 140b25adc79b0adb99d37e1baacbec59afd1705e diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.md b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.md rename to .agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.md index 3809dfadce..591cd058b3 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.md @@ -1,6 +1,7 @@ # Agent Note: Task tab recovery after a reload Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-schedule-task-tab-restore.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md rename to .agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md index 84a1a1247b..140b25adc7 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-schedule-task-tab-restore.zh.md @@ -1,6 +1,7 @@ # Agent Note: 刷新后任务标签页的恢复 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-schedule-task-tab-restore.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml new file mode 100644 index 0000000000..a54dc3f984 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.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/archived/bug-fix/2026-09-18-windows-directory-junction-listing.md +2026-09-18-windows-directory-junction-listing.md: ad8f410fb197f4348e7b4f9d7106acc48ce63472 +2026-09-18-windows-directory-junction-listing.zh.md: b1a70b44467c78fc9623d8a741427251fef9ab16 diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.md b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.md rename to .agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.md index 8e0edc0b99..ad8f410fb1 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.md @@ -1,6 +1,7 @@ # Agent Note: List directory links in the workspace file tree Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-windows-directory-junction-listing.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md rename to .agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md index 3236c063d6..b1a70b4446 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-18-windows-directory-junction-listing.zh.md @@ -1,6 +1,7 @@ # Agent Note: 列举工作区文件树中的目录链接 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-windows-directory-junction-listing.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml new file mode 100644 index 0000000000..3b1e487449 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.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/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.md +2026-09-19-declared-session-attachment-carriers.md: 6890221634c90e39181ed58ad3aa667e02993af0 +2026-09-19-declared-session-attachment-carriers.zh.md: d6ce675e7abc0bfdaa5bdc1ebb8aee62b9aecb81 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.md b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.md rename to .agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.md index 1619624800..6890221634 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.md @@ -1,6 +1,7 @@ # Agent Note: Declared Session attachment carriers Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-declared-session-attachment-carriers.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md rename to .agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md index bd66b1c804..d6ce675e7a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md @@ -1,6 +1,7 @@ # Agent Note: Session 声明的附件载体 Status: implemented +Archived: 2026-09-30 [English](2026-09-19-declared-session-attachment-carriers.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml new file mode 100644 index 0000000000..5c4d4068a7 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.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/archived/bug-fix/2026-09-19-failed-step-tool-results.md +2026-09-19-failed-step-tool-results.md: 3d0b8dacaa80934d37f9b69543fdf72eb232a272 +2026-09-19-failed-step-tool-results.zh.md: 9fa74aef7708a49f4e1e0a78d399763aecc3e3f0 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md rename to .agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.md index 80c813598e..3d0b8dacaa 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.md @@ -1,6 +1,7 @@ # Agent Note: Record unanswered tool results before closing a failed step Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-failed-step-tool-results.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md rename to .agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.zh.md index 7d69b46a9a..9fa74aef77 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-failed-step-tool-results.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在关闭失败步骤前补齐工具结果 Status: implemented +Archived: 2026-09-30 [English](2026-09-19-failed-step-tool-results.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml new file mode 100644 index 0000000000..4a37497bce --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.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/archived/bug-fix/2026-09-19-session-local-subagent-migration.md +2026-09-19-session-local-subagent-migration.md: 5b426431874f62f67beaae7614a9a6f6dc367333 +2026-09-19-session-local-subagent-migration.zh.md: 01c650b50de2547057b414ad4890c4613a1368f7 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.md b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.md rename to .agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.md index 26911ea6e2..5b42643187 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.md @@ -1,6 +1,7 @@ # Agent Note: Complete parent catalogs without migrating child catalogs Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-session-local-subagent-migration.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.zh.md b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.zh.md rename to .agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.zh.md index 673040a3dc..01c650b50d 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-session-local-subagent-migration.zh.md @@ -1,6 +1,7 @@ # Agent Note:补齐父目录而不迁移子目录 Status: implemented +Archived: 2026-09-30 [English](2026-09-19-session-local-subagent-migration.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml new file mode 100644 index 0000000000..b6303b7c32 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.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/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md +2026-09-19-v3-incomplete-child-catalog-evidence.md: f20acd1e1f8fec1869eb338bcae45e59497d1d92 +2026-09-19-v3-incomplete-child-catalog-evidence.zh.md: 09a0131c1d610ae2c4e9253438ef45c9b7f5f561 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md rename to .agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md index 400a84f376..f20acd1e1f 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md @@ -1,6 +1,7 @@ # Agent Note: Preserve V3 Sessions with incomplete child catalog evidence Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-v3-incomplete-child-catalog-evidence.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md rename to .agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md index 535951630b..09a0131c1d 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md @@ -1,6 +1,7 @@ # Agent Note: 保留子目录证据不完整的 V3 Session Status: implemented +Archived: 2026-09-30 [English](2026-09-19-v3-incomplete-child-catalog-evidence.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml new file mode 100644 index 0000000000..088df94605 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.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/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.md +2026-09-20-windows-embedded-mandatory-update.md: c7e3081a80ff6caf76d4203e933030b5e656455a +2026-09-20-windows-embedded-mandatory-update.zh.md: 7630407003d6b9fb6b023b45ecd5ecad039bb73b diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.md b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.md rename to .agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.md index 99577a4d85..c7e3081a80 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.md +++ b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.md @@ -1,6 +1,7 @@ # Agent Note: Windows embedded mandatory update Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-20-windows-embedded-mandatory-update.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md similarity index 98% rename from .agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md rename to .agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md index abe4a9577d..7630407003 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 内嵌强更弹窗 Status: implemented +Archived: 2026-09-30 [English](2026-09-20-windows-embedded-mandatory-update.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml new file mode 100644 index 0000000000..609bdaa113 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.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/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.md +2026-09-21-multimodal-tool-result-retention.md: 9c5fd071b105969d97b8db606e8e3e0e91a008f4 +2026-09-21-multimodal-tool-result-retention.zh.md: 22adc6cd952a13267028931099f9a0c34a6747eb diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md rename to .agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.md index 4147072491..9c5fd071b1 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md +++ b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.md @@ -1,6 +1,7 @@ # Agent Note: Multimodal tool-result retention Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-21-multimodal-tool-result-retention.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md rename to .agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md index 8e6d4953f7..22adc6cd95 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md @@ -1,6 +1,7 @@ # Agent Note: 工具结果的图文保留 Status: implemented +Archived: 2026-09-30 [English](2026-09-21-multimodal-tool-result-retention.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml new file mode 100644 index 0000000000..fd27e67164 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.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/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md +2026-09-21-persistent-pwsh-keeps-controlled-prompt.md: 163ad258a9cbf55a61d2a69ad7f03b8f0bb8f97a +2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md: 4ba383718926e0b1e4a2d092631f3122883e0f46 diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md rename to .agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md index 3fd42b77d4..163ad258a9 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md +++ b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md @@ -1,6 +1,7 @@ # Agent Note: Persistent pwsh keeps the backend's controlled prompt Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md rename to .agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md index 88d4e2deef..4ba3837189 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md @@ -1,6 +1,7 @@ # Agent Note: 持久 pwsh 保留后端的受控提示符 Status: implemented +Archived: 2026-09-30 [English](2026-09-21-persistent-pwsh-keeps-controlled-prompt.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml new file mode 100644 index 0000000000..a6ea3dacfc --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.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/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md +2026-09-22-chat-scroll-follow-and-footer-geometry.md: c54140e53266ca49d9aaab3b5c9b1e8bf1e9ef20 +2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md: 2bcda3f673ba3d654c24931d91610a7198455565 diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md rename to .agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md index 56cd1681ee..c54140e532 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md @@ -1,6 +1,7 @@ # Agent Note: Separate Chat clipping and independent follow intent Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md rename to .agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md index 9979688d52..2bcda3f673 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md @@ -1,6 +1,7 @@ # Agent Note: 分离 Chat 裁剪与独立滚动跟随意图 Status: implemented +Archived: 2026-09-30 [English](2026-09-22-chat-scroll-follow-and-footer-geometry.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml new file mode 100644 index 0000000000..4f2568e824 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.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/archived/bug-fix/2026-09-22-input-echo-admission-ownership.md +2026-09-22-input-echo-admission-ownership.md: 5336dd9a034841809d2f87b0510ad4893d1521f8 +2026-09-22-input-echo-admission-ownership.zh.md: 2edd7185d6f055cc31ebc5f0fe80e6edb319675c diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.md b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.md rename to .agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.md index 227857bd81..5336dd9a03 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.md @@ -1,6 +1,7 @@ # Agent Note: Input echoes retain display ownership through admission Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-input-echo-admission-ownership.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md rename to .agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md index 1d64de220a..2edd7185d6 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-input-echo-admission-ownership.zh.md @@ -1,6 +1,7 @@ # Agent Note: 输入回显在入档期间保持展示归属 Status: implemented +Archived: 2026-09-30 [English](2026-09-22-input-echo-admission-ownership.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml new file mode 100644 index 0000000000..ba79cd7539 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.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/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.md +2026-09-22-windows-open-through-shell-resolution.md: 6373c8731ad23ff42c8cef23aed5952cfd1b8305 +2026-09-22-windows-open-through-shell-resolution.zh.md: a053b74178ae48f97e87e16c4acf1eb174b2e671 diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.md b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.md rename to .agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.md index 7bfe559e7f..6373c8731a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.md @@ -1,6 +1,7 @@ # Agent Note: Windows opens go through explorer.exe Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-windows-open-through-shell-resolution.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md rename to .agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md index ff33455da1..a053b74178 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 打开经 explorer.exe 完成 Status: implemented +Archived: 2026-09-30 [English](2026-09-22-windows-open-through-shell-resolution.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml new file mode 100644 index 0000000000..110e48aacd --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.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/archived/bug-fix/2026-09-23-bounded-pnpm-runs.md +2026-09-23-bounded-pnpm-runs.md: 9fa68d67f6d7725c70687f907f2d38a427c50599 +2026-09-23-bounded-pnpm-runs.zh.md: 7526f7ed9e22a5094ecb007e479e111fe37bffcb diff --git a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.md b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.md rename to .agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.md index 7ce6b1776e..9fa68d67f6 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.md +++ b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.md @@ -1,6 +1,7 @@ # Agent Note: Bounding a pnpm run that stops progressing Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-23-bounded-pnpm-runs.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md rename to .agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md index 1cb91132cc..7526f7ed9e 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-23-bounded-pnpm-runs.zh.md @@ -1,6 +1,7 @@ # Agent Note: 为停止推进的 pnpm 运行设界 Status: implemented +Archived: 2026-09-30 [English](2026-09-23-bounded-pnpm-runs.md) | 中文 diff --git a/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml new file mode 100644 index 0000000000..6983c7da48 --- /dev/null +++ b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.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/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md +2026-09-24-exited-holder-lock-takeover.md: 61712db971e238de9d820cdac5b5ce3c4ca0827e +2026-09-24-exited-holder-lock-takeover.zh.md: 651dc2df66b0d200631f613966de1a464be422b2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.md b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.md rename to .agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md index dd42c5cae3..61712db971 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.md +++ b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md @@ -1,6 +1,7 @@ # Agent Note: Taking over a writer lock whose holder exited Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-24-exited-holder-lock-takeover.zh.md) diff --git a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md similarity index 99% rename from .agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md rename to .agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md index 59162daa65..651dc2df66 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md +++ b/.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md @@ -1,6 +1,7 @@ # Agent Note: 接管持有者已退出的写锁 Status: implemented +Archived: 2026-09-30 [English](2026-09-24-exited-holder-lock-takeover.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.i18n.yaml b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.i18n.yaml new file mode 100644 index 0000000000..41e8fa81fd --- /dev/null +++ b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md +2026-06-18-compaction-capability-seam.md: 6e59c7e4110d3813600872bac94e37901c41d0b1 +2026-06-18-compaction-capability-seam.zh.md: 2988655d8298d9e809ebd03cdfb2f0214fdd55fa diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md rename to .agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md index 7d8b4f5011..6e59c7e411 100644 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md +++ b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md @@ -1,6 +1,7 @@ # Agent Note: Compaction as a capability seam (abstract contract + basic backend) Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-18-compaction-capability-seam.zh.md) diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md rename to .agents/notes/archived/feature/2026-06-18-compaction-capability-seam.zh.md index 6a255bb05e..2988655d82 100644 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md +++ b/.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.zh.md @@ -1,6 +1,7 @@ # Agent Note: 压缩作为能力 seam(抽象约定 + 基础后端) Status: implemented +Archived: 2026-09-30 [English](2026-06-18-compaction-capability-seam.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.i18n.yaml b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.i18n.yaml new file mode 100644 index 0000000000..6310983634 --- /dev/null +++ b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-06-21-subagent-capability-seam.md +2026-06-21-subagent-capability-seam.md: a36870d320a968f1d221caff14b542d40fc2bb10 +2026-06-21-subagent-capability-seam.zh.md: 4b95c69f52e50b2b4adfff0c89d05a25fe569bad diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md rename to .agents/notes/archived/feature/2026-06-21-subagent-capability-seam.md index 6eedb3a4b0..a36870d320 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md +++ b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.md @@ -1,6 +1,7 @@ # Agent Note: Subagent capability seam Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-21-subagent-capability-seam.zh.md) diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md rename to .agents/notes/archived/feature/2026-06-21-subagent-capability-seam.zh.md index 7e73fdc58f..4b95c69f52 100644 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md +++ b/.agents/notes/archived/feature/2026-06-21-subagent-capability-seam.zh.md @@ -1,6 +1,7 @@ # Agent Note: subagent 能力 seam Status: implemented +Archived: 2026-09-30 [English](2026-06-21-subagent-capability-seam.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-06-30-interception-extension-points.i18n.yaml b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.i18n.yaml new file mode 100644 index 0000000000..ffb00d0d6f --- /dev/null +++ b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.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/archived/feature/2026-06-30-interception-extension-points.md +2026-06-30-interception-extension-points.md: d8a2d75333bea36b33c07e50a993d881d668aa15 +2026-06-30-interception-extension-points.zh.md: fa9745d02035c9390cda4f9ffe1c08e5bc6e1e64 diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.md b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-30-interception-extension-points.md rename to .agents/notes/archived/feature/2026-06-30-interception-extension-points.md index 5adffd1679..d8a2d75333 100644 --- a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.md +++ b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.md @@ -1,6 +1,7 @@ # Agent Note: Interception extension points — the typed-Decision surface a hook programs against Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-30-interception-extension-points.zh.md) diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.zh.md b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-06-30-interception-extension-points.zh.md rename to .agents/notes/archived/feature/2026-06-30-interception-extension-points.zh.md index c3aa374a22..fa9745d020 100644 --- a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.zh.md +++ b/.agents/notes/archived/feature/2026-06-30-interception-extension-points.zh.md @@ -1,6 +1,7 @@ # Agent Note: 拦截扩展点——钩子编程所面对的类型化 Decision 接口 Status: implemented +Archived: 2026-09-30 [English](2026-06-30-interception-extension-points.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.i18n.yaml b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.i18n.yaml new file mode 100644 index 0000000000..7272e49850 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.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/archived/feature/2026-07-05-dynamic-workflows.md +2026-07-05-dynamic-workflows.md: ca87e27496bab0cba00bb6a6c9d7e562b6bb2e7c +2026-07-05-dynamic-workflows.zh.md: 498fcd17cec631d2afaa740646c93819b5a865d0 diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md rename to .agents/notes/archived/feature/2026-07-05-dynamic-workflows.md index c7245704b0..ca87e27496 100644 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md +++ b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.md @@ -1,6 +1,7 @@ # Agent Note: Dynamic workflows — a script-driven multi-agent orchestration seam Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-05-dynamic-workflows.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md rename to .agents/notes/archived/feature/2026-07-05-dynamic-workflows.zh.md index a4502d7d6b..498fcd17ce 100644 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md +++ b/.agents/notes/archived/feature/2026-07-05-dynamic-workflows.zh.md @@ -1,6 +1,7 @@ # Agent Note: 动态工作流——脚本驱动的多 agent 编排 seam Status: implemented +Archived: 2026-09-30 [English](2026-07-05-dynamic-workflows.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/archived/feature/2026-07-06-sandbox.i18n.yaml new file mode 100644 index 0000000000..83f44dc077 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-06-sandbox.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/archived/feature/2026-07-06-sandbox.md +2026-07-06-sandbox.md: 973f76d81bf78e2f1dd01b8fde20025f51c6d0d0 +2026-07-06-sandbox.zh.md: 986f469da4903304f711a0fc1ebfe63bb62557b0 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/archived/feature/2026-07-06-sandbox.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-06-sandbox.md rename to .agents/notes/archived/feature/2026-07-06-sandbox.md index 1f8ca60c83..973f76d81b 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/archived/feature/2026-07-06-sandbox.md @@ -1,6 +1,7 @@ # Agent Note: The subprocess sandbox — confinement seam, native runners, escalation, and per-session modes Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-06-sandbox.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/archived/feature/2026-07-06-sandbox.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md rename to .agents/notes/archived/feature/2026-07-06-sandbox.zh.md index 707a576e97..986f469da4 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/archived/feature/2026-07-06-sandbox.zh.md @@ -1,6 +1,7 @@ # Agent Note: 子进程沙箱——约束 seam、原生 runner、升级机制与按会话模式 Status: implemented +Archived: 2026-09-30 [English](2026-07-06-sandbox.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.i18n.yaml b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.i18n.yaml new file mode 100644 index 0000000000..fec28efef7 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.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/archived/feature/2026-07-07-mcp-client-plugin.md +2026-07-07-mcp-client-plugin.md: 9df85257e9e90f0e7266356ec0cdd0d5da37ec9f +2026-07-07-mcp-client-plugin.zh.md: 2fcbabdd9d0514a4d27a385e64fe1c2fcfa4510d diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md rename to .agents/notes/archived/feature/2026-07-07-mcp-client-plugin.md index dd793b67bf..9df85257e9 100644 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md +++ b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.md @@ -1,6 +1,7 @@ # Agent Note: MCP client plugin — connect to external MCP servers and bridge their tools Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-07-mcp-client-plugin.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md rename to .agents/notes/archived/feature/2026-07-07-mcp-client-plugin.zh.md index 5056118df0..2fcbabdd9d 100644 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.zh.md +++ b/.agents/notes/archived/feature/2026-07-07-mcp-client-plugin.zh.md @@ -1,6 +1,7 @@ # Agent Note: MCP 客户端插件——连接外部 MCP 服务器并桥接其工具 Status: implemented +Archived: 2026-09-30 [English](2026-07-07-mcp-client-plugin.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml new file mode 100644 index 0000000000..134e9b5d9c --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.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/archived/feature/2026-07-08-self-referential-cordis-toolset.md +2026-07-08-self-referential-cordis-toolset.md: a47cc3e5be467accc924b757e30fef23350e23e4 +2026-07-08-self-referential-cordis-toolset.zh.md: 30b0bfe8614c5be40d64e54015927d879ee3fbc9 diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md rename to .agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.md index 278c71051d..a47cc3e5be 100644 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md +++ b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.md @@ -1,6 +1,7 @@ # Agent Note: Cordis runtime inspection and runner isolation Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-08-self-referential-cordis-toolset.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md rename to .agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.zh.md index 8a8e197e19..30b0bfe861 100644 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md +++ b/.agents/notes/archived/feature/2026-07-08-self-referential-cordis-toolset.zh.md @@ -1,6 +1,7 @@ # Agent Note:Cordis 运行时检查与 runner 隔离 Status: implemented +Archived: 2026-09-30 [English](2026-07-08-self-referential-cordis-toolset.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml new file mode 100644 index 0000000000..2a5bbfd67c --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.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/archived/feature/2026-07-14-cross-family-fs-sandbox.md +2026-07-14-cross-family-fs-sandbox.md: 049cb7358f1c5f1474744b0e2b60bcf06659f455 +2026-07-14-cross-family-fs-sandbox.zh.md: e3bb5a4ccbc2cb208ab72453ff9bc493530f9036 diff --git a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md rename to .agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.md index 0fe1d40787..049cb7358f 100644 --- a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md +++ b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.md @@ -1,6 +1,7 @@ # Agent Note: Cross-family file sandbox — one policy home, a sandboxed fs provider, and fs escalation parity Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-14-cross-family-fs-sandbox.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md rename to .agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.zh.md index 021923c7e3..e3bb5a4ccb 100644 --- a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md +++ b/.agents/notes/archived/feature/2026-07-14-cross-family-fs-sandbox.zh.md @@ -1,6 +1,7 @@ # Agent Note: 跨家族文件沙箱——统一策略归属、沙箱化 fs 提供方、fs 升级对等 Status: implemented +Archived: 2026-09-30 [English](2026-07-14-cross-family-fs-sandbox.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-16-harness-level-loop.i18n.yaml b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.i18n.yaml new file mode 100644 index 0000000000..a29e048b0a --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.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/archived/feature/2026-07-16-harness-level-loop.md +2026-07-16-harness-level-loop.md: 9f208f871905bcde365d072494cf30ceba1957f4 +2026-07-16-harness-level-loop.zh.md: 3c6c57e1631367358a0bd418ac6834f86f866ba5 diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md rename to .agents/notes/archived/feature/2026-07-16-harness-level-loop.md index 54adc1aad6..9f208f8719 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md +++ b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.md @@ -1,6 +1,7 @@ # Agent Note: Harness-level goal-based execution Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-16-harness-level-loop.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md rename to .agents/notes/archived/feature/2026-07-16-harness-level-loop.zh.md index 9e1a7e73fb..3c6c57e163 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md +++ b/.agents/notes/archived/feature/2026-07-16-harness-level-loop.zh.md @@ -1,6 +1,7 @@ # Agent Note: Harness 层目标式执行 Status: implemented +Archived: 2026-09-30 [English](2026-07-16-harness-level-loop.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml new file mode 100644 index 0000000000..75eb18986d --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.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/archived/feature/2026-07-19-persisted-same-session-goal-domain.md +2026-07-19-persisted-same-session-goal-domain.md: 5304af8375b5367c3d75e4ba4a7c5ac0034d696d +2026-07-19-persisted-same-session-goal-domain.zh.md: 06442ca888c9274b8295ecd1f9301aaf73a8c680 diff --git a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md rename to .agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.md index 4ddc02c3bd..5304af8375 100644 --- a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md +++ b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.md @@ -1,6 +1,7 @@ # Agent Note: Persisted same-session goal domain Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-persisted-same-session-goal-domain.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md rename to .agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.zh.md index 2bb937b0f6..06442ca888 100644 --- a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md +++ b/.agents/notes/archived/feature/2026-07-19-persisted-same-session-goal-domain.zh.md @@ -1,6 +1,7 @@ # Agent Note: 持久化的同会话目标领域 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-persisted-same-session-goal-domain.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.i18n.yaml b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.i18n.yaml new file mode 100644 index 0000000000..0082dfbc36 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.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/archived/feature/2026-07-19-plugin-command-registration.md +2026-07-19-plugin-command-registration.md: 505495036501bc6c3ea32662aa9c88ec5beb1072 +2026-07-19-plugin-command-registration.zh.md: 10a47da8a7d3785818d8a45d852b4a3586f8d098 diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md rename to .agents/notes/archived/feature/2026-07-19-plugin-command-registration.md index f5033c6ac5..5054950365 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md +++ b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.md @@ -1,6 +1,7 @@ # Agent Note: Plugin-owned human command registration Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-plugin-command-registration.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md rename to .agents/notes/archived/feature/2026-07-19-plugin-command-registration.zh.md index 55f3a58f0e..10a47da8a7 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md +++ b/.agents/notes/archived/feature/2026-07-19-plugin-command-registration.zh.md @@ -1,6 +1,7 @@ # Agent Note: 插件自有的人类命令注册 Status: implemented +Archived: 2026-09-30 [English](2026-07-19-plugin-command-registration.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.i18n.yaml b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.i18n.yaml new file mode 100644 index 0000000000..4c3c6912bd --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.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/archived/feature/2026-07-21-follow-instruction-symlinks.md +2026-07-21-follow-instruction-symlinks.md: ffc80d4e55fcdb1dd27325e816614f00ad08bd54 +2026-07-21-follow-instruction-symlinks.zh.md: 2d4b5711ca2f022daedacd5c1fbdf375bea66adb diff --git a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.md b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.md rename to .agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.md index a6b4cb5d46..ffc80d4e55 100644 --- a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.md +++ b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.md @@ -1,6 +1,7 @@ # Agent Note: Follow symlinked instruction files Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-21-follow-instruction-symlinks.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.zh.md b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.zh.md rename to .agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.zh.md index a44f89d8ec..2d4b5711ca 100644 --- a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.zh.md +++ b/.agents/notes/archived/feature/2026-07-21-follow-instruction-symlinks.zh.md @@ -1,6 +1,7 @@ # Agent Note: 跟随符号链接指向的指令文件 Status: implemented +Archived: 2026-09-30 [English](2026-07-21-follow-instruction-symlinks.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml new file mode 100644 index 0000000000..b67f87e9fd --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.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/archived/feature/2026-07-23-session-telemetry-otel-revival.md +2026-07-23-session-telemetry-otel-revival.md: fd61878316c59911a550d6cccf06e51dfca6d772 +2026-07-23-session-telemetry-otel-revival.zh.md: fe581972b208eb1f0e67edb76b1232cbe9995da4 diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md rename to .agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.md index b3b50d4be5..fd61878316 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md +++ b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.md @@ -1,6 +1,7 @@ # Agent Note: Session telemetry seam with mandatory redaction and the OTel backend Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-23-session-telemetry-otel-revival.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md rename to .agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.zh.md index 349cd47e59..fe581972b2 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md +++ b/.agents/notes/archived/feature/2026-07-23-session-telemetry-otel-revival.zh.md @@ -1,6 +1,7 @@ # Agent Note: 设有强制脱敏点和 OTel 后端的会话遥测 seam Status: implemented +Archived: 2026-09-30 [English](2026-07-23-session-telemetry-otel-revival.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.i18n.yaml b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.i18n.yaml new file mode 100644 index 0000000000..55cee84f53 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.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/archived/feature/2026-07-25-subagent-policy-inheritance.md +2026-07-25-subagent-policy-inheritance.md: 969321a3c1ef12e7d0088c891e67c65f27f3b97d +2026-07-25-subagent-policy-inheritance.zh.md: 1d2579ece286b20d468a6dae7508017daa9dae26 diff --git a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md rename to .agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.md index a7973bcc07..969321a3c1 100644 --- a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md +++ b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.md @@ -1,6 +1,7 @@ # Agent Note: In-process subagent policy inheritance — the child starts under the parent's sandbox override Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-25-subagent-policy-inheritance.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.zh.md b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.zh.md rename to .agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.zh.md index 0b4a2d4099..1d2579ece2 100644 --- a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.zh.md +++ b/.agents/notes/archived/feature/2026-07-25-subagent-policy-inheritance.zh.md @@ -1,6 +1,7 @@ # Agent Note: 进程内 subagent 策略继承——子 agent 在父级的沙箱覆盖项下启动 Status: implemented +Archived: 2026-09-30 [English](2026-07-25-subagent-policy-inheritance.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml new file mode 100644 index 0000000000..5fa5959945 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.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/archived/feature/2026-07-26-ptc-live-parallel-dispatch.md +2026-07-26-ptc-live-parallel-dispatch.md: 699d8aeb3c876dce7d194e6e9a6f7dd9d4adadea +2026-07-26-ptc-live-parallel-dispatch.zh.md: dd8e1d5741836117762f1adc4030890c3ecc2cd3 diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md rename to .agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.md index f064314098..699d8aeb3c 100644 --- a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md +++ b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.md @@ -1,6 +1,7 @@ # Agent Note: PTC mode live dispatch lifecycle and native-contract parallelism Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-26-ptc-live-parallel-dispatch.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md rename to .agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md index 70cbebcdc3..dd8e1d5741 100644 --- a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md +++ b/.agents/notes/archived/feature/2026-07-26-ptc-live-parallel-dispatch.zh.md @@ -1,6 +1,7 @@ # Agent Note: PTC mode 的实时分发生命周期,以及复用原生约定的并行执行 Status: implemented +Archived: 2026-09-30 [English](2026-07-26-ptc-live-parallel-dispatch.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.i18n.yaml b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.i18n.yaml new file mode 100644 index 0000000000..49fbe169cd --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.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/archived/feature/2026-07-27-web-subagent-conversations.md +2026-07-27-web-subagent-conversations.md: 1c67900b2dd8c63e7e25a81eda76151703687b2e +2026-07-27-web-subagent-conversations.zh.md: 4d95ae19f675358a50f3e50b004ea92fa8c8e9bb diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md rename to .agents/notes/archived/feature/2026-07-27-web-subagent-conversations.md index ba72e31d88..1c67900b2d 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md +++ b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.md @@ -1,6 +1,7 @@ # Agent Note: Web subagent catalog and human continuation Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-27-web-subagent-conversations.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md rename to .agents/notes/archived/feature/2026-07-27-web-subagent-conversations.zh.md index a3841b05fb..4d95ae19f6 100644 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md +++ b/.agents/notes/archived/feature/2026-07-27-web-subagent-conversations.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web subagent 目录与用户继续交互 Status: implemented +Archived: 2026-09-30 [English](2026-07-27-web-subagent-conversations.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.i18n.yaml b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.i18n.yaml new file mode 100644 index 0000000000..0cfbe0720e --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.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/archived/feature/2026-07-27-workspace-registration-deletion.md +2026-07-27-workspace-registration-deletion.md: 78aee4649c9959582088c42432263c0b9ee835e9 +2026-07-27-workspace-registration-deletion.zh.md: e58977e01e55b633a4f3fdcf185d2b2ce5956c37 diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md rename to .agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.md index 1d9804cfc4..78aee4649c 100644 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md +++ b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.md @@ -1,6 +1,7 @@ # Agent Note: Workspace Registration Deletion Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-27-workspace-registration-deletion.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md rename to .agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.zh.md index 54e10b67a7..e58977e01e 100644 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md +++ b/.agents/notes/archived/feature/2026-07-27-workspace-registration-deletion.zh.md @@ -1,6 +1,7 @@ # Agent Note: 删除 Workspace 注册记录 Status: implemented +Archived: 2026-09-30 [English](2026-07-27-workspace-registration-deletion.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.i18n.yaml b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.i18n.yaml new file mode 100644 index 0000000000..bab32952bb --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.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/archived/feature/2026-07-28-skill-invocation-policy.md +2026-07-28-skill-invocation-policy.md: a0ae057cc5fd8d837afe998389476b3ed1f295ad +2026-07-28-skill-invocation-policy.zh.md: a7e5965507c9dc5afaa946db4eb4e9b3693d7fa7 diff --git a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md rename to .agents/notes/archived/feature/2026-07-28-skill-invocation-policy.md index 614a0058e3..a0ae057cc5 100644 --- a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md +++ b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.md @@ -1,6 +1,7 @@ # Agent Note: Independent model and user skill invocation policy Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-28-skill-invocation-policy.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md rename to .agents/notes/archived/feature/2026-07-28-skill-invocation-policy.zh.md index a49cb96ea6..a7e5965507 100644 --- a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.zh.md +++ b/.agents/notes/archived/feature/2026-07-28-skill-invocation-policy.zh.md @@ -1,6 +1,7 @@ # Agent Note: 模型与用户彼此独立的 skill(技能)调用策略 Status: implemented +Archived: 2026-09-30 [English](2026-07-28-skill-invocation-policy.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.i18n.yaml b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.i18n.yaml new file mode 100644 index 0000000000..1da21928e6 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.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/archived/feature/2026-07-30-queued-manual-compaction.md +2026-07-30-queued-manual-compaction.md: 09388ba8403e93a887e0fad6beac626770fceb44 +2026-07-30-queued-manual-compaction.zh.md: 6ff1996dfab67a4783a0aadcda1c86c4f2a75011 diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md rename to .agents/notes/archived/feature/2026-07-30-queued-manual-compaction.md index 3efd1a4901..09388ba840 100644 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md +++ b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.md @@ -1,6 +1,7 @@ # Agent Note: Queued manual compaction with one durable lock Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-30-queued-manual-compaction.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md rename to .agents/notes/archived/feature/2026-07-30-queued-manual-compaction.zh.md index 2da3f494af..6ff1996dfa 100644 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.zh.md +++ b/.agents/notes/archived/feature/2026-07-30-queued-manual-compaction.zh.md @@ -1,6 +1,7 @@ # Agent Note: 使用单一持久锁实现排队手动压缩 Status: implemented +Archived: 2026-09-30 [English](2026-07-30-queued-manual-compaction.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.i18n.yaml b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.i18n.yaml new file mode 100644 index 0000000000..b2aa307da1 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.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/archived/feature/2026-07-30-web-remote-markdown-images.md +2026-07-30-web-remote-markdown-images.md: 9de12f02c1cf9c4836b9b8cbcd58ae379d06ac2d +2026-07-30-web-remote-markdown-images.zh.md: a1a8d1b8deb9b05dca4a45bfc2d4e060d89e1ee3 diff --git a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.md b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.md rename to .agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.md index 23dc699aab..9de12f02c1 100644 --- a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.md +++ b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.md @@ -1,6 +1,7 @@ # Agent Note: Remote Web Markdown images Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-30-web-remote-markdown-images.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.zh.md b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.zh.md rename to .agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.zh.md index 54db9b25e9..a1a8d1b8de 100644 --- a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.zh.md +++ b/.agents/notes/archived/feature/2026-07-30-web-remote-markdown-images.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 中的远程 Markdown 图片 Status: implemented +Archived: 2026-09-30 [English](2026-07-30-web-remote-markdown-images.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml new file mode 100644 index 0000000000..e0ccf3eb17 --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.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/archived/feature/2026-07-31-even-out-shipped-tool-rosters.md +2026-07-31-even-out-shipped-tool-rosters.md: 2c82e8d880ef9f2c3bdec8ceff747b98adc2ebb5 +2026-07-31-even-out-shipped-tool-rosters.zh.md: 956077009b6aebdd57b53f087345e75dad564706 diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md rename to .agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.md index 90efaeb473..2c82e8d880 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md +++ b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.md @@ -1,6 +1,7 @@ # Agent Note: Even out the shipped tool rosters Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-31-even-out-shipped-tool-rosters.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md rename to .agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md index 4379ffbcd2..956077009b 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md +++ b/.agents/notes/archived/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md @@ -1,6 +1,7 @@ # Agent Note: 拉平交付的工具清单 Status: implemented +Archived: 2026-09-30 [English](2026-07-31-even-out-shipped-tool-rosters.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-31-web-default-search.i18n.yaml b/.agents/notes/archived/feature/2026-07-31-web-default-search.i18n.yaml new file mode 100644 index 0000000000..545b96357c --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-31-web-default-search.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/archived/feature/2026-07-31-web-default-search.md +2026-07-31-web-default-search.md: 793fedbbe1e4095e4c58978fd5907336caf5f8c1 +2026-07-31-web-default-search.zh.md: 3e524e98697aa45cb927396b8706bdf3afb09667 diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.md b/.agents/notes/archived/feature/2026-07-31-web-default-search.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-web-default-search.md rename to .agents/notes/archived/feature/2026-07-31-web-default-search.md index fed162cf25..793fedbbe1 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.md +++ b/.agents/notes/archived/feature/2026-07-31-web-default-search.md @@ -1,6 +1,7 @@ # Agent Note: Default Web search in shipped compositions Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-31-web-default-search.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md b/.agents/notes/archived/feature/2026-07-31-web-default-search.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md rename to .agents/notes/archived/feature/2026-07-31-web-default-search.zh.md index 3f39ef7d9f..3e524e9869 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md +++ b/.agents/notes/archived/feature/2026-07-31-web-default-search.zh.md @@ -1,6 +1,7 @@ # Agent Note: 已交付组合中的默认 Web 搜索 Status: implemented +Archived: 2026-09-30 [English](2026-07-31-web-default-search.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.i18n.yaml b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.i18n.yaml new file mode 100644 index 0000000000..539bcb9c5c --- /dev/null +++ b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.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/archived/feature/2026-07-31-web-workspace-file-links.md +2026-07-31-web-workspace-file-links.md: c486d50505d4ec55f29a9ee702b1664cd3f71f47 +2026-07-31-web-workspace-file-links.zh.md: 3afde6f09f10a654f095dc2dc79be8d1f609d52e diff --git a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md rename to .agents/notes/archived/feature/2026-07-31-web-workspace-file-links.md index 1fd58deafa..c486d50505 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md +++ b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.md @@ -1,6 +1,7 @@ # Agent Note: opening a produced file from the web UI Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-31-web-workspace-file-links.zh.md) diff --git a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.zh.md b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.zh.md rename to .agents/notes/archived/feature/2026-07-31-web-workspace-file-links.zh.md index 8cb1d06562..3afde6f09f 100644 --- a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.zh.md +++ b/.agents/notes/archived/feature/2026-07-31-web-workspace-file-links.zh.md @@ -1,6 +1,7 @@ # Agent Note: 从 web UI 打开产出的文件 Status: implemented +Archived: 2026-09-30 [English](2026-07-31-web-workspace-file-links.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml new file mode 100644 index 0000000000..f9df2357bd --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.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/archived/feature/2026-08-02-pwsh-tool-bash-parity.md +2026-08-02-pwsh-tool-bash-parity.md: 768c606a8901229642d729d568a3dcf90b707f15 +2026-08-02-pwsh-tool-bash-parity.zh.md: b8861e8760be5a9194be91529602fb6078258160 diff --git a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.md b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.md rename to .agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.md index cc2e7797c0..768c606a89 100644 --- a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.md +++ b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.md @@ -1,6 +1,7 @@ # Agent Note: pwsh tool bash parity Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-02-pwsh-tool-bash-parity.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.zh.md b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.zh.md rename to .agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.zh.md index b7949f1a51..b8861e8760 100644 --- a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.zh.md +++ b/.agents/notes/archived/feature/2026-08-02-pwsh-tool-bash-parity.zh.md @@ -1,6 +1,7 @@ # Agent Note: pwsh 工具与 bash 对齐 Status: implemented +Archived: 2026-09-30 [English](2026-08-02-pwsh-tool-bash-parity.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml new file mode 100644 index 0000000000..a0ac1d9970 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.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/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.md +2026-08-04-claude-code-and-codex-subagent-backends.md: a806a3347da94773920c61573a45e5c9f9babfbe +2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 72b3ebc877f16dee05e8ac63083c0725b28bd16b diff --git a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md rename to .agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.md index d11b200e95..a806a3347d 100644 --- a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md +++ b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.md @@ -1,6 +1,7 @@ # Agent Note: Claude Code and Codex subagent backends Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-04-claude-code-and-codex-subagent-backends.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md rename to .agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md index 5899a27bb8..72b3ebc877 100644 --- a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md +++ b/.agents/notes/archived/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md @@ -1,6 +1,7 @@ # Agent Note: Claude Code 与 Codex subagent 后端 Status: implemented +Archived: 2026-09-30 [English](2026-08-04-claude-code-and-codex-subagent-backends.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.i18n.yaml b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.i18n.yaml new file mode 100644 index 0000000000..278702c01c --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.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/archived/feature/2026-08-05-durable-web-schedule.md +2026-08-05-durable-web-schedule.md: 42b0c079b47cbeb616937f7e8c19426cfd436d7c +2026-08-05-durable-web-schedule.zh.md: 1f3935a054912f1a3427ad774dd06263b002f3aa diff --git a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md rename to .agents/notes/archived/feature/2026-08-05-durable-web-schedule.md index f918a6f44f..42b0c079b4 100644 --- a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.md +++ b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.md @@ -1,6 +1,7 @@ # Agent Note: Durable scheduled reminders Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-05-durable-web-schedule.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.zh.md b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-05-durable-web-schedule.zh.md rename to .agents/notes/archived/feature/2026-08-05-durable-web-schedule.zh.md index dec8916db7..1f3935a054 100644 --- a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.zh.md +++ b/.agents/notes/archived/feature/2026-08-05-durable-web-schedule.zh.md @@ -1,6 +1,7 @@ # Agent Note: 持久定时提醒 Status: implemented +Archived: 2026-09-30 [English](2026-08-05-durable-web-schedule.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml new file mode 100644 index 0000000000..948316a7ef --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.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/archived/feature/2026-08-06-continuable-subagent-interrupt.md +2026-08-06-continuable-subagent-interrupt.md: fcd06310d6440770aa33ea60a8964403887b5036 +2026-08-06-continuable-subagent-interrupt.zh.md: 873c35b976a659791a05708a8969414a30ab8e7c diff --git a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.md similarity index 95% rename from .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md rename to .agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.md index 3da06ff860..fcd06310d6 100644 --- a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md +++ b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.md @@ -1,6 +1,7 @@ # Agent Note: Continuable subagent current-turn interrupt Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-06-continuable-subagent-interrupt.zh.md) @@ -39,7 +40,7 @@ A human or ancestor can stop a runaway continuable turn without losing the child The address-only RPC exposes one bit of live residency: an absent target is accepted while a live target under a mismatched parent returns `subagent-unauthorized`. The single-user local Host trust model accepts that observability; a future multi-principal Host must revisit both authority and response indistinguishability. -The Web surface keeps Send and Stop as independent actions for a running continuable child: the client `Session.cancel()` routes Stop through `subagent.interrupt` (one-shot addresses stay uncancellable, ordinary sessions keep their existing primary Send/Stop toggle through `session.cancel`), while Send delivers follow-ups through the busy-Enter setting's mode — Queue by default, Steer when selected — exactly as plain Enter does ([busy Send button](../bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md)). A running parent-offline continuable child keeps the default composer with input and Send disabled but Stop reachable, returning to the read-only takeover once it stops ([Web subagent conversations](2026-07-27-web-subagent-conversations.md) owns the surrounding catalog and composer contract). +The Web surface keeps Send and Stop as independent actions for a running continuable child: the client `Session.cancel()` routes Stop through `subagent.interrupt` (one-shot addresses stay uncancellable, ordinary sessions keep their existing primary Send/Stop toggle through `session.cancel`), while Send delivers follow-ups through the busy-Enter setting's mode — Queue by default, Steer when selected — exactly as plain Enter does ([busy Send button](../../../../packages/client/ui-conversation/README.md)). A running parent-offline continuable child keeps the default composer with input and Send disabled but Stop reachable, returning to the read-only takeover once it stops ([Web subagent conversations](2026-07-27-web-subagent-conversations.md) owns the surrounding catalog and composer contract). The model-facing `interrupt_agent(agent_id)` tool in `dsh-tool-subagent-control` passes `exec.agent` as the `ancestor` authority and adds none of its own: the core primitive verifies live registry identity and recorded lineage, so the tool can name a direct child or a deeper descendant with the same generic `agent_id` parameter — deliberately not `subagent_id`, which would imply direct children only. Discovery rides `list_agents({ scope: 'descendants' })` over the `SubagentRuntime.listDescendants()` recursive catalog walk with `parentId`/`depth` per entry ([parent-catalog note](../architecture/2026-09-01-parent-owned-subagent-catalog.md) owns discovery); discovery is a hint, never authority. `send_message` keeps its exact-direct-parent authority — only interrupt is ancestor-wide. diff --git a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.zh.md similarity index 95% rename from .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md rename to .agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.zh.md index 0ef423905c..873c35b976 100644 --- a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md +++ b/.agents/notes/archived/feature/2026-08-06-continuable-subagent-interrupt.zh.md @@ -1,6 +1,7 @@ # Agent Note: Continuable subagent 当前轮次中断 Status: implemented +Archived: 2026-09-30 [English](2026-08-06-continuable-subagent-interrupt.md) | 中文 @@ -39,7 +40,7 @@ Host RPC `subagent.interrupt` 接收 continuable 的 `SubagentAddress` 并返回 仅凭地址的 RPC 会暴露一项关于在线驻留状态的二值信息:不存在的目标会被接受,而 parent 不匹配的在线目标会返回 `subagent-unauthorized`。单用户本地 Host 的信任模型接受这种可观察性;未来的多主体 Host 必须重新审视权限和响应不可区分性。 -在 Web 侧,正在运行的 continuable child 使用相互独立的 Send 与 Stop 操作:客户端 `Session.cancel()` 将 Stop 路由到 `subagent.interrupt`(one-shot 地址保持不可取消,普通会话仍通过 `session.cancel` 保留既有的 primary Send/Stop 切换),同时 Send 按繁忙态 Enter 设置的模式投递后续消息——默认 Queue,选择后为 Steer——与 plain Enter 完全一致([繁忙态 Send 按钮](../bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md))。parent 离线但仍在运行的 continuable child 保留默认 composer,禁用输入区与 Send,但 Stop 仍然可达;停止后恢复为只读接管界面(周边目录与 composer 约定由 [Web subagent 对话](2026-07-27-web-subagent-conversations.zh.md)拥有)。 +在 Web 侧,正在运行的 continuable child 使用相互独立的 Send 与 Stop 操作:客户端 `Session.cancel()` 将 Stop 路由到 `subagent.interrupt`(one-shot 地址保持不可取消,普通会话仍通过 `session.cancel` 保留既有的 primary Send/Stop 切换),同时 Send 按繁忙态 Enter 设置的模式投递后续消息——默认 Queue,选择后为 Steer——与 plain Enter 完全一致([繁忙态 Send 按钮](../../../../packages/client/ui-conversation/README.zh.md))。parent 离线但仍在运行的 continuable child 保留默认 composer,禁用输入区与 Send,但 Stop 仍然可达;停止后恢复为只读接管界面(周边目录与 composer 约定由 [Web subagent 对话](2026-07-27-web-subagent-conversations.zh.md)拥有)。 `dsh-tool-subagent-control` 中面向模型的 `interrupt_agent(agent_id)` 工具把 `exec.agent` 作为 `ancestor` 授权传入,自身不增加任何权限:核心原语校验在线注册表身份与记录的 lineage,因此该工具可以用同一个通用 `agent_id` 参数指定直接 child 或更深的后代——刻意不用会暗示仅限直接 child 的 `subagent_id`。发现依赖 `list_agents({ scope: 'descendants' })`,其底层是 `SubagentRuntime.listDescendants()` 递归目录遍历,每个条目带 `parentId`/`depth`(发现机制由[父目录 note](../architecture/2026-09-01-parent-owned-subagent-catalog.zh.md)拥有);发现只是提示,绝非权限。`send_message` 保持其确切直接 parent 权限——只有中断是 ancestor 级的。 diff --git a/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml new file mode 100644 index 0000000000..9bdc79c751 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.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/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md +2026-08-06-manager-owned-subagent-settlement-delivery.md: 30438dcc73ae63ed82a61938820f92482b159e08 +2026-08-06-manager-owned-subagent-settlement-delivery.zh.md: 22cd140cc200f3f754ff630243d3e8cfd679d74b diff --git a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md rename to .agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md index 7f6cfc9086..30438dcc73 100644 --- a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md +++ b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md @@ -1,6 +1,7 @@ # Agent Note: Settlement delivery belongs to the continuation manager Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-06-manager-owned-subagent-settlement-delivery.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md rename to .agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md index 23c5506f79..22cd140cc2 100644 --- a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md +++ b/.agents/notes/archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md @@ -1,6 +1,7 @@ # Agent Note: Settlement delivery belongs to the continuation manager Status: implemented +Archived: 2026-09-30 [English](2026-08-06-manager-owned-subagent-settlement-delivery.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-08-web-background-job-display.i18n.yaml b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.i18n.yaml new file mode 100644 index 0000000000..e7a460d0fa --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.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/archived/feature/2026-08-08-web-background-job-display.md +2026-08-08-web-background-job-display.md: 48bc73d9e2d41fc83e277f83d66c49da496b911a +2026-08-08-web-background-job-display.zh.md: 7222711dfa2628fb0a000d9f60ad7b8fd2ad1581 diff --git a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.md b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-08-web-background-job-display.md rename to .agents/notes/archived/feature/2026-08-08-web-background-job-display.md index 1ebd600e4d..48bc73d9e2 100644 --- a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.md +++ b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.md @@ -1,6 +1,7 @@ # Agent Note: Web background-job display Status: implemented +Archived: 2026-09-30 Superseded: the roster this note put on the session control stream (`jobsBySession`, `onJobsChanged`) now streams from the job controller's `job.list` into `ctx.jobs` — see [the jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md). The display decisions below (one roster, sections, durations, no kill control) still hold. diff --git a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.zh.md b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-08-web-background-job-display.zh.md rename to .agents/notes/archived/feature/2026-08-08-web-background-job-display.zh.md index 56d062b507..7222711dfa 100644 --- a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.zh.md +++ b/.agents/notes/archived/feature/2026-08-08-web-background-job-display.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 后台任务展示 Status: implemented +Archived: 2026-09-30 Superseded:本文放在会话控制流上的名册(`jobsBySession`、`onJobsChanged`)现在从 job 控制器的 `job.list` 流入 `ctx.jobs`——见 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)。下文的展示决策(单一名册、分组、时长、没有 kill 控件)仍然成立。 diff --git a/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml new file mode 100644 index 0000000000..9fd8522fdd --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.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/archived/feature/2026-08-10-subagent-approval-pinned-never.md +2026-08-10-subagent-approval-pinned-never.md: 7c91b65cf9a3777008a9de29dfc5e4135f03e099 +2026-08-10-subagent-approval-pinned-never.zh.md: ba280ed8899000a78a4e7253ff71c6510bf31b20 diff --git a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.md b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.md rename to .agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.md index a21c6b966b..7c91b65cf9 100644 --- a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.md +++ b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.md @@ -1,6 +1,7 @@ # Agent Note: Delegated subagents run with approvals pinned to `'never'` Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-10-subagent-approval-pinned-never.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.zh.md b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.zh.md rename to .agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.zh.md index 32f3673035..ba280ed889 100644 --- a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.zh.md +++ b/.agents/notes/archived/feature/2026-08-10-subagent-approval-pinned-never.zh.md @@ -1,6 +1,7 @@ # Agent Note: 被委派的 subagent 以钉定为 `'never'` 的审批策略运行 Status: implemented +Archived: 2026-09-30 [English](2026-08-10-subagent-approval-pinned-never.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml new file mode 100644 index 0000000000..25bcc2dc3f --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.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/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md +2026-08-11-background-job-completion-wakes-an-idle-owner.md: 566ffdd6d22036e3aa7f61efd8ed92df3ca35c93 +2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md: f8acd560be99f090ba982f84389886d7ed8ddef2 diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md rename to .agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md index f13f09d326..566ffdd6d2 100644 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md +++ b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md @@ -1,6 +1,7 @@ # Agent Note: Background job completion wakes an idle owner Status: implemented +Archived: 2026-09-30 Update: the `reported` bit and `onJobDone` this note relies on left the registry with the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md): `dsh-tool-jobs` now keeps the delivery ledger (a wait or an accepted `job_kill` claims the task) and skips teardown settlements by their `settled` cause, so the wake-or-inject decision below stands while the mechanism it names is the tool's ledger, not a registry flag. diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md rename to .agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md index f9b563ebbf..f8acd560be 100644 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md +++ b/.agents/notes/archived/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md @@ -1,6 +1,7 @@ # Agent Note: Background job completion wakes an idle owner Status: implemented +Archived: 2026-09-30 Update:本文依赖的 `reported` 位与 `onJobDone` 已随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)离开注册表:`dsh-tool-jobs` 现在持有投递台账(等待或被接受的 `job_kill` 认领任务),并按 `settled` 事件的 cause 跳过 teardown 结算,因此下文唤醒或注入的决策仍然成立,只是它提到的机制是工具的台账而非注册表标志。 diff --git a/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml new file mode 100644 index 0000000000..27568d7e7c --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.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/archived/feature/2026-08-11-deepseek-request-user-id-header.md +2026-08-11-deepseek-request-user-id-header.md: 8d77ee9b201bba2123d69d3033369aeddeeff3ac +2026-08-11-deepseek-request-user-id-header.zh.md: e0fcc74def0711eae8b767e78b87eb28d29f0ab0 diff --git a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.md b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.md rename to .agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.md index 54f59a3f69..8d77ee9b20 100644 --- a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.md +++ b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek request user and session identity headers Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-11-deepseek-request-user-id-header.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.zh.md b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.zh.md rename to .agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.zh.md index 4904a08fcf..e0fcc74def 100644 --- a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.zh.md +++ b/.agents/notes/archived/feature/2026-08-11-deepseek-request-user-id-header.zh.md @@ -1,6 +1,7 @@ # Agent Note: DeepSeek 请求用户与会话身份头部 Status: implemented +Archived: 2026-09-30 [English](2026-08-11-deepseek-request-user-id-header.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml new file mode 100644 index 0000000000..b19efe9cb2 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.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/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md +2026-08-11-minimal-profiles-bare-two-tool-runtime.md: ec391dc741b05b9c4be46a1bdf58f7b46b7bb406 +2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md: 060e38e2b192939a2dbc94aed25974e4f6c6e013 diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md rename to .agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md index 8cfa4aa71e..ec391dc741 100644 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md +++ b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md @@ -1,6 +1,7 @@ # Agent Note: Minimal profiles use a bare runtime Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md rename to .agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md index f1f916bdde..060e38e2b1 100644 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md +++ b/.agents/notes/archived/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md @@ -1,6 +1,7 @@ # Agent Note: minimal profile 使用裸运行时 Status: implemented +Archived: 2026-09-30 [English](2026-08-11-minimal-profiles-bare-two-tool-runtime.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml new file mode 100644 index 0000000000..e42131cef6 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.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/archived/feature/2026-08-18-arbitrary-seq-session-fork.md +2026-08-18-arbitrary-seq-session-fork.md: d2963caf57436cc231f4bc11d0bbb593752f1cde +2026-08-18-arbitrary-seq-session-fork.zh.md: bd7b33bff075811de8fd099465c4bed701f02d51 diff --git a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.md b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.md rename to .agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.md index c02ba6c1f2..d2963caf57 100644 --- a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.md +++ b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.md @@ -1,6 +1,7 @@ # Agent Note: Exact-event session forks with shared crash repair Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-18-arbitrary-seq-session-fork.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.zh.md b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.zh.md rename to .agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.zh.md index fe78e50531..bd7b33bff0 100644 --- a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.zh.md +++ b/.agents/notes/archived/feature/2026-08-18-arbitrary-seq-session-fork.zh.md @@ -1,6 +1,7 @@ # Agent Note:复用崩溃修复的精确事件会话分叉 Status: implemented +Archived: 2026-09-30 [English](2026-08-18-arbitrary-seq-session-fork.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.i18n.yaml b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.i18n.yaml new file mode 100644 index 0000000000..7b77748ddb --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.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/archived/feature/2026-08-18-model-selected-subagent-routes.md +2026-08-18-model-selected-subagent-routes.md: 9c9487b652e4540264ee5026b17a1bc3b3cfb70e +2026-08-18-model-selected-subagent-routes.zh.md: 4dcbe37ed765dcc0b911bde05afa9f25702e7c45 diff --git a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md rename to .agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.md index 9cdbdbec3b..9c9487b652 100644 --- a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md +++ b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.md @@ -1,6 +1,7 @@ # Agent Note: Model-selected subagent routes Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-18-model-selected-subagent-routes.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.zh.md b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.zh.md rename to .agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.zh.md index 3b81f54173..4dcbe37ed7 100644 --- a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.zh.md +++ b/.agents/notes/archived/feature/2026-08-18-model-selected-subagent-routes.zh.md @@ -1,6 +1,7 @@ # Agent Note: 模型选择的 subagent 路由 Status: implemented +Archived: 2026-09-30 [English](2026-08-18-model-selected-subagent-routes.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.i18n.yaml b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.i18n.yaml new file mode 100644 index 0000000000..e38d0860a3 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.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/archived/feature/2026-08-20-unified-image-request-pipeline.md +2026-08-20-unified-image-request-pipeline.md: 75b5fdcd47d23b58a0e1727f9298bc5696cf8268 +2026-08-20-unified-image-request-pipeline.zh.md: 7e40c7c6c5ccd6178817e42610ac49a9b567bd02 diff --git a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.md b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.md rename to .agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.md index b79eeb7d90..75b5fdcd47 100644 --- a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.md +++ b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.md @@ -1,6 +1,7 @@ # Agent Note: Unified normalized attachments, request versions, and provider files Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-20-unified-image-request-pipeline.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.zh.md b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.zh.md rename to .agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.zh.md index 4442347152..7e40c7c6c5 100644 --- a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.zh.md +++ b/.agents/notes/archived/feature/2026-08-20-unified-image-request-pipeline.zh.md @@ -1,6 +1,7 @@ # Agent Note: 统一规范化附件、请求版本与提供方文件 Status: implemented +Archived: 2026-09-30 [English](2026-08-20-unified-image-request-pipeline.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml new file mode 100644 index 0000000000..447c34820b --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.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/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md +2026-08-22-fire-and-forget-webhook-sessions.md: 8b13fcf5c29c803c58500ce6b1e196743e623023 +2026-08-22-fire-and-forget-webhook-sessions.zh.md: 1ce583abe9975317fc6de772a99dbe22e28a25e2 diff --git a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md rename to .agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md index 08f718b806..8b13fcf5c2 100644 --- a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md +++ b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md @@ -1,6 +1,7 @@ # Agent Note: Fire-and-forget webhook Sessions Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-22-fire-and-forget-webhook-sessions.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md rename to .agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md index f015d993e6..1ce583abe9 100644 --- a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md +++ b/.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md @@ -1,6 +1,7 @@ # Agent Note: Fire-and-forget webhook Session Status: implemented +Archived: 2026-09-30 [English](2026-08-22-fire-and-forget-webhook-sessions.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.i18n.yaml b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.i18n.yaml new file mode 100644 index 0000000000..cc3df2fcaf --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.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/archived/feature/2026-08-22-standard-acp-automation-controls.md +2026-08-22-standard-acp-automation-controls.md: 3973cfce341349aeebaa6c2858bb58bcb2a56c17 +2026-08-22-standard-acp-automation-controls.zh.md: a6ad21631d1247e0a716221ab943c0f4077933cf diff --git a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.md b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.md rename to .agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.md index dd1e39b84f..3973cfce34 100644 --- a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.md +++ b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.md @@ -1,6 +1,7 @@ # Agent Note: Standard ACP v1 automation controls Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-22-standard-acp-automation-controls.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.zh.md b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.zh.md rename to .agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.zh.md index 2d7160bda1..a6ad21631d 100644 --- a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.zh.md +++ b/.agents/notes/archived/feature/2026-08-22-standard-acp-automation-controls.zh.md @@ -1,6 +1,7 @@ # Agent Note: 标准 ACP v1 自动化控制 Status: implemented +Archived: 2026-09-30 [English](2026-08-22-standard-acp-automation-controls.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.i18n.yaml b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.i18n.yaml new file mode 100644 index 0000000000..b1ea7650b2 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-08-24-activity-observation-seam.md +2026-08-24-activity-observation-seam.md: 069d8d709790a407d19948463a7a182abdbd919e +2026-08-24-activity-observation-seam.zh.md: 5ef1edf132e242a92a68f1193f700ef5da0694ca diff --git a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.md b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-24-activity-observation-seam.md rename to .agents/notes/archived/feature/2026-08-24-activity-observation-seam.md index 21fe08fd21..069d8d7097 100644 --- a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.md +++ b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.md @@ -1,6 +1,7 @@ # Agent Note: The activity observation seam (`ctx.activities`) and live output streaming to the Web client Status: implemented +Archived: 2026-09-30 Superseded: the standalone seam described here was folded into `ctx.jobs` as the per-job observation record — see [jobs absorb the record](../architecture/2026-09-01-jobs-absorb-activity-record.md); the durable-vs-live analysis below still holds. diff --git a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.zh.md b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-24-activity-observation-seam.zh.md rename to .agents/notes/archived/feature/2026-08-24-activity-observation-seam.zh.md index ffb6766b3e..5ef1edf132 100644 --- a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.zh.md +++ b/.agents/notes/archived/feature/2026-08-24-activity-observation-seam.zh.md @@ -1,6 +1,7 @@ # Agent Note:activity 观察 seam(`ctx.activities`)与 Web 客户端实时输出流 -状态:implemented +Status: implemented +Archived: 2026-09-30 Superseded:本文描述的独立 seam 已折入 `ctx.jobs`,成为逐 job 的观测 record——见 [jobs 吸收 record](../architecture/2026-09-01-jobs-absorb-activity-record.zh.md);下文关于持久化与实时通道的分析仍然成立。 diff --git a/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml new file mode 100644 index 0000000000..6480a178d0 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.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/archived/feature/2026-08-25-feedback-gated-telemetry-default.md +2026-08-25-feedback-gated-telemetry-default.md: 3b2075477d7a9a5fd9d175a768ca8bae78b72a75 +2026-08-25-feedback-gated-telemetry-default.zh.md: 6c8267ef3087a942e3e7ccfe6aea600c7ebd0ef8 diff --git a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md rename to .agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.md index 1a5ecd6b34..3b2075477d 100644 --- a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md +++ b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.md @@ -1,6 +1,7 @@ # Agent Note: Feedback-gated session-telemetry default Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-25-feedback-gated-telemetry-default.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.zh.md b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.zh.md rename to .agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.zh.md index 55936aa404..6c8267ef30 100644 --- a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.zh.md +++ b/.agents/notes/archived/feature/2026-08-25-feedback-gated-telemetry-default.zh.md @@ -1,6 +1,7 @@ # Agent Note: 反馈门控的会话遥测默认值 Status: implemented +Archived: 2026-09-30 [English](2026-08-25-feedback-gated-telemetry-default.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml new file mode 100644 index 0000000000..6331bdd027 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.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/archived/feature/2026-08-25-promote-open-anywhere-plugin.md +2026-08-25-promote-open-anywhere-plugin.md: e333341ea4d9f3c100c0558628c6b8a81766ae09 +2026-08-25-promote-open-anywhere-plugin.zh.md: 57a51eec59a0a977d8cb7b0a53f9f25c60c6be63 diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md rename to .agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.md index e5a435d24c..e333341ea4 100644 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md +++ b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.md @@ -1,6 +1,7 @@ # Agent Note: Promote open-anywhere from community plugin to first-party package Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-25-promote-open-anywhere-plugin.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md rename to .agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.zh.md index 108091f3e7..57a51eec59 100644 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.zh.md +++ b/.agents/notes/archived/feature/2026-08-25-promote-open-anywhere-plugin.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将 open-anywhere 从社区插件转正为第一方包 Status: implemented +Archived: 2026-09-30 [English](2026-08-25-promote-open-anywhere-plugin.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-08-26-human-job-kill.i18n.yaml b/.agents/notes/archived/feature/2026-08-26-human-job-kill.i18n.yaml new file mode 100644 index 0000000000..cbaada2a48 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-26-human-job-kill.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/archived/feature/2026-08-26-human-job-kill.md +2026-08-26-human-job-kill.md: 22885aac4ef185103492dd2a99cf40e71139760a +2026-08-26-human-job-kill.zh.md: 2d9736f41d810436ec467e1661e51ad88a0caa7e diff --git a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.md b/.agents/notes/archived/feature/2026-08-26-human-job-kill.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-26-human-job-kill.md rename to .agents/notes/archived/feature/2026-08-26-human-job-kill.md index efffdbd85b..22885aac4e 100644 --- a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.md +++ b/.agents/notes/archived/feature/2026-08-26-human-job-kill.md @@ -1,6 +1,7 @@ # Agent Note: Human job kill — an unclaimed terminal report instead of a second cancellation path Status: implemented +Archived: 2026-09-30 Update: the `reported` option this note introduced is gone with the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md). The registry keeps no report bit; `dsh-tool-jobs` claims only its own `job_kill` and waits in a ledger, so a human `job.kill` leaves the notice due by construction. The reason merge, the `job.kill` Remote, and the two-press control are unchanged. diff --git a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.zh.md b/.agents/notes/archived/feature/2026-08-26-human-job-kill.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-26-human-job-kill.zh.md rename to .agents/notes/archived/feature/2026-08-26-human-job-kill.zh.md index 206acca008..2d9736f41d 100644 --- a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.zh.md +++ b/.agents/notes/archived/feature/2026-08-26-human-job-kill.zh.md @@ -1,6 +1,7 @@ # Agent Note:人工任务终止——用「未认领的终态报告」而非第二条取消路径 -状态:已实现 +Status: implemented +Archived: 2026-09-30 Update:本文引入的 `reported` 选项已随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md) 消失。注册表不再保留播报位;`dsh-tool-jobs` 只在台账里认领自己的 `job_kill` 与等待,因此人类的 `job.kill` 天然让通知保持待送达。原因合并、`job.kill` Remote 与两击式控件保持不变。 diff --git a/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml new file mode 100644 index 0000000000..f9973e47f0 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.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/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md +2026-08-26-shell-execute-projection-and-jobs-at-start.md: 8786d7b727d6c3bf219cd0a1fed4b9e565bd10bc +2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md: 9dd7753c295aed973fa0891aca1cf8ab24b3e602 diff --git a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md rename to .agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md index 2a03cd9f99..8786d7b727 100644 --- a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md +++ b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md @@ -1,6 +1,7 @@ # Agent Note: One shell execute() — foreground as a projection, timeout as a bounded wait on the job Status: implemented +Archived: 2026-09-30 Update: the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md) owns the output ring, the cursors, and the pull sources every registered command feeds. diff --git a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md rename to .agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md index bdc6cc432d..9dd7753c29 100644 --- a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md +++ b/.agents/notes/archived/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md @@ -1,6 +1,7 @@ # Agent Note:唯一的 shell execute()——前台是投影,超时是对 job 的有界等待 -状态:已实现 +Status: implemented +Archived: 2026-09-30 Update:[jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)拥有每条已登记命令现在填充的输出环、游标与拉取源。 diff --git a/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml new file mode 100644 index 0000000000..4120d95a38 --- /dev/null +++ b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.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/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.md +2026-08-27-continuable-subagent-human-inbox-control.md: e2b9738a19a8d6ecfa5601000a55468ad422d1f1 +2026-08-27-continuable-subagent-human-inbox-control.zh.md: 9d9d9ff4f8949f45a3e93e03bdf138fee7995bc9 diff --git a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md rename to .agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.md index 6bf8ceb2b8..e2b9738a19 100644 --- a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md +++ b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.md @@ -1,6 +1,7 @@ # Agent Note: Human inbox controls for continuable subagents Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-27-continuable-subagent-human-inbox-control.zh.md) diff --git a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md rename to .agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md index 081fb84f75..9d9d9ff4f8 100644 --- a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md +++ b/.agents/notes/archived/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md @@ -1,6 +1,7 @@ # Agent Note: 可继续 subagent 的人类 inbox 控制 Status: implemented +Archived: 2026-09-30 [English](2026-08-27-continuable-subagent-human-inbox-control.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.i18n.yaml b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.i18n.yaml new file mode 100644 index 0000000000..d16790f227 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.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/archived/feature/2026-09-01-workflow-run-in-background.md +2026-09-01-workflow-run-in-background.md: 548d6b8c35f585f6e0c2d057674494c8985adb92 +2026-09-01-workflow-run-in-background.zh.md: 1ab9acf95af72b7476892fd9be77da179e61c791 diff --git a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.md b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.md rename to .agents/notes/archived/feature/2026-09-01-workflow-run-in-background.md index 5a141b3841..548d6b8c35 100644 --- a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.md +++ b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.md @@ -1,6 +1,7 @@ # Agent Note: Workflow runs gain `run_in_background` over the job registry Status: implemented +Archived: 2026-09-30 Update: with the [jobs seam consolidation](../architecture/2026-09-03-jobs-seam-consolidation.md) the run registers a `JobSpec` (no `record` flag), narrates through `JobHandle.append` and `updateProgress`, and returns its rendered value as `JobOutcome.result`, which the model's first read after settlement carries once. diff --git a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.zh.md b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.zh.md rename to .agents/notes/archived/feature/2026-09-01-workflow-run-in-background.zh.md index 102a398d95..1ab9acf95a 100644 --- a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.zh.md +++ b/.agents/notes/archived/feature/2026-09-01-workflow-run-in-background.zh.md @@ -1,6 +1,7 @@ # Agent Note:workflow 运行经任务注册表获得 `run_in_background` -状态:已实现 +Status: implemented +Archived: 2026-09-30 Update:随 [jobs seam 收敛](../architecture/2026-09-03-jobs-seam-consolidation.zh.md),该运行注册的是 `JobSpec`(没有 `record` 标志),通过 `JobHandle.append` 与 `updateProgress` 叙述进度,并把渲染后的值作为 `JobOutcome.result` 返回,模型在结算后的第一次读取携带它一次。 diff --git a/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml new file mode 100644 index 0000000000..62be6a8d51 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.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/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.md +2026-09-05-sidebar-text-preview-and-file-tree.md: 61b697481b5b70eda74f762486860f3f788f62f0 +2026-09-05-sidebar-text-preview-and-file-tree.zh.md: 3f63031fd1fe9425203d5f2eb6e1cfe66ed53b78 diff --git a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md rename to .agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.md index e978043545..61b697481b 100644 --- a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md +++ b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.md @@ -1,6 +1,7 @@ # Agent Note: Sidebar text preview and file tree Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-sidebar-text-preview-and-file-tree.zh.md) @@ -16,7 +17,7 @@ Three tab types ship with the Sidebar: the **guide** (`ui-sidebar-right`), the * ### The guide -[Default pages and close protection](2026-09-08-sidebar-default-pages.md) supersedes this section's default-guide selection; guide registration, replacement and uniqueness remain unchanged. +[Sidebar guide rules](../../../../packages/client/ui-sidebar-right/README.md#the-guide) define default-page selection and close protection; guide registration, replacement and uniqueness remain unchanged. The guide is what a pane shows before it holds content. Its registration is `{ id: '@deepseek-ai/dsh-client-ui-sidebar-right/guide', kind: 'guide', priority: 'builtin', title }` with no `patterns`: a guide views nothing, so it is opened by kind through `openTab` and recorded under the page address `sidebar://guide`, which is the registry's bookkeeping and never composed by a caller. The tab's title is `开始` / `Start`, captured into the layout record when the pane is seeded, so a later language change relabels the type and not tabs already open. diff --git a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md rename to .agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md index 088d4a58c9..3f63031fd1 100644 --- a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md +++ b/.agents/notes/archived/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md @@ -1,6 +1,7 @@ # Agent Note: Sidebar 文本预览与文件树 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-sidebar-text-preview-and-file-tree.md) | 中文 @@ -16,7 +17,7 @@ Sidebar 随包交付三个 tab 类型:**引导页**(`ui-sidebar-right`)、 ### 引导页 -[默认页与关闭保护](2026-09-08-sidebar-default-pages.zh.md)取代本节的默认引导选择;引导页注册、替换和唯一性保持不变。 +[Sidebar 引导页规则](../../../../packages/client/ui-sidebar-right/README.zh.md#the-guide)定义默认页选择与关闭保护;引导页注册、替换和唯一性保持不变。 引导页是 pane 承载内容之前显示的东西。它的注册定义是 `{ id: '@deepseek-ai/dsh-client-ui-sidebar-right/guide', kind: 'guide', priority: 'builtin', title }`,没有 `patterns`:引导页不查看任何东西,所以经 `openTab` 按 kind 打开,并记在页地址 `sidebar://guide` 之下——那是注册表自己的记账,调用方从不拼它。tab 标题是 `开始` / `Start`,在 pane 播种时捕获进布局记录,于是之后切换语言只重标类型,不改已开着的 tab。 diff --git a/.agents/notes/archived/feature/2026-09-07-model-switch-notice.i18n.yaml b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.i18n.yaml new file mode 100644 index 0000000000..e1692973e4 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.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/archived/feature/2026-09-07-model-switch-notice.md +2026-09-07-model-switch-notice.md: c6fefe71f949ce6d07e4ab4f2b7b2334e47914fa +2026-09-07-model-switch-notice.zh.md: fc4bffb6c8dba1c33fc71d488efded0a342f7cac diff --git a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.md b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-07-model-switch-notice.md rename to .agents/notes/archived/feature/2026-09-07-model-switch-notice.md index cb2f915f13..c6fefe71f9 100644 --- a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.md +++ b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.md @@ -1,6 +1,7 @@ # Agent Note: Model-visible route-change notices Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-model-switch-notice.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.zh.md b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-07-model-switch-notice.zh.md rename to .agents/notes/archived/feature/2026-09-07-model-switch-notice.zh.md index b6dd8da027..fc4bffb6c8 100644 --- a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.zh.md +++ b/.agents/notes/archived/feature/2026-09-07-model-switch-notice.zh.md @@ -1,6 +1,7 @@ # Agent Note: 模型可见的路由切换提示 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-model-switch-notice.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-07-public-web-deployments.i18n.yaml b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.i18n.yaml new file mode 100644 index 0000000000..2460b49113 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.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/archived/feature/2026-09-07-public-web-deployments.md +2026-09-07-public-web-deployments.md: e7cf809bce4186c173df783b0fb383ff8abb4140 +2026-09-07-public-web-deployments.zh.md: b218ba9e7d759aba961f42c76bc742e59469bafd diff --git a/.agents/notes/archived/feature/2026-09-07-public-web-deployments.md b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.md new file mode 100644 index 0000000000..e7cf809bce --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.md @@ -0,0 +1,38 @@ +# Agent Note: Advertised public HTTP(S) Web roots + +Status: implemented +Archived: 2026-09-30 + +English | [中文](2026-09-07-public-web-deployments.zh.md) + +## Problem + +A forwarding gateway can expose the Web GUI beneath a shared HTTP(S) origin and strip its path prefix before forwarding, so one process answers loopback `/` and an external mount at once — a mount the listener cannot know and an origin only the operator knows. A proxied request carries that external authority in `Host`, which the browser-trust fence refuses until the deployment declares it. + +## Decision + +`--public-url` is an optional advertised HTTP(S) application root — `http(s)://`, an optional forwarding prefix, and no credentials, query, or fragment — reaching the Web bundle's `publicUrl` config through `src/public-url.ts`. It supplies the printed and opened URL, `DSH_WEB_URL`, and the web-surface orientation, and configures no routing, client transport, or cookie scope. The config field and the CLI flag are both advertisement only: the browser-visible authority must be named with repeatable `--trusted-host`, and the fence still validates `Host` and `Origin` per request, so advertisement grants neither identity nor authentication. + +The advertised root is the first production input that can carry a mount prefix, so the launch flow adds the launch token without collapsing that mount. Network listening stays explicit and independent — the shipped CLI refuses a wildcard `--host` — and the proxy owns the external leg, whose required operations the [public deployments guide](../../../../docs/user/guide/public-deployments.md) publishes. + +## Alternatives considered + +**Let the advertised root select routing and cookies.** One root would supply every browser URL and cookie path, but the advertisement would then decide request routing, so one process could no longer serve loopback and a prefixed mount at once. + +**Fold the advertised host into trustedHosts.** Rejected: advertisement must not change the fence, and #4181 keeps trust explicit. + +**Have the backend emit mount-aware URLs.** A configured `` or prefixed asset paths would require the webserver to know the external mount and duplicate knowledge the proxy already holds. + +**Infer the external leg from requests or forwarded headers.** Request-derived authority is spoofable, and accepting stripped and preserved paths alike leaves the deployment ambiguous. + +**Infer network exposure from the advertised root or a trusted host.** A display or authority choice cannot authorize opening every interface, so listener selection stays explicit and a wildcard host stays rejected. + +## Consequences + +The advertised URL carries no credential until the process adds its launch token, and a misconfigured address can still disclose it. An HTTP root carries that tokenized URL and the session cookie without TLS, and an HTTPS root encrypts only the browser-to-proxy leg. + +Advertising an authority admits nothing on its own: a browser reaching the deployment under any non-loopback authority stays outside the fence until `--trusted-host` names it. + +## Testing + +`tests/public-url.spec.ts` covers URL validation and normalization; `tests/startup.spec.ts` covers the flag over a real Loader tree, including that the advertised host leaves the accepted authorities untouched; `tests/web-app.spec.ts` covers the printed URL, handoff, web-surface prompt, `DSH_WEB_URL`, and a `null` field advertising loopback; `apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts` boots the built profile with `--public-url` behind the reference prefix-stripping proxy and authenticates through both the advertised mount and loopback, so the bundle patch's `publicUrl` expression is exercised as shipped. diff --git a/.agents/notes/archived/feature/2026-09-07-public-web-deployments.zh.md b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.zh.md new file mode 100644 index 0000000000..b218ba9e7d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-07-public-web-deployments.zh.md @@ -0,0 +1,38 @@ +# Agent Note: 对外公告的公开 HTTP(S) Web 根 + +Status: implemented +Archived: 2026-09-30 + +[English](2026-09-07-public-web-deployments.md) | 中文 + +## Problem + +转发网关可以把 Web GUI 暴露在共享的 HTTP(S) origin 之下,并在转发前剥离其路径前缀,因此同一个进程要同时应答 loopback `/` 与外部挂载——该挂载监听器无从得知,而该 origin 只有运维方知道。经代理转发的请求会在 `Host` 中携带这个外部 authority,浏览器信任栅栏在部署声明它之前会予以拒绝。 + +## Decision + +`--public-url` 是可选的对外公告 HTTP(S) 应用根——`http(s)://`,可带转发前缀,不允许凭据、query 或 fragment——经由 `src/public-url.ts` 抵达 Web bundle 的 `publicUrl` 配置。它提供打印与打开的 URL、`DSH_WEB_URL` 与 web 表层定位,且不配置任何路由、客户端传输或 cookie 作用域。配置字段与 CLI flag 都只是公告:浏览器可见的 authority 必须用可重复的 `--trusted-host` 点名,栅栏才会接受它;栅栏仍逐请求校验 `Host` 与 `Origin`,因此公告既不授予身份,也不代表已认证。 + +公告根是首个可能携带挂载前缀的生产输入,因此启动流程加上启动 token 时不会压平该挂载。网络监听始终显式且独立——随附 CLI 拒绝通配的 `--host`——外部链路归代理所有,部署必须提供的操作发布在[公开部署指南](../../../../docs/user/guide/public-deployments.zh.md)。 + +## Alternatives considered + +**让公告根同时决定路由与 cookie。** 单一根会提供全部浏览器 URL 与 cookie 路径,但公告随即成为请求路由决策,一个进程再也无法同时服务 loopback 与带前缀的挂载。 + +**把公告主机并入 trustedHosts。** 否决:公告不得改变栅栏,且 #4181 让信任始终显式。 + +**让后端产出感知挂载的 URL。** 配置 `` 或带前缀的资源路径会要求 webserver 了解外部挂载,并重复代理已经掌握的信息。 + +**从请求或转发头推断外部链路。** 请求派生的 authority 可被伪造,而同时接受被剥离与未被剥离的路径会让部署变得含糊。 + +**从公告根或可信主机推断网络暴露。** 展示或 authority 选择不能授权打开所有网卡,因此监听选择始终显式,通配主机也始终被拒绝。 + +## Consequences + +公告 URL 在进程附带启动 token 之前不含凭据,而配置错误的地址仍可能泄露它。HTTP 根会在无 TLS 的情况下携带带 token 的 URL 与会话 cookie,HTTPS 根则只加密浏览器到代理这一段。 + +公告本身不接纳任何 authority:浏览器以任何非 loopback authority 访问部署,都会一直处于栅栏之外,直到 `--trusted-host` 点名它。 + +## Testing + +`tests/public-url.spec.ts` 覆盖 URL 校验与归一化;`tests/startup.spec.ts` 在真实 Loader 树上覆盖该 flag,包括公告主机不会改动被接受的 authority;`tests/web-app.spec.ts` 覆盖打印 URL、交接、web 表层提示词、`DSH_WEB_URL`,以及字段为 `null` 时公告 loopback;`apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts` 以 `--public-url` 在参考剥前缀代理之后启动已构建的 profile,并同时经公告挂载与 loopback 完成认证,因此 bundle patch 中的 `publicUrl` 表达式按出厂形态得到执行。 diff --git a/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.i18n.yaml b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.i18n.yaml new file mode 100644 index 0000000000..b5292d274d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.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/archived/feature/2026-09-07-session-prose-local-media-display.md +2026-09-07-session-prose-local-media-display.md: c5dcfa0fca7da7057b81704cf222471fba623c14 +2026-09-07-session-prose-local-media-display.zh.md: 754c1021997bc3caa9b110eb4f2794f54a94558f diff --git a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.md b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.md rename to .agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.md index fe769d1cbe..c5dcfa0fca 100644 --- a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.md +++ b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.md @@ -1,6 +1,7 @@ # Agent Note: Session prose local media paths display through a same-origin file route Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-session-prose-local-media-display.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.zh.md b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.zh.md rename to .agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.zh.md index 2cfff4016f..754c102199 100644 --- a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.zh.md +++ b/.agents/notes/archived/feature/2026-09-07-session-prose-local-media-display.zh.md @@ -1,6 +1,7 @@ # Agent Note: 会话正文本地媒体路径通过同源文件路由显示 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-session-prose-local-media-display.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml new file mode 100644 index 0000000000..fc70e47b50 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.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/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md +2026-09-08-desktop-uninstall-preserve-dsh-home.md: f4c9b1b63a2f47840ae28ea8e91f4d277d0f8533 +2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md: c195049b1cbd35d02ff9a7b7283d731f7702f593 diff --git a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md rename to .agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md index 921d9681aa..f4c9b1b63a 100644 --- a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md +++ b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md @@ -1,6 +1,7 @@ # Agent Note: Windows uninstall retains the Harness home Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md rename to .agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md index cb9187bb6d..c195049b1c 100644 --- a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md +++ b/.agents/notes/archived/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 卸载保留 Harness 主目录 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-desktop-uninstall-preserve-dsh-home.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml new file mode 100644 index 0000000000..7c70d4e579 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.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/archived/feature/2026-09-08-feedback-dialog-and-categories.md +2026-09-08-feedback-dialog-and-categories.md: d3a630a4bbb7304807a4a322e553fe75a6455efa +2026-09-08-feedback-dialog-and-categories.zh.md: de19580d3200f141858fe335d3976ed272a4d5a2 diff --git a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md rename to .agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.md index 1d544e6ef9..d3a630a4bb 100644 --- a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md +++ b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.md @@ -1,6 +1,7 @@ # Agent Note: Feedback dialog, categories, and the acknowledgement toast Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-feedback-dialog-and-categories.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.zh.md b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.zh.md rename to .agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.zh.md index 6e58820bf3..de19580d32 100644 --- a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.zh.md +++ b/.agents/notes/archived/feature/2026-09-08-feedback-dialog-and-categories.zh.md @@ -1,6 +1,7 @@ # Agent Note: 反馈弹窗、分类与确认 toast Status: implemented +Archived: 2026-09-30 [English](2026-09-08-feedback-dialog-and-categories.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.i18n.yaml b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.i18n.yaml new file mode 100644 index 0000000000..d710c56b6d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.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/archived/feature/2026-09-08-file-display-through-filesystem.md +2026-09-08-file-display-through-filesystem.md: 26af6ce3babb4400caf4d056fa358af5197a6725 +2026-09-08-file-display-through-filesystem.zh.md: 4627685508b37cb96014164c9390e24219282051 diff --git a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.md b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.md rename to .agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.md index b1f93f8c9f..26af6ce3ba 100644 --- a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.md +++ b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.md @@ -1,6 +1,7 @@ # Agent Note: Authenticated file display reuses filesystem byte reads Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-file-display-through-filesystem.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.zh.md b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.zh.md rename to .agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.zh.md index faa3fdb757..4627685508 100644 --- a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.zh.md +++ b/.agents/notes/archived/feature/2026-09-08-file-display-through-filesystem.zh.md @@ -1,6 +1,7 @@ # Agent Note: 鉴权文件展示复用文件系统字节读取 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-file-display-through-filesystem.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml new file mode 100644 index 0000000000..104b0fc326 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.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/archived/feature/2026-09-09-headless-machine-readable-run-surface.md +2026-09-09-headless-machine-readable-run-surface.md: 5e0e4f9b0b41a6a988cf4b5a361e438c87ff513f +2026-09-09-headless-machine-readable-run-surface.zh.md: 4c3bfa3c85a3f5c75c3a87bd39b1cd82bec39108 diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md rename to .agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.md index 317f213095..5e0e4f9b0b 100644 --- a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md +++ b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.md @@ -1,6 +1,7 @@ # Agent Note: Headless machine-readable run surface Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-headless-machine-readable-run-surface.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md rename to .agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.zh.md index 0114f87aef..4c3bfa3c85 100644 --- a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.zh.md +++ b/.agents/notes/archived/feature/2026-09-09-headless-machine-readable-run-surface.zh.md @@ -1,6 +1,7 @@ # Agent Note: Headless 的机器可读运行接口 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-headless-machine-readable-run-surface.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.i18n.yaml b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.i18n.yaml new file mode 100644 index 0000000000..835c249c01 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.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/archived/feature/2026-09-09-present-filesystem-access.md +2026-09-09-present-filesystem-access.md: bc4be249d5d28141333101e69a09b0ae2c9fbd3b +2026-09-09-present-filesystem-access.zh.md: 0828d42946a925efdd18130b8592a287e8e3faf3 diff --git a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.md b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-present-filesystem-access.md rename to .agents/notes/archived/feature/2026-09-09-present-filesystem-access.md index 5c9d5bec47..bc4be249d5 100644 --- a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.md +++ b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.md @@ -1,6 +1,7 @@ # Agent Note: Present follows Session filesystem access Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-present-filesystem-access.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.zh.md b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-present-filesystem-access.zh.md rename to .agents/notes/archived/feature/2026-09-09-present-filesystem-access.zh.md index 6387f07b17..0828d42946 100644 --- a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.zh.md +++ b/.agents/notes/archived/feature/2026-09-09-present-filesystem-access.zh.md @@ -1,6 +1,7 @@ # Agent Note:Present 遵循 Session 文件系统访问规则 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-present-filesystem-access.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml new file mode 100644 index 0000000000..c8b432a440 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.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/archived/feature/2026-09-09-ptc-trajectory-code-inspection.md +2026-09-09-ptc-trajectory-code-inspection.md: 7e196a370c73bafe166d2bc8af2116e04d6883c5 +2026-09-09-ptc-trajectory-code-inspection.zh.md: d46d875ea19c36595499e336f26b281f885f18ad diff --git a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.md b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.md rename to .agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.md index 19779d20e8..7e196a370c 100644 --- a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.md +++ b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.md @@ -1,6 +1,7 @@ # Agent Note: Inspect recorded PTC source in Trajectory Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-ptc-trajectory-code-inspection.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md rename to .agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md index 7cdb2321d6..d46d875ea1 100644 --- a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md +++ b/.agents/notes/archived/feature/2026-09-09-ptc-trajectory-code-inspection.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在 Trajectory 中检查已记录的 PTC 源码 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-ptc-trajectory-code-inspection.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.i18n.yaml b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.i18n.yaml new file mode 100644 index 0000000000..50fc47af45 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.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/archived/feature/2026-09-10-composer-reference-previews.md +2026-09-10-composer-reference-previews.md: b6603570ff3ce9ae8d5a4628d92ff81226403219 +2026-09-10-composer-reference-previews.zh.md: 18df7582e01187b491a55a1930ff4074d0ff9ffd diff --git a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.md b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-10-composer-reference-previews.md rename to .agents/notes/archived/feature/2026-09-10-composer-reference-previews.md index e9425851c1..b6603570ff 100644 --- a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.md +++ b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.md @@ -1,6 +1,7 @@ # Agent Note: Composer reference previews Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-composer-reference-previews.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.zh.md b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-10-composer-reference-previews.zh.md rename to .agents/notes/archived/feature/2026-09-10-composer-reference-previews.zh.md index c47718468b..18df7582e0 100644 --- a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.zh.md +++ b/.agents/notes/archived/feature/2026-09-10-composer-reference-previews.zh.md @@ -1,6 +1,7 @@ # Agent Note: 输入框引用预览 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-composer-reference-previews.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml new file mode 100644 index 0000000000..b691470d2b --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.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/archived/feature/2026-09-10-symmetric-message-feedback-submission.md +2026-09-10-symmetric-message-feedback-submission.md: 9fb9e1b0f508bf926e4cf4ae9eb1c2dda6e29757 +2026-09-10-symmetric-message-feedback-submission.zh.md: 58cf25b23adcf6135b070d262b2e9b226fb80f95 diff --git a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.md b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.md rename to .agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.md index 51c754dbd3..9fb9e1b0f5 100644 --- a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.md +++ b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.md @@ -1,6 +1,7 @@ # Agent Note: Symmetric message feedback submission Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-symmetric-message-feedback-submission.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.zh.md b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.zh.md rename to .agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.zh.md index 0ba5d1a1c5..58cf25b23a 100644 --- a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.zh.md +++ b/.agents/notes/archived/feature/2026-09-10-symmetric-message-feedback-submission.zh.md @@ -1,6 +1,7 @@ # Agent Note: 对称消息反馈提交 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-symmetric-message-feedback-submission.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml new file mode 100644 index 0000000000..d2540328f3 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.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/archived/feature/2026-09-11-desktop-mandatory-update-client.md +2026-09-11-desktop-mandatory-update-client.md: 86d1861469cde2b01722d9fdd899d1bed078f135 +2026-09-11-desktop-mandatory-update-client.zh.md: c8b2a3f0f1e5e043258b041078af986a741c72be diff --git a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md rename to .agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.md index 838403a2b1..86d1861469 100644 --- a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md +++ b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.md @@ -1,6 +1,7 @@ # Agent Note: Desktop mandatory-update client and blocking window Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-desktop-mandatory-update-client.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md rename to .agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.zh.md index f3c862fdcc..c8b2a3f0f1 100644 --- a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md +++ b/.agents/notes/archived/feature/2026-09-11-desktop-mandatory-update-client.zh.md @@ -1,6 +1,7 @@ # Agent Note: Desktop 强更客户端与阻塞窗口 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-desktop-mandatory-update-client.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml new file mode 100644 index 0000000000..712021e78e --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.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/archived/feature/2026-09-11-sidebar-document-preview-polish.md +2026-09-11-sidebar-document-preview-polish.md: 2629646f803a45223421d371d7e92a35908e37d9 +2026-09-11-sidebar-document-preview-polish.zh.md: 2cc93195b1bddba6233c55b56018ac20db58c63a diff --git a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md rename to .agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.md index b781418815..2629646f80 100644 --- a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md +++ b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.md @@ -1,6 +1,7 @@ # Agent Note: Sidebar document preview polish Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-sidebar-document-preview-polish.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.zh.md b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.zh.md rename to .agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.zh.md index 43016af0df..2cc93195b1 100644 --- a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.zh.md +++ b/.agents/notes/archived/feature/2026-09-11-sidebar-document-preview-polish.zh.md @@ -1,6 +1,7 @@ # Agent Note:侧边栏文档预览打磨 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-sidebar-document-preview-polish.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.i18n.yaml b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.i18n.yaml new file mode 100644 index 0000000000..9b1a412a55 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.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/archived/feature/2026-09-11-turn-changed-files-card.md +2026-09-11-turn-changed-files-card.md: 9a34d773cb0a0cc1e7d25d8d54c075093ac54f48 +2026-09-11-turn-changed-files-card.zh.md: ad97d0273243ca4eea079e2a8ebe3f849e528550 diff --git a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.md b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.md rename to .agents/notes/archived/feature/2026-09-11-turn-changed-files-card.md index 0c41783c6d..9a34d773cb 100644 --- a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.md +++ b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.md @@ -1,6 +1,7 @@ # Agent Note: Turn changed-files card Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-turn-changed-files-card.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.zh.md b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.zh.md rename to .agents/notes/archived/feature/2026-09-11-turn-changed-files-card.zh.md index f417b31b89..ad97d02732 100644 --- a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.zh.md +++ b/.agents/notes/archived/feature/2026-09-11-turn-changed-files-card.zh.md @@ -1,6 +1,7 @@ # Agent Note: 本轮改动文件卡片 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-turn-changed-files-card.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml new file mode 100644 index 0000000000..639b005e5a --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.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/archived/feature/2026-09-12-mcp-resources-and-instructions.md +2026-09-12-mcp-resources-and-instructions.md: 35d67988e73dc6bffef6bf6f01054405bcccc038 +2026-09-12-mcp-resources-and-instructions.zh.md: d9f0f2bd3f0728865638d18d0528bd82629df70b diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.md b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.md rename to .agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.md index a5eabd6170..35d67988e7 100644 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.md +++ b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.md @@ -1,6 +1,7 @@ # Agent Note: On-demand MCP resources and scoped server instructions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-mcp-resources-and-instructions.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.zh.md b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.zh.md rename to .agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.zh.md index 44890b2394..d9f0f2bd3f 100644 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.zh.md +++ b/.agents/notes/archived/feature/2026-09-12-mcp-resources-and-instructions.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按需 MCP 资源与作用域服务器指令 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-mcp-resources-and-instructions.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml new file mode 100644 index 0000000000..d1ca22630e --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.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/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.md +2026-09-12-mcp-sdk-protocol-negotiation.md: 5fadb93c14ea492cf8d614a434f6e68b384541fe +2026-09-12-mcp-sdk-protocol-negotiation.zh.md: 7e076ff66a272fa29de129ba08269feab107b9b3 diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.md b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.md rename to .agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.md index 74f027cead..5fadb93c14 100644 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.md +++ b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.md @@ -1,6 +1,7 @@ # Agent Note: MCP SDK protocol negotiation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-mcp-sdk-protocol-negotiation.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md rename to .agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md index bc8a9a41b8..7e076ff66a 100644 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md +++ b/.agents/notes/archived/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md @@ -1,6 +1,7 @@ # Agent Note: MCP SDK 协议协商 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-mcp-sdk-protocol-negotiation.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml new file mode 100644 index 0000000000..6dfd6b7356 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.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/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md +2026-09-13-macos-hidden-titlebar-vibrancy.md: acc3c5c410a7ebc2c7ebf7bc5fd20778ccddd0fc +2026-09-13-macos-hidden-titlebar-vibrancy.zh.md: b19070ee9741a5b3aa73a5a5fc62887104e471ad diff --git a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md rename to .agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md index 7e69fea3a4..acc3c5c410 100644 --- a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md +++ b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md @@ -1,6 +1,7 @@ # Agent Note: macOS hidden titlebar with vibrancy sidebar Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-13-macos-hidden-titlebar-vibrancy.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md rename to .agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md index fbba185740..b19070ee97 100644 --- a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md +++ b/.agents/notes/archived/feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md @@ -1,6 +1,7 @@ # Agent Note: macOS hidden titlebar with vibrancy sidebar Status: implemented +Archived: 2026-09-30 [English](2026-09-13-macos-hidden-titlebar-vibrancy.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml new file mode 100644 index 0000000000..d5e3b7d693 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.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/archived/feature/2026-09-13-mcp-resources-in-profiles.md +2026-09-13-mcp-resources-in-profiles.md: 90cdda6bda977160170f924d9e341b4b975f3a02 +2026-09-13-mcp-resources-in-profiles.zh.md: f54a68c5cc2171e6ec1764df45958754cd797c6f diff --git a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.md b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.md rename to .agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.md index 236cd28493..90cdda6bda 100644 --- a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.md +++ b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.md @@ -1,6 +1,7 @@ # Agent Note: MCP resource availability follows configured servers Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-13-mcp-resources-in-profiles.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.zh.md b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.zh.md rename to .agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.zh.md index d2e0fbc45c..f54a68c5cc 100644 --- a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.zh.md +++ b/.agents/notes/archived/feature/2026-09-13-mcp-resources-in-profiles.zh.md @@ -1,6 +1,7 @@ # Agent Note: MCP 资源可用性取决于已配置服务器 Status: implemented +Archived: 2026-09-30 [English](2026-09-13-mcp-resources-in-profiles.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.i18n.yaml b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.i18n.yaml new file mode 100644 index 0000000000..e8380e714d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.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/archived/feature/2026-09-14-composer-selection-keyboard.md +2026-09-14-composer-selection-keyboard.md: f8be46c1054989f02e76d4a6c0be8eb79d50607a +2026-09-14-composer-selection-keyboard.zh.md: 41cac14718e441f9d568624671eaadeeb72f9608 diff --git a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.md b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.md rename to .agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.md index 675e723910..f8be46c105 100644 --- a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.md +++ b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.md @@ -1,6 +1,7 @@ # Agent Note: Composer selection surfaces mirror the composer's Enter and Escape keys Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-14-composer-selection-keyboard.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.zh.md b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.zh.md rename to .agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.zh.md index 2b296a609c..41cac14718 100644 --- a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.zh.md +++ b/.agents/notes/archived/feature/2026-09-14-composer-selection-keyboard.zh.md @@ -1,6 +1,7 @@ # Agent Note: composer 的选择界面沿用 composer 的 Enter 与 Escape Status: implemented +Archived: 2026-09-30 [English](2026-09-14-composer-selection-keyboard.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.i18n.yaml b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.i18n.yaml new file mode 100644 index 0000000000..a9927dde9f --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.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/archived/feature/2026-09-14-docs-mermaid-viewer.md +2026-09-14-docs-mermaid-viewer.md: 4ddeebfa7ec5cc54ae68461d4ba8d14546135fea +2026-09-14-docs-mermaid-viewer.zh.md: ddebf672aae29e0281249ace16e98e22504d6a2f diff --git a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md rename to .agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.md index 5cb4bae84f..4ddeebfa7e 100644 --- a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md +++ b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.md @@ -1,6 +1,7 @@ # Agent Note: Documentation diagram and image viewer Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-14-docs-mermaid-viewer.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.zh.md b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.zh.md rename to .agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.zh.md index 665ab65e48..ddebf672aa 100644 --- a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.zh.md +++ b/.agents/notes/archived/feature/2026-09-14-docs-mermaid-viewer.zh.md @@ -1,6 +1,7 @@ # Agent Note: 文档站图表与图片查看器 Status: implemented +Archived: 2026-09-30 [English](2026-09-14-docs-mermaid-viewer.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.i18n.yaml new file mode 100644 index 0000000000..173ab7adac --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.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/archived/feature/2026-09-15-bundled-office-skills.md +2026-09-15-bundled-office-skills.md: 6cddeeee8065898eae0609f091335dbca8499b95 +2026-09-15-bundled-office-skills.zh.md: 7e81113bc913f58278c684500c3e3e1d719c88fc diff --git a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.md b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-bundled-office-skills.md rename to .agents/notes/archived/feature/2026-09-15-bundled-office-skills.md index 089ca0326a..6cddeeee80 100644 --- a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.md +++ b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.md @@ -1,6 +1,7 @@ # Agent Note: Bundled Office skills with structural verification Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-bundled-office-skills.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.zh.md b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-bundled-office-skills.zh.md rename to .agents/notes/archived/feature/2026-09-15-bundled-office-skills.zh.md index cd10aa863b..7e81113bc9 100644 --- a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-bundled-office-skills.zh.md @@ -1,6 +1,7 @@ # Agent Note: Bundled Office skills with structural verification Status: implemented +Archived: 2026-09-30 [English](2026-09-15-bundled-office-skills.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.i18n.yaml new file mode 100644 index 0000000000..710d75ac59 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.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/archived/feature/2026-09-15-changed-file-diff-preview.md +2026-09-15-changed-file-diff-preview.md: ba8614829185dc258d2e422b2961c0ffdb380c0c +2026-09-15-changed-file-diff-preview.zh.md: eb81190754808cebda0455323e1304f816b7acb2 diff --git a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.md b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.md rename to .agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.md index e265c1f452..ba86148291 100644 --- a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.md +++ b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.md @@ -1,6 +1,7 @@ # Agent Note: Turn review tab Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-changed-file-diff-preview.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.zh.md b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.zh.md rename to .agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.zh.md index 48571cd986..eb81190754 100644 --- a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-changed-file-diff-preview.zh.md @@ -1,6 +1,7 @@ # Agent Note: 轮次 review tab Status: implemented +Archived: 2026-09-30 [English](2026-09-15-changed-file-diff-preview.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.i18n.yaml new file mode 100644 index 0000000000..1892b9e83d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.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/archived/feature/2026-09-15-continuable-activation-capacity.md +2026-09-15-continuable-activation-capacity.md: fe53d659f1378e593822a34570422ac1f3d1c6a5 +2026-09-15-continuable-activation-capacity.zh.md: 61f1208a406e5f2fc142174b1fc713696ae7885d diff --git a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.md b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.md rename to .agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.md index a6403d10fd..fe53d659f1 100644 --- a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.md +++ b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.md @@ -1,6 +1,7 @@ # Agent Note: Shared continuable Activation capacity Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-continuable-activation-capacity.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.zh.md b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.zh.md rename to .agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.zh.md index 75cf6ca7a3..61f1208a40 100644 --- a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-continuable-activation-capacity.zh.md @@ -1,6 +1,7 @@ # Agent Note: 共享的可续接 Activation 容量 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-continuable-activation-capacity.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.i18n.yaml new file mode 100644 index 0000000000..505768c8f8 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.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/archived/feature/2026-09-15-docs-page-markdown-actions.md +2026-09-15-docs-page-markdown-actions.md: 8db5989de8860de07a9012f315deae4bbf069738 +2026-09-15-docs-page-markdown-actions.zh.md: 8cfe44aa2de5d044d999d453bd69ffe169a13c63 diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.md b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.md rename to .agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.md index ff4002fd69..8db5989de8 100644 --- a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.md +++ b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.md @@ -1,6 +1,7 @@ # Agent Note: Page Markdown actions in the documentation site Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-docs-page-markdown-actions.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.zh.md b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.zh.md rename to .agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.zh.md index 4e2db5e55f..8cfe44aa2d 100644 --- a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-docs-page-markdown-actions.zh.md @@ -1,6 +1,7 @@ # Agent Note: 文档站页面 Markdown 操作 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-docs-page-markdown-actions.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.i18n.yaml new file mode 100644 index 0000000000..1cba5fecff --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.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/archived/feature/2026-09-15-markdown-file-preview-links.md +2026-09-15-markdown-file-preview-links.md: fadd0cb8a38cfbd32bf0542293063894a7f289ab +2026-09-15-markdown-file-preview-links.zh.md: 5c028a6f14a51dfea9d4fc95d802f2863ce014e0 diff --git a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.md b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.md rename to .agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.md index d9d535afc5..fadd0cb8a3 100644 --- a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.md +++ b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.md @@ -1,6 +1,7 @@ # Agent Note: Markdown file preview links Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-markdown-file-preview-links.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.zh.md b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.zh.md rename to .agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.zh.md index 63c18e284b..5c028a6f14 100644 --- a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-markdown-file-preview-links.zh.md @@ -1,6 +1,7 @@ # Agent Note: Markdown 文件预览链接 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-markdown-file-preview-links.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.i18n.yaml b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.i18n.yaml new file mode 100644 index 0000000000..84daa67e0f --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.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/archived/feature/2026-09-15-profile-command-shorthand.md +2026-09-15-profile-command-shorthand.md: 9fe20dee4331f647d01a68e10c99af41fc5b39e0 +2026-09-15-profile-command-shorthand.zh.md: e3c26d76f06520287694113070a933eaef9a6acc diff --git a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.md b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.md similarity index 98% rename from .agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.md rename to .agents/notes/archived/feature/2026-09-15-profile-command-shorthand.md index 15bdf30b98..9fe20dee43 100644 --- a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.md +++ b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.md @@ -1,6 +1,7 @@ # Agent Note: Profile command shorthand Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-profile-command-shorthand.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.zh.md b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.zh.md similarity index 98% rename from .agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.zh.md rename to .agents/notes/archived/feature/2026-09-15-profile-command-shorthand.zh.md index 77ffd2324f..e3c26d76f0 100644 --- a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.zh.md +++ b/.agents/notes/archived/feature/2026-09-15-profile-command-shorthand.zh.md @@ -1,6 +1,7 @@ # Agent Note: Profile 命令简写 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-profile-command-shorthand.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.i18n.yaml b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.i18n.yaml new file mode 100644 index 0000000000..cd00d5b7b9 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.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/archived/feature/2026-09-16-desktop-onboarding.md +2026-09-16-desktop-onboarding.md: 86b36d1da9a7d290ba8cc3c42d217dbdff524122 +2026-09-16-desktop-onboarding.zh.md: 28d02b5ded239b918791d5ca7d9ac25aa7821bfc diff --git a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md rename to .agents/notes/archived/feature/2026-09-16-desktop-onboarding.md index e8b1b99279..86b36d1da9 100644 --- a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md +++ b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.md @@ -1,6 +1,7 @@ # Agent Note: Device-local Desktop onboarding Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-desktop-onboarding.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md rename to .agents/notes/archived/feature/2026-09-16-desktop-onboarding.zh.md index 8a23ff191a..28d02b5ded 100644 --- a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.zh.md +++ b/.agents/notes/archived/feature/2026-09-16-desktop-onboarding.zh.md @@ -1,6 +1,7 @@ # Agent Note: 设备本地 Desktop 引导 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-desktop-onboarding.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml new file mode 100644 index 0000000000..5c05d2c8a1 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.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/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.md +2026-09-16-open-in-default-app-for-sidebar-files.md: 4b8aaeb59c4a1186139ebc1a1150fd90e30c17dd +2026-09-16-open-in-default-app-for-sidebar-files.zh.md: 37f943a823a288309d75553b5a8a95fa727f6804 diff --git a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.md b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.md rename to .agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.md index fa7506be77..4b8aaeb59c 100644 --- a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.md +++ b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.md @@ -1,6 +1,7 @@ # Agent Note: Open Sidebar-previewed files in the default application Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-open-in-default-app-for-sidebar-files.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md rename to .agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md index af124182dc..37f943a823 100644 --- a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md +++ b/.agents/notes/archived/feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md @@ -1,6 +1,7 @@ # Agent Note:用默认应用打开 Sidebar 预览的文件 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-open-in-default-app-for-sidebar-files.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.i18n.yaml b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.i18n.yaml new file mode 100644 index 0000000000..c72685cbcc --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.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/archived/feature/2026-09-16-sandbox-same-mode.md +2026-09-16-sandbox-same-mode.md: b7f817029dee32851df222c147ea09083043ee7d +2026-09-16-sandbox-same-mode.zh.md: b0a67d8c04f091deaa992602b59d2fba26b1c2ae diff --git a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.md b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.md similarity index 98% rename from .agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.md rename to .agents/notes/archived/feature/2026-09-16-sandbox-same-mode.md index f1b7c3ae05..b7f817029d 100644 --- a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.md +++ b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.md @@ -1,6 +1,7 @@ # Agent Note: Repeated sandbox modes need no approval Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-sandbox-same-mode.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.zh.md b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.zh.md similarity index 98% rename from .agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.zh.md rename to .agents/notes/archived/feature/2026-09-16-sandbox-same-mode.zh.md index 0bb0441523..b0a67d8c04 100644 --- a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.zh.md +++ b/.agents/notes/archived/feature/2026-09-16-sandbox-same-mode.zh.md @@ -1,6 +1,7 @@ # Agent Note: 重复沙箱模式无需审批 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-sandbox-same-mode.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.i18n.yaml b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.i18n.yaml new file mode 100644 index 0000000000..ef2406ca3b --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.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/archived/feature/2026-09-16-unified-model-input-controls.md +2026-09-16-unified-model-input-controls.md: 4c188de6ab649eb6db627cf6811a7fa70e010b0e +2026-09-16-unified-model-input-controls.zh.md: 1e15c3fa62d53d5fff9c6a8b041078fd65c4e013 diff --git a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.md b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.md rename to .agents/notes/archived/feature/2026-09-16-unified-model-input-controls.md index 7391388092..4c188de6ab 100644 --- a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.md +++ b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.md @@ -1,6 +1,7 @@ # Agent Note: Unified model input controls Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-unified-model-input-controls.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.zh.md b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.zh.md rename to .agents/notes/archived/feature/2026-09-16-unified-model-input-controls.zh.md index 691ca5229c..1e15c3fa62 100644 --- a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.zh.md +++ b/.agents/notes/archived/feature/2026-09-16-unified-model-input-controls.zh.md @@ -1,6 +1,7 @@ # Agent Note:统一模型输入控件 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-unified-model-input-controls.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.i18n.yaml b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.i18n.yaml new file mode 100644 index 0000000000..2a96bced1d --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.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/archived/feature/2026-09-16-windows-desktop-titlebar.md +2026-09-16-windows-desktop-titlebar.md: 999deffe96440c2f3136004e576960a4bc46f430 +2026-09-16-windows-desktop-titlebar.zh.md: 8bc310bf4cd7bf6655904a73b798dd343e794930 diff --git a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.md b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.md rename to .agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.md index 17fd2aac30..999deffe96 100644 --- a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.md +++ b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.md @@ -1,6 +1,7 @@ # Agent Note: Windows desktop caption and application menus Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-windows-desktop-titlebar.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.zh.md b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.zh.md rename to .agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.zh.md index 05903d8696..8bc310bf4c 100644 --- a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.zh.md +++ b/.agents/notes/archived/feature/2026-09-16-windows-desktop-titlebar.zh.md @@ -1,6 +1,7 @@ # Agent Note: Windows 桌面顶栏与应用菜单 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-windows-desktop-titlebar.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.i18n.yaml b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.i18n.yaml new file mode 100644 index 0000000000..68dfce0f07 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.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/archived/feature/2026-09-17-developer-tools-settings.md +2026-09-17-developer-tools-settings.md: b17a79594e44b8fa749fc16f41eae2295f5502ce +2026-09-17-developer-tools-settings.zh.md: 20ea9c73f948e1738af29a3d422eb04d9e56028e diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md rename to .agents/notes/archived/feature/2026-09-17-developer-tools-settings.md index 61dc5976d3..b17a79594e 100644 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md +++ b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.md @@ -1,6 +1,7 @@ # Agent Note: Shared developer-tool settings Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-developer-tools-settings.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md rename to .agents/notes/archived/feature/2026-09-17-developer-tools-settings.zh.md index 8605a0ba0a..20ea9c73f9 100644 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.zh.md +++ b/.agents/notes/archived/feature/2026-09-17-developer-tools-settings.zh.md @@ -1,6 +1,7 @@ # Agent Note: 共享开发者工具设置 Status: implemented +Archived: 2026-09-30 [English](2026-09-17-developer-tools-settings.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.i18n.yaml b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.i18n.yaml new file mode 100644 index 0000000000..1f54f03ac4 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.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/archived/feature/2026-09-17-persistent-plan-cards.md +2026-09-17-persistent-plan-cards.md: 82067b34ff564c1aae624ae6093fe466d31924d6 +2026-09-17-persistent-plan-cards.zh.md: 669c7c86067e893dac79e25f45bb3dd120603049 diff --git a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md rename to .agents/notes/archived/feature/2026-09-17-persistent-plan-cards.md index 096c89c7ed..82067b34ff 100644 --- a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md +++ b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.md @@ -1,6 +1,7 @@ # Agent Note: Persistent plan cards and sidebar reading Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-17-persistent-plan-cards.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.zh.md b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.zh.md rename to .agents/notes/archived/feature/2026-09-17-persistent-plan-cards.zh.md index 95cce3fed8..669c7c8606 100644 --- a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.zh.md +++ b/.agents/notes/archived/feature/2026-09-17-persistent-plan-cards.zh.md @@ -1,6 +1,7 @@ # Agent Note: 常驻计划卡片与侧边栏阅读 Status: implemented +Archived: 2026-09-30 [English](2026-09-17-persistent-plan-cards.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.i18n.yaml b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.i18n.yaml new file mode 100644 index 0000000000..a7014d383c --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.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/archived/feature/2026-09-18-account-quota-top-up.md +2026-09-18-account-quota-top-up.md: cdd1d5053d9acfde8d8b97260a4359a6e84ecec4 +2026-09-18-account-quota-top-up.zh.md: 4b8f04bed97446b0d3ecbaa72dfc43bb2d48864c diff --git a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.md b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-18-account-quota-top-up.md rename to .agents/notes/archived/feature/2026-09-18-account-quota-top-up.md index f48b12be29..cdd1d5053d 100644 --- a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.md +++ b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.md @@ -1,6 +1,7 @@ # Agent Note: Account quota top-up action Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-account-quota-top-up.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.zh.md b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-18-account-quota-top-up.zh.md rename to .agents/notes/archived/feature/2026-09-18-account-quota-top-up.zh.md index 965e246ecb..4b8f04bed9 100644 --- a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.zh.md +++ b/.agents/notes/archived/feature/2026-09-18-account-quota-top-up.zh.md @@ -1,6 +1,7 @@ # Agent Note: 账号欠费充值操作 Status: implemented +Archived: 2026-09-30 [English](2026-09-18-account-quota-top-up.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml new file mode 100644 index 0000000000..f8b112547c --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.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/archived/feature/2026-09-18-session-pin-and-sidebar-archive.md +2026-09-18-session-pin-and-sidebar-archive.md: eb23e970c08b516a59950a2407f91462fa4ed554 +2026-09-18-session-pin-and-sidebar-archive.zh.md: 5f76fa767528909744b780bebf9b80027694f7f0 diff --git a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.md b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.md rename to .agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.md index ac580979b9..eb23e970c0 100644 --- a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.md +++ b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.md @@ -1,6 +1,7 @@ # Agent Note: Session pinning and the sidebar archived filter Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-18-session-pin-and-sidebar-archive.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md rename to .agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md index d4719fe75b..5f76fa7675 100644 --- a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md +++ b/.agents/notes/archived/feature/2026-09-18-session-pin-and-sidebar-archive.zh.md @@ -1,6 +1,7 @@ # Agent Note: Session pinning and the sidebar archived filter Status: implemented +Archived: 2026-09-30 [English](2026-09-18-session-pin-and-sidebar-archive.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-20-default-workspace.i18n.yaml b/.agents/notes/archived/feature/2026-09-20-default-workspace.i18n.yaml new file mode 100644 index 0000000000..18d8ec9071 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-20-default-workspace.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/archived/feature/2026-09-20-default-workspace.md +2026-09-20-default-workspace.md: f070167012a12ac7bf9bc496bff7977a48efe802 +2026-09-20-default-workspace.zh.md: 8b24874f14594cfe1318ece03ad41c40bd43daa9 diff --git a/.agents/notes/implemented/feature/2026-09-20-default-workspace.md b/.agents/notes/archived/feature/2026-09-20-default-workspace.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-default-workspace.md rename to .agents/notes/archived/feature/2026-09-20-default-workspace.md index b358cba3ad..f070167012 100644 --- a/.agents/notes/implemented/feature/2026-09-20-default-workspace.md +++ b/.agents/notes/archived/feature/2026-09-20-default-workspace.md @@ -1,6 +1,7 @@ # Agent Note: First-use default Workspace Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-20-default-workspace.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-20-default-workspace.zh.md b/.agents/notes/archived/feature/2026-09-20-default-workspace.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-default-workspace.zh.md rename to .agents/notes/archived/feature/2026-09-20-default-workspace.zh.md index e9a8bd6d57..8b24874f14 100644 --- a/.agents/notes/implemented/feature/2026-09-20-default-workspace.zh.md +++ b/.agents/notes/archived/feature/2026-09-20-default-workspace.zh.md @@ -1,6 +1,7 @@ # Agent Note: 首次使用默认 Workspace Status: implemented +Archived: 2026-09-30 [English](2026-09-20-default-workspace.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.i18n.yaml b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.i18n.yaml new file mode 100644 index 0000000000..a6000451b7 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.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/archived/feature/2026-09-20-developer-tools-default-on.md +2026-09-20-developer-tools-default-on.md: 7f5e31b037f11e7ae27a1ab4cc32da4b2caa9318 +2026-09-20-developer-tools-default-on.zh.md: 2c6ada115d8028ed6f7e73e849a2e894b2da13d4 diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md rename to .agents/notes/archived/feature/2026-09-20-developer-tools-default-on.md index 42c89faed1..7f5e31b037 100644 --- a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md +++ b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.md @@ -1,6 +1,7 @@ # Agent Note: Developer tools default on after preference resolution Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-20-developer-tools-default-on.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md rename to .agents/notes/archived/feature/2026-09-20-developer-tools-default-on.zh.md index a684844f5b..2c6ada115d 100644 --- a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.zh.md +++ b/.agents/notes/archived/feature/2026-09-20-developer-tools-default-on.zh.md @@ -1,6 +1,7 @@ # Agent Note: 偏好解析后默认开启开发者工具 Status: implemented +Archived: 2026-09-30 [English](2026-09-20-developer-tools-default-on.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.i18n.yaml b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.i18n.yaml new file mode 100644 index 0000000000..72de74ca30 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.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/archived/feature/2026-09-20-focus-ring-brand-primary.md +2026-09-20-focus-ring-brand-primary.md: 1e549b15eaa49822ee2161453d0c8f3218294bb6 +2026-09-20-focus-ring-brand-primary.zh.md: 2721962afcf73673b9758047d3b3d22f66ac010e diff --git a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.md b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.md rename to .agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.md index 8212eaf529..1e549b15ea 100644 --- a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.md +++ b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.md @@ -1,6 +1,7 @@ # Agent Note: One keyboard-focus colour for every control Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-20-focus-ring-brand-primary.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.zh.md b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.zh.md rename to .agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.zh.md index 479e2d0efd..2721962afc 100644 --- a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.zh.md +++ b/.agents/notes/archived/feature/2026-09-20-focus-ring-brand-primary.zh.md @@ -1,6 +1,7 @@ # Agent Note:所有控件共用一个键盘焦点颜色 Status: implemented +Archived: 2026-09-30 [English](2026-09-20-focus-ring-brand-primary.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml new file mode 100644 index 0000000000..91c3b3788a --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.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/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.md +2026-09-22-unbounded-completion-wakes-by-default.md: 31397aecd7b38b4b8bdfc82e23c957547adfa813 +2026-09-22-unbounded-completion-wakes-by-default.zh.md: 813c84d4dc03f64741d98eab6549bbba9468415b diff --git a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.md b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.md rename to .agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.md index 8760154054..31397aecd7 100644 --- a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.md +++ b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.md @@ -1,6 +1,7 @@ # Agent Note: Background job completion wakes are unbounded by default Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-unbounded-completion-wakes-by-default.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md rename to .agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md index e5560bc7ce..813c84d4dc 100644 --- a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md +++ b/.agents/notes/archived/feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md @@ -1,6 +1,7 @@ # Agent Note: Background job completion wakes are unbounded by default Status: implemented +Archived: 2026-09-30 [English](2026-09-22-unbounded-completion-wakes-by-default.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml new file mode 100644 index 0000000000..f6ce0fc9fe --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.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/archived/feature/2026-09-23-language-neutral-default-workspace-naming.md +2026-09-23-language-neutral-default-workspace-naming.md: 455567b6677ec529ad6a0520076cfe84fa98fc76 +2026-09-23-language-neutral-default-workspace-naming.zh.md: d4f0c2740a1ab8421755810079fccc1dba49f149 diff --git a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.md b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.md rename to .agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.md index 7d96f91aef..455567b667 100644 --- a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.md +++ b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.md @@ -1,6 +1,7 @@ # Agent Note: Language-neutral default Workspace naming Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-23-language-neutral-default-workspace-naming.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md rename to .agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md index 4095ccf7ac..d4f0c2740a 100644 --- a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md +++ b/.agents/notes/archived/feature/2026-09-23-language-neutral-default-workspace-naming.zh.md @@ -1,6 +1,7 @@ # Agent Note: 语言中立的默认工作区命名 Status: implemented +Archived: 2026-09-30 [English](2026-09-23-language-neutral-default-workspace-naming.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml new file mode 100644 index 0000000000..0a2a67ab65 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.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/archived/feature/2026-09-24-fixed-rate-floor-one-minute.md +2026-09-24-fixed-rate-floor-one-minute.md: 04784104d42e7b3d6653bfb9a038b117f648731b +2026-09-24-fixed-rate-floor-one-minute.zh.md: 6521e0f039e3095072952572d1eddf80902a3bb1 diff --git a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.md b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.md rename to .agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.md index 77fffc7813..04784104d4 100644 --- a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.md +++ b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.md @@ -1,6 +1,7 @@ # Agent Note: One-minute fixed-rate floor Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-24-fixed-rate-floor-one-minute.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md rename to .agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md index 6d103be09b..6521e0f039 100644 --- a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md +++ b/.agents/notes/archived/feature/2026-09-24-fixed-rate-floor-one-minute.zh.md @@ -1,6 +1,7 @@ # Agent Note: 固定速率下限改为 1 分钟 Status: implemented +Archived: 2026-09-30 [English](2026-09-24-fixed-rate-floor-one-minute.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.i18n.yaml b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.i18n.yaml new file mode 100644 index 0000000000..51ab84eb26 --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.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/archived/feature/2026-09-27-desktop-cli-runtime.md +2026-09-27-desktop-cli-runtime.md: a11612c4b013ba123b6950ec0b3affd225c42c2b +2026-09-27-desktop-cli-runtime.zh.md: c56a1e32984f25ac12cda56751c24df917ba61bf diff --git a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.md b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.md rename to .agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.md index 0336e7d612..a11612c4b0 100644 --- a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.md +++ b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.md @@ -1,6 +1,7 @@ # Agent Note: Desktop-installed CLI runtime Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-27-desktop-cli-runtime.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.zh.md b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.zh.md rename to .agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.zh.md index 73cfaa688d..c56a1e3298 100644 --- a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.zh.md +++ b/.agents/notes/archived/feature/2026-09-27-desktop-cli-runtime.zh.md @@ -1,6 +1,7 @@ # Agent Note: Desktop 安装的 CLI 运行时 Status: implemented +Archived: 2026-09-30 [English](2026-09-27-desktop-cli-runtime.md) | 中文 diff --git a/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.i18n.yaml b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.i18n.yaml new file mode 100644 index 0000000000..8076d698fd --- /dev/null +++ b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.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/archived/feature/2026-09-28-desktop-login-shell-environment.md +2026-09-28-desktop-login-shell-environment.md: 328194a254ccd0cd570d2cf832e31443c2cb2687 +2026-09-28-desktop-login-shell-environment.zh.md: d4007f2904c1ed358a0d76f5170d3f49c03dfd59 diff --git a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.md b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.md rename to .agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.md index 4ed2e6eb55..328194a254 100644 --- a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.md +++ b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.md @@ -1,6 +1,7 @@ # Agent Note: Desktop reads the login-shell environment once Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-28-desktop-login-shell-environment.zh.md) diff --git a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.zh.md b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.zh.md similarity index 99% rename from .agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.zh.md rename to .agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.zh.md index 4fd099720b..d4007f2904 100644 --- a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.zh.md +++ b/.agents/notes/archived/feature/2026-09-28-desktop-login-shell-environment.zh.md @@ -1,6 +1,7 @@ # Agent Note: Desktop 一次性读取登录 shell 环境 Status: implemented +Archived: 2026-09-30 [English](2026-09-28-desktop-login-shell-environment.md) | 中文 diff --git a/.agents/notes/archived/manifest.json b/.agents/notes/archived/manifest.json index 44178cc0ec..28ecf269b2 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -1,9 +1,15 @@ { "version": 1, "files": { + "architecture/2026-06-11-content-block-vocabulary.i18n.yaml": "sha256:825e605a80afd11cda526f5a54154b6bd6c65593ebce394664cc6fe26ac9d125", + "architecture/2026-06-11-content-block-vocabulary.md": "sha256:c0c64270bf31a7cae5db5aed79e754e3777c759b9daee7575353617108951b47", + "architecture/2026-06-11-content-block-vocabulary.zh.md": "sha256:0a20a3d61dfdfc597ccd56893eece8f16c2d8018ed58808427e712e9dd90ccf6", "architecture/2026-06-11-custom-schema-dsl.i18n.yaml": "sha256:f05d94c11762e506183044ddb1494a2b200ca16999ef3cef51c7b3a324eec945", "architecture/2026-06-11-custom-schema-dsl.md": "sha256:71286f2676f8b47d0bd56c6cc43cf8102946e6d195942860a5810b9c534d2b2b", "architecture/2026-06-11-custom-schema-dsl.zh.md": "sha256:999ff59565a4459184a644c4de6ef98c1bb1a174712e529f5c8417342abdd437", + "architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml": "sha256:158ce20babe0d42cfad4a558d098a401cd0169f4ec46458ff0542c6d75f720b1", + "architecture/2026-06-11-microkernel-event-taxonomy.md": "sha256:2931b1fa5eb21eaa3a73997fccd5c209b85df41666e215e7d01252c5d7248982", + "architecture/2026-06-11-microkernel-event-taxonomy.zh.md": "sha256:507817e05695a5ee68833ad41319f885d65bce231c3e2557fd4f891effa2cc9d", "architecture/2026-06-11-runtime-arg-validation.i18n.yaml": "sha256:2298b757376b5be2d15c9431f484344bbd9a2dbf5786ff27464ec079d6f1ff36", "architecture/2026-06-11-runtime-arg-validation.md": "sha256:19b8cc0fb42371aae27fb942a6d1ea31dacd59f46afb7eef1f2f2984487d5ec6", "architecture/2026-06-11-runtime-arg-validation.zh.md": "sha256:690b7a109a719bd416bf41ffe35029c8c9101df3c52f9896b9adf7d2f0b8d194", @@ -13,6 +19,9 @@ "architecture/2026-06-11-tool-schemas-in-prompt-assembly.i18n.yaml": "sha256:37230a2f5b9dbe160b4f36b6906065637846261cbef380c3e2b777cf0d8e9013", "architecture/2026-06-11-tool-schemas-in-prompt-assembly.md": "sha256:6f7b7f15f53f857ccb8477b3fdf65bf2de3a6f96acd93049c03ce4aff1629538", "architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md": "sha256:fd5ddfc53f8a1c4afa86599c858c2ad27858166e676754ee6e2ce0881f55cef7", + "architecture/2026-06-14-session-persistence.i18n.yaml": "sha256:f3cdf40bd320d7f829e18cb4513208824cec0d1deb56f84bb955c4d3d56ac3da", + "architecture/2026-06-14-session-persistence.md": "sha256:325a5f797132ace0ccbd0ae9902346eed702ae794b11b3a248d6af8b92ccb9be", + "architecture/2026-06-14-session-persistence.zh.md": "sha256:32ddf8974828e89e5517646b7ee7f9d0ff5af60dea214885941e403662722a9f", "architecture/2026-06-15-turn-enclosure-invariant.i18n.yaml": "sha256:7eb471a53b7bef104c57e9343b80d672763f062ecb086b01b318b65b488d3c02", "architecture/2026-06-15-turn-enclosure-invariant.md": "sha256:afefa3a268c84f26cf5461e08933245352a9e63cff688d3c398c8064a4ac6e85", "architecture/2026-06-15-turn-enclosure-invariant.zh.md": "sha256:c54fdac980abc922cdc252a8fef59e4bdd7567316c7fbb6f7dbc035e470d95fa", @@ -31,9 +40,18 @@ "architecture/2026-06-20-extract-example-app-packages.i18n.yaml": "sha256:d99b612cc1051c86d883d74737c72e921735e7a28e0b5e6351d3870c664bdcc4", "architecture/2026-06-20-extract-example-app-packages.md": "sha256:9c7aca3a1e9a1ccc3729961663bc649b90076e671cae23e3db8203305983ccce", "architecture/2026-06-20-extract-example-app-packages.zh.md": "sha256:19bd50232d9f25d35aa3f9dc72d9af0df457dd0eaca8b982d5aa625e5b95bcff", + "architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml": "sha256:42bd649f7c0f494edc59b8e51ea77be9e43a607a35bdda83a57a4852349ab2c6", + "architecture/2026-06-20-generic-long-running-tool-runtime.md": "sha256:9638a533c9ffcde6da6b36e9cc042ded07b46f1b12120130c797cb736e152fb7", + "architecture/2026-06-20-generic-long-running-tool-runtime.zh.md": "sha256:c14f72853aa447a454ba23e658f4e24d355f9b9e09ed5024f5783894969f6273", "architecture/2026-06-20-package-hierarchy.i18n.yaml": "sha256:02b9c383c0fc4e9d6cdd2d28b70b73d02ed913861834db8e2a3111ad82e05dee", "architecture/2026-06-20-package-hierarchy.md": "sha256:a0c6b3022e27f6106996b7aaf5d087b3e3f66f24f7b5fd7b7c12f172ab5dc2f4", "architecture/2026-06-20-package-hierarchy.zh.md": "sha256:40eec478cc5ff8ae76996e567d1f58309ed71b3bf0068ee06b088deae283512a", + "architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml": "sha256:cadd048affca937aab200aacc09e2c61d4789e178811c4beaf999a5d9eb2f858", + "architecture/2026-06-21-mandatory-app-attribution-headers.md": "sha256:1fad4ae82c6c74a436319c2423d2b973c8895a6aa9e17f5113ea5e1f0fec052a", + "architecture/2026-06-21-mandatory-app-attribution-headers.zh.md": "sha256:d5e39dd4188054b4291cf35014a7c375f889af0fb3a7a82eb27f4505eb0b98e4", + "architecture/2026-06-26-file-context-as-event-gate.i18n.yaml": "sha256:3bf4f900b65487beaf0ca0a355484caae9ab9249d70f8db7a24dd024e034b641", + "architecture/2026-06-26-file-context-as-event-gate.md": "sha256:66e447d3066bb8fd06fff450ed168fff017dba666e929312de058747288c067b", + "architecture/2026-06-26-file-context-as-event-gate.zh.md": "sha256:253d172be02ce33c33e07de2ff8ea5b9a703c2759818c49f3502d3627d97e9f5", "architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.i18n.yaml": "sha256:32605633787a782818314707569f45e9c90d2ca21b707c46567d845fb641bb11", "architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md": "sha256:7433ba1518acb9f2fd0645ddca46c376b8b1b1ba7828ff2f7af9555c576aac9a", "architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md": "sha256:7f4090e5764f9545671ce86be5eac7cae30a86aaf95e089aea16b1427c87b6d8", @@ -52,9 +70,21 @@ "architecture/2026-07-05-windows-fs-permissions.i18n.yaml": "sha256:7e61ee9bbd9de4bf3285a6f250d9625bd062e5fb90279dbffd64c820f1f7fe6b", "architecture/2026-07-05-windows-fs-permissions.md": "sha256:03734da511eae3b0736f7cad73d9da76ae2f69f9d5ed09089b0121ccb135a861", "architecture/2026-07-05-windows-fs-permissions.zh.md": "sha256:454848057ea905fe76c88d17264e71e71fb685f08f82088de6976878372865c3", + "architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml": "sha256:453f51c4e3bc387232b051281fc2b5f15b99f632bd780aa268c82cd5b514446a", + "architecture/2026-07-05-windows-jsonl-durable-publish.md": "sha256:f52c9e66a23eceec03fc943db77bb8fb086595edaf8779dedf40f203afc77673", + "architecture/2026-07-05-windows-jsonl-durable-publish.zh.md": "sha256:53ee9c2d6f40bc590188512d09157a469f0dd0b291efd21825097b4d955e07f7", "architecture/2026-07-06-tool-result-retention-library.i18n.yaml": "sha256:3d658500765ece3eb305fed4775e36a9846d922bcd0bc8e9c057e8746dde910e", "architecture/2026-07-06-tool-result-retention-library.md": "sha256:2d2877fd52d32f027a6974c3e887282cd5f8b2050bf2533adf12a2b9ad4e4cb5", "architecture/2026-07-06-tool-result-retention-library.zh.md": "sha256:b1c3f980ddf6a299902d5dd7f211cd384b89c2ea26afc0d642c167b07d965dc8", + "architecture/2026-07-08-agent-scope-contexts.i18n.yaml": "sha256:80e24129f8ef91cf0e104e21a920233ebb5d27294fce2c2d7617e581d06c822a", + "architecture/2026-07-08-agent-scope-contexts.md": "sha256:92afb2b68312b2ae455783e862fee81d9636a713d56e6006938545948eeafbb7", + "architecture/2026-07-08-agent-scope-contexts.zh.md": "sha256:c8e4e10d8f6e29a7c98b5fbb726ebc8181d361581437b4281ba5a253a3321902", + "architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml": "sha256:315185feb1cd69b568e4ef82592d44bb41ea3a64efa74844905f706b7044b862", + "architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md": "sha256:a93accd5f0de88ef8d2b3f37f900d20fe73cc3d6e822312f130cf0f7af4a0abf", + "architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md": "sha256:04547a382062ae78d32fc6ebdcca231d39af2c9fce232c5d2bc9fbf11e940d1d", + "architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml": "sha256:32012e5705d6f44e054d7415a9265bcdcc279d6e260e2b04b0d8b2dd143f8bb7", + "architecture/2026-07-12-agent-scope-runtime-design.md": "sha256:ba835250a9ff8c3b258e937cbb5337d49fe4bcf55b8f63b6fb90b15d2ea25613", + "architecture/2026-07-12-agent-scope-runtime-design.zh.md": "sha256:4193323cfdbf30215f87a2d90826e44b8e4798ef956fe5c72947e5dac5ca558a", "architecture/2026-07-12-scoped-layers-store.i18n.yaml": "sha256:c2a1251102215d60bf06e2de7f3f497d162f52210485524df1f512462afc0f03", "architecture/2026-07-12-scoped-layers-store.md": "sha256:0db4c96247ccd15f37edd2d00fea1bd11df6aa9d505ec710e8a606797deedf11", "architecture/2026-07-12-scoped-layers-store.zh.md": "sha256:dd20c33a397898901931579b7a01339d1997327f64dc561e87f3bafcd3ec8e9b", @@ -67,18 +97,36 @@ "architecture/2026-07-15-replay-token-meter-service.i18n.yaml": "sha256:cb2356e0771b0c06da8c3cf494d1e36f09b4034d015cf213bd2413202c34a6fb", "architecture/2026-07-15-replay-token-meter-service.md": "sha256:dab85a043b5a71b49aed3b0cba810e95bedfb4f318033b50664337fe4bb69f25", "architecture/2026-07-15-replay-token-meter-service.zh.md": "sha256:725f5773e52b2f580aa217be43c26acfada21570d266d407a995ed9a9f7b99c0", + "architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml": "sha256:f2149444168373309cca7bd808017509847b50e55724b31ae0a0ac24b2112d9e", + "architecture/2026-07-16-explicit-turn-cancellation.md": "sha256:f1df651db30ec4f21d1276db0e616223604c7c4c60de4686c19863252f18be41", + "architecture/2026-07-16-explicit-turn-cancellation.zh.md": "sha256:21ec502b7ec801bb30d5a5fa2bb0053c2dce2757e0ea3cd1400c12e130b2c686", "architecture/2026-07-17-local-spill-startup-cleanup.i18n.yaml": "sha256:8aac3691a971964dfb7df662c11930119832f8f4a207906b49c2dc7824fdf987", "architecture/2026-07-17-local-spill-startup-cleanup.md": "sha256:5abf50a2bec99734477a7c885c0c44d57ad12f3c59621ea1502927193364917d", "architecture/2026-07-17-local-spill-startup-cleanup.zh.md": "sha256:17f574516921abd0c84915822d08699fdbaadb00447c15e6b4c1410783b61478", "architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml": "sha256:855477999c84236430dc9308e16797eb21658a67a72b8537315c6964ff0c0c0a", "architecture/2026-07-19-gui-layering-and-rpc-protocol.md": "sha256:3517f37e98e74865dced37d5e1559d443e8fa827031c8335e99a1e910586e9ac", "architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md": "sha256:8181386d957fa6d6b3eb9b05d29adb10804b5a926853425415d368cc7fceaefa", + "architecture/2026-07-19-gui-web-client-architecture.i18n.yaml": "sha256:b5fa3e83a617f695d3dc80898b58fc815fce13210937606d32cf0a2c644cb312", + "architecture/2026-07-19-gui-web-client-architecture.md": "sha256:82badb52e2c0a9ad38ab49f68411f48a732a63f1c8226460ceb78e9487cf4c0a", + "architecture/2026-07-19-gui-web-client-architecture.zh.md": "sha256:26897bdcbc1c2f9909c46faf9bfa973a19822848477b0dcaa846fa008835545e", + "architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml": "sha256:a232db6670ea51521112ad54bcc0cc4ad43c5b11dfa0df2963a6f04e79434c7f", + "architecture/2026-07-19-package-invariant-runtime-contracts.md": "sha256:8f4a84b310d5ad8398cc272201c8c0ed259496050fad8cb4d3634ee5bbb1dc1e", + "architecture/2026-07-19-package-invariant-runtime-contracts.zh.md": "sha256:6b43c6437523e2715cdb8d75fd286a1eebd0d17f5a35f6c9afddb3f15cdfb945", "architecture/2026-07-19-package-owned-invariant-service.i18n.yaml": "sha256:9ea696b84c3af215eb3640e8acfe75a73b0f16109cbdffd4ce18cbf616221231", "architecture/2026-07-19-package-owned-invariant-service.md": "sha256:514f93f2261fecab9eb7efa8613340b1f01b729b411ac4adce903c50935fa95e", "architecture/2026-07-19-package-owned-invariant-service.zh.md": "sha256:cbcc5550b68bc6699ac7411c46c5da67db3fc4fe8330873f1d190ef8141f1280", + "architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml": "sha256:d2f52ec7fa1713d66912d4e5e478330a4c62f18f88feda1f4db908ef32ed0738", + "architecture/2026-07-19-zstandard-jsonl-session-logs.md": "sha256:a2546e7222fc1aca0f54bd7aedcecdd141cde931f3598f2133291f49f5c6bb08", + "architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md": "sha256:1b4115638c3bfc23bc5079da4d48b6e5243fedc9e723b7538f835a06f2392aed", + "architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml": "sha256:d5ec751780608ac698785f5ce4f4dbe60fbc899e00eda6983fbdc76571c5230a", + "architecture/2026-07-20-canonical-tool-output-contract.md": "sha256:4d2f2f72c6fed24474644fed1c05ff9cdd8ec52dad972704a11575d414c5e393", + "architecture/2026-07-20-canonical-tool-output-contract.zh.md": "sha256:ef64daa6f359680110a0eab22c0b5db9a13fb8a6b3396090f3cc46052812ce6d", "architecture/2026-07-20-todo-event-ownership.i18n.yaml": "sha256:d8c7537ec3110834eef3f5ec934842acc23315e6cd235ed92baea967244c78b4", "architecture/2026-07-20-todo-event-ownership.md": "sha256:4c92c6dd9a5879437224f57616d1e106bf5a6afc1dd6229b033a01a51a035470", "architecture/2026-07-20-todo-event-ownership.zh.md": "sha256:9218336958487145fbb7bdf2a47259f2d43646db3233800364c234fb061be2e4", + "architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml": "sha256:e4b8a1816b6f4c04a4c711ede05bf92b7ba3221ddb9dbbc5095d4123daa16129", + "architecture/2026-07-20-unified-json-value-schema-dsl.md": "sha256:320fc081b5d01f91649e72292e314af6dd5f90ae2fee84b00eca2232c6957916", + "architecture/2026-07-20-unified-json-value-schema-dsl.zh.md": "sha256:f65c484aa3d25e680fd83bf095282a2686749f2b1d970d10b814e76ed7252f79", "architecture/2026-07-22-tui-interactive-extension-service.i18n.yaml": "sha256:1b4822af5c8d642b73e3a0b04fb0a1dea9f50d0147046fbef53f5e49c030fb91", "architecture/2026-07-22-tui-interactive-extension-service.md": "sha256:ca6b2774f4821e66f7c8397f20fcd34926728ded853fa48cbe451db7a8d2f883", "architecture/2026-07-22-tui-interactive-extension-service.zh.md": "sha256:5b060c7626ee796c27108be7467a5e4be0677d7525d383336e7ec31ddce5c303", @@ -97,6 +145,15 @@ "architecture/2026-07-24-dsh-commander-argument-adapter.i18n.yaml": "sha256:cf99eda0e58b49630d5f95792459d7095666fafbef61f614165d5cdd031b7118", "architecture/2026-07-24-dsh-commander-argument-adapter.md": "sha256:705654c8a43bcd199f72c21a77d24ca8bfa02447aff1c7f3e4e820be61dcd562", "architecture/2026-07-24-dsh-commander-argument-adapter.zh.md": "sha256:3844f02d7659d18caf5d39e1131ed775c789cbf92dc44b4a446c7d6468aa5d00", + "architecture/2026-07-24-project-session-directories.i18n.yaml": "sha256:06849b9065103b0cd1d1e33bdd83c1326825845d175a80c4f188c61545651890", + "architecture/2026-07-24-project-session-directories.md": "sha256:ee09140a83a8c3b1f1fb6c575c4df1af495e254fe28fec04a96d98c2c1032d1f", + "architecture/2026-07-24-project-session-directories.zh.md": "sha256:e5aea80277d74ba8b30126e1c92bb0b4365550dfb8a477dad94d0f90e9f9db6a", + "architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml": "sha256:029d75a97d1a59f2bb114e49cf7c2151e48f6d7b32270883916815ac5fc68c51", + "architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md": "sha256:e445cd8debebd85235d13b9b1a5c4b3b2514416562945066c2e47caed20bf9d2", + "architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md": "sha256:dab93a6feed2e863e4ce489b75fdc0f223eac0f8a82625f8f7488c22c6ae0592", + "architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml": "sha256:f215fa8dad60a322e0076120e880383b8f814ff57ae7c8eb256062e509bf1bea", + "architecture/2026-07-25-web-client-session-scope-and-provide-channel.md": "sha256:b79bb0a5d0cc800ae523b16539e84b75292417f38a892e9864c5a68eebd41bc9", + "architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md": "sha256:cb2f0c57bcf68b6e21f39a1dee9551ba124d7d4ce12f53fbdbae3ff98c694e1c", "architecture/2026-07-25-web-command-surfaces-and-assembly.i18n.yaml": "sha256:726092004674738dfaf8a6cace26d9d39d567b9710a147fbc56b0bfa6bd780d0", "architecture/2026-07-25-web-command-surfaces-and-assembly.md": "sha256:8415f54cc7b2c1ed06d55863c8c2f9800c32caaf10f62e7ad0c9593c49e2f7ec", "architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md": "sha256:acd5de934fcf43d87334feded440d4109f1fab6735e6480cabc847a27f8a75ad", @@ -148,12 +205,18 @@ "architecture/2026-07-30-config-plane-boundaries.i18n.yaml": "sha256:af908b31293de6da141ed4e7a61422174ededda06c99d01f900fe96509910f39", "architecture/2026-07-30-config-plane-boundaries.md": "sha256:7d628e144fd9d872f0f7bcf7c5e29e0a75069aba7aef4d3be8f9ac8091495701", "architecture/2026-07-30-config-plane-boundaries.zh.md": "sha256:8cc0c48a73aae63201e1225980c675c72b9277ea6056acf104e8073468b8bef9", + "architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml": "sha256:ff22f65940837b7577488d367976cdd5ebc60558449f01c0e85ae716f5b8a4b6", + "architecture/2026-07-30-session-end-seed-log-boundary.md": "sha256:6cda510a72167eb1f8f2df2320753cc5797c80cff2babb05c408344df152d15f", + "architecture/2026-07-30-session-end-seed-log-boundary.zh.md": "sha256:1a093e1cbe48591101896e59aafef62aaac61a441521435f396e90beb049aacb", "architecture/2026-07-30-settings-write-path-integrity.i18n.yaml": "sha256:729e669c522107d5029db8a61a239f225587130e769f0a806a60a045e834983f", "architecture/2026-07-30-settings-write-path-integrity.md": "sha256:648c2302ccd60835d94ee94a3516d534541a37bfd4d3a2902509995e3429c43f", "architecture/2026-07-30-settings-write-path-integrity.zh.md": "sha256:c2de9383504ea00ef940df5f6174606893b4665bd196bb7c5f1db19e515ee038", "architecture/2026-07-30-web-config-plane.i18n.yaml": "sha256:81f973a05eed7ed6845b37e5c3e28db6141abd7d133c27fef9bb0e0ac0eb4486", "architecture/2026-07-30-web-config-plane.md": "sha256:80ec41d4cc43c9f6096d0269700f6ea2d26f06aee3a4b12afeddce1e51916944", "architecture/2026-07-30-web-config-plane.zh.md": "sha256:b7821469eb9fe37fe1bc178be4ac803fdb19d55c9d36e7c69048a51b9bfe52f6", + "architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml": "sha256:7c485a9979a13ad0bf8d45c60d3e5f5c80611ac8bc84c699a8a2bfdbd56d9236", + "architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md": "sha256:9b94e0284e7a8cc6475d8bd2463afbd28814653110bb6c7dcc4f12c7844bda90", + "architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md": "sha256:91c645127dd7306d65358bb07f7824da52360dd3ad0c336e1d3fad5879a8f1bb", "architecture/2026-07-31-code-runtime-portable-identifier-seam.i18n.yaml": "sha256:e243a62d3b468b6a65c80b284fe934fad09099a42d33c1dca47f435427a851b7", "architecture/2026-07-31-code-runtime-portable-identifier-seam.md": "sha256:d7a050d3a654b7b0445b8dbe6e87331bbd91ce0bf6ad23c6d83ee32ada85fc2f", "architecture/2026-07-31-code-runtime-portable-identifier-seam.zh.md": "sha256:286c53e0000bd0a109b67bf3ce5f190197fa674f103075461168768d78ea6770", @@ -172,6 +235,9 @@ "architecture/2026-08-04-declaring-a-provider-from-the-models-page.i18n.yaml": "sha256:0d8bce49724dddba6fb8aa5d4beedadebce36f38838111c59a18ee418c9b61c9", "architecture/2026-08-04-declaring-a-provider-from-the-models-page.md": "sha256:99c4faf487b177a8231803ed68551c93f2f8f4902ad02ebdd6cd09d273c79603", "architecture/2026-08-04-declaring-a-provider-from-the-models-page.zh.md": "sha256:1443455cf5da2963612285b8852d8663def596f86e79066456a48e933c66825f", + "architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml": "sha256:92c633b150fbd623c76fd3a7aac43cc9a565f69ce49945f39e0e246b4eb7f483", + "architecture/2026-08-04-draft-provider-endpoint-interrogation.md": "sha256:232e922d12306089b62966b6a428b0858fdde5bcab9fa3eced213e17266a0986", + "architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md": "sha256:c4132fddc1dd64fa507e1f453fe1e4dbf98dd9785b1743bf00f52dc7a974daaa", "architecture/2026-08-04-websocket-downlink-carrier.i18n.yaml": "sha256:b9d742d068a0e36df2f3030f6a04a3638f3461f7e10b50ed0cd6bd5e85de5019", "architecture/2026-08-04-websocket-downlink-carrier.md": "sha256:b9be27a4cda8abd410c6e8b728c571f96b4891eb003c5200e9a0ffbbc9145b42", "architecture/2026-08-04-websocket-downlink-carrier.zh.md": "sha256:118b71b33710a7a3d28375c48b42c1ec19993ca7e13f286dd6ea64f934456f46", @@ -226,6 +292,9 @@ "architecture/2026-08-10-product-subagent-providers-in-shared-host.i18n.yaml": "sha256:09fa68844f08eca349a49cb82a3b949a4a57856334679fb43cb95a12243669e3", "architecture/2026-08-10-product-subagent-providers-in-shared-host.md": "sha256:d7918f9ef4164035f29bb9f3f7851efdc42f6f7a5f12dc77af0c6f865c4580e0", "architecture/2026-08-10-product-subagent-providers-in-shared-host.zh.md": "sha256:f9ec3b3e6fb3173c4eed84b09956b923cf7a7a8a00fc495e36e306712795a97d", + "architecture/2026-08-10-remote-event-delivery.i18n.yaml": "sha256:ca96befabb8c38dfa81aa703f713fec0e3c965dc728aadcc28f9c2a49fb24c7f", + "architecture/2026-08-10-remote-event-delivery.md": "sha256:3f48c972e9a54baaa0eb35f58e1f2a0979986b5a063f839fe817aa975b9c2edf", + "architecture/2026-08-10-remote-event-delivery.zh.md": "sha256:287267edc264aefef7aec3696b0777940cb3e00e2f4f971fde9294c2bf160a8c", "architecture/2026-08-10-unary-apiproxy-remote-migration.i18n.yaml": "sha256:53a0193dca8f4164b59210df550a3c17436c28c7a78c75d9e3c2c9d3dd0ff8d2", "architecture/2026-08-10-unary-apiproxy-remote-migration.md": "sha256:a95a1a85baefecd74dec1641692a6990c0b935869210409f01c9c1757876dd0f", "architecture/2026-08-10-unary-apiproxy-remote-migration.zh.md": "sha256:8118a06af8672aa87ffb3733a36cbf7d142b2c6fcfedf5db3e545c0ca7ace52e", @@ -268,6 +337,9 @@ "architecture/2026-08-18-client-build-environment.i18n.yaml": "sha256:a5c953aa89b9084c76ff6fad64d93e5cb53814787c088875ef53b3c3730997e0", "architecture/2026-08-18-client-build-environment.md": "sha256:a962d301170e54d1f82b68868f9717c2aaaf110bf397ca64366cb67216292fa0", "architecture/2026-08-18-client-build-environment.zh.md": "sha256:08982b9ae233596e04bc485d5b67176ece0767f9671cd15aa928f8774c4d86dc", + "architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml": "sha256:17904bf8f4c8596ecf94aa2512e9a3c2d04e048d5fcf0eb64ca4712e6098ce97", + "architecture/2026-08-18-experimental-agent-teams-packages.md": "sha256:b627a894d44aa1ecff955e643d4cabce9e681a2915ee6a80dac1e1ee9edc3339", + "architecture/2026-08-18-experimental-agent-teams-packages.zh.md": "sha256:1c498e1e55e490562983cbef9389d679da70fdea3e0323e807ea5522fe505a0c", "architecture/2026-08-18-sqlite-physical-chunk-row-compression.i18n.yaml": "sha256:42bce930799cb511e9fb245dec5e26efd78bdab4c9b75f7393e37b40fbee4d10", "architecture/2026-08-18-sqlite-physical-chunk-row-compression.md": "sha256:4fe241f1b272278d9f3ca1a4431971220e1fa54411df043826ef6f59225bf949", "architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md": "sha256:73178c9ec5abf571680d8facfb145cbadc1efbb2e67e3f039747c2f9cf4bb730", @@ -346,6 +418,9 @@ "architecture/2026-08-27-process-table-snapshots.i18n.yaml": "sha256:d43fc5cd1d2439a2a9d20b1ab85b78c2007e635770b99fa8e8f36ca2476075ef", "architecture/2026-08-27-process-table-snapshots.md": "sha256:583a5aa1ee3949ace886237b151e94092cd7abd1f6d0c775bd7a2d132f8fa1e5", "architecture/2026-08-27-process-table-snapshots.zh.md": "sha256:0dfe98b3220a790bf2d67b5b791d7fc288e9960f813afe55275215ea4f930c3d", + "architecture/2026-08-28-subprocess-native-containment.i18n.yaml": "sha256:b418169a40a2954ea0ec6af73b93e0b862f8a0824397f62487ff641df9ba49f5", + "architecture/2026-08-28-subprocess-native-containment.md": "sha256:97808213de8f4d675957beb5459c40ed2950afc05712de0022af7f237640c1ef", + "architecture/2026-08-28-subprocess-native-containment.zh.md": "sha256:b0b38785e954682b8b61d1c6eb9b02550a14512953eec633317542b78b6f2198", "architecture/2026-08-29-plugin-inventory-agent-preset-scopes.i18n.yaml": "sha256:0ead49a8471f0f2db56c114b89825d7c3a913db24d3c69e9b89aac717cf4338d", "architecture/2026-08-29-plugin-inventory-agent-preset-scopes.md": "sha256:cf111d4863a2b64a7ecfd1abd12768755e311c9a229fa6f7adc1885298c806af", "architecture/2026-08-29-plugin-inventory-agent-preset-scopes.zh.md": "sha256:6688736349c677c7698f4417e795167b84cc5e368e247424080b746fdaf9fcbf", @@ -358,24 +433,126 @@ "architecture/2026-08-31-viewport-activated-syntax-highlighting.i18n.yaml": "sha256:f45afe590c67c8874480b8a2e1739b918c69c709d53e5e1ab673797eb6619bd2", "architecture/2026-08-31-viewport-activated-syntax-highlighting.md": "sha256:df1c5883c13611104fd41e4df920513724a1daee3f5cf06db8d1da97627bdec5", "architecture/2026-08-31-viewport-activated-syntax-highlighting.zh.md": "sha256:da3fd4b4d22b20f204e4418b8249f719fd9d8860f5e148af8f960d8983b379b7", + "architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml": "sha256:0eb984fb9c4ac6923c5a62d38c7ec3c4d5764210209c4a059dedd86088d241f9", + "architecture/2026-09-01-jobs-absorb-activity-record.md": "sha256:c18d816a566c61c1f06cf6f050fa856ea84d62b7dac247b404e97ae90b7543bc", + "architecture/2026-09-01-jobs-absorb-activity-record.zh.md": "sha256:754e63f4be3df22679e88df4abf4b4202a983663e9bcfd0fd9534ba9eca2f700", "architecture/2026-09-01-streamed-tool-call-identity.i18n.yaml": "sha256:5193bf7429a0300f743ea7e6087c3175a7d28c2c468ca1cfd1f77702b68b6399", "architecture/2026-09-01-streamed-tool-call-identity.md": "sha256:303433b95c22571bf6a801da604b42a4c7231f5f6d07daeb518354f0d7019141", "architecture/2026-09-01-streamed-tool-call-identity.zh.md": "sha256:53728d3ee06b29d6023e28024fb3ab6fa4a7473ae5e55368cd19690719d120f3", "architecture/2026-09-02-durable-image-offload.i18n.yaml": "sha256:d06155597e7df4ca4c15a2902ab664d69cc2fde1e7963e61543d20a49930d2b9", "architecture/2026-09-02-durable-image-offload.md": "sha256:02b7becba3dd73a4e627b85705e8bd5dd55978cb041f4d3a0fd1420a76e4de92", "architecture/2026-09-02-durable-image-offload.zh.md": "sha256:a0464b8272086d663ee698efd0b2edd6224d40770d7a8fd88a6d8bfe030b2a1d", + "architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml": "sha256:ef49c74369799cb814196c9228db389c49a4726941d4b15b87b491becb0370b2", + "architecture/2026-09-02-projcache-cross-version-read-compat.md": "sha256:bddc84e2ece19ee3c46f46548f8dad82adfdd9562e5c006f5a49e701bdd356d2", + "architecture/2026-09-02-projcache-cross-version-read-compat.zh.md": "sha256:e330bb34c498b5622c991dc0c7ad221e539d5d4a3b360252f65758b9f9f11975", "architecture/2026-09-02-protocol-specific-model-listing-discovery.i18n.yaml": "sha256:ba8a62a9fa3263de4162709cb1aaf81f6ad4feee5011d7a5d8b24eb92ad81d30", "architecture/2026-09-02-protocol-specific-model-listing-discovery.md": "sha256:9db9c47559c17f0e2931b9e646f1d2d17b590df4d5699d4e779ed6d7f65fc371", "architecture/2026-09-02-protocol-specific-model-listing-discovery.zh.md": "sha256:9a266590d49093a097bcdb5d09865757cdd07625cfaaddce1303046d580551f1", + "architecture/2026-09-05-package-manifest-types.i18n.yaml": "sha256:ba2b549ec3078cf5f76f312f43e84519036ba183901ac98b2acfc60e7ca76c30", + "architecture/2026-09-05-package-manifest-types.md": "sha256:9e04158267478a3d59ca135f68a58f267ed3336f485828253f504cb2952b5ee4", + "architecture/2026-09-05-package-manifest-types.zh.md": "sha256:ca6ae444dcb4f30a3f255d0a1f6eac3d214dd8c41c8e62a09bfe6a90b5c82800", + "architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml": "sha256:b58fc8771ee412fa59eedcb1fa69790290a8357f62e84f750af6e76702958801", + "architecture/2026-09-05-sidebar-tab-types-and-navigation.md": "sha256:81f6d5c3608f253e831218e3ae189aa8a716b98d5f386108f596f42a0aca28bb", + "architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md": "sha256:55f321a2936592979814a703eab2c6d240e8466f6c14a71da5138190320a4549", + "architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml": "sha256:a8b30138ffbf2727fc679f3b2e45882aad0caaca8bde317ccf97bca3efefbd96", + "architecture/2026-09-06-embedded-stream-record-readers.md": "sha256:a097578d3bff23df4f054224b1ab76472a20a31ade0c0034d630fc59c8295d87", + "architecture/2026-09-06-embedded-stream-record-readers.zh.md": "sha256:ce3bbbb0a987f4b014b9369c15c73b8087299d89d254439ac0e22e017701ff11", + "architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml": "sha256:e49ccc6e8aa8bc3d5717674fe5da0446286d828ae7d351221aca0c4c46a2b194", + "architecture/2026-09-07-sidebar-responsive-tab-info.md": "sha256:ff96e8a1da552eee4a7ae4482bf6dca23e60b6a7d9453cd393920d600a1667e2", + "architecture/2026-09-07-sidebar-responsive-tab-info.zh.md": "sha256:9302d848bdb978804eb47e56687dff5b6507b2bd1175bead73a750deddecd914", + "architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml": "sha256:acdd98ead2beaf40b1a450e70304393b957861b399020bab2a87980b6a45ad64", + "architecture/2026-09-07-workspace-files-dual-face-package.md": "sha256:b1fe56b80b2fd7250df5545bd86aaac3ec5c54c5a56263d8ca0ada37eacdf5b5", + "architecture/2026-09-07-workspace-files-dual-face-package.zh.md": "sha256:b21c72f4203b770f22f4e9f8777b86391ae2a6a4191b0d43efeb5b02758391c3", + "architecture/2026-09-08-global-main-panels.i18n.yaml": "sha256:6e7644b9802afae7a3fb0ca1a888bb00b0b9ff59479362d7b31a0302935652d4", + "architecture/2026-09-08-global-main-panels.md": "sha256:6a2603ff00f14502a733f1fcbfe6904478edf4e969cddf7b51db476e913d3c53", + "architecture/2026-09-08-global-main-panels.zh.md": "sha256:2cd95305765e7f233f204640162bd5fbb5676fc13fbd78b26941e81bd9ed513c", + "architecture/2026-09-09-desktop-build-release-validation.i18n.yaml": "sha256:351e5a2d53633d3cb48fd2209d1e51cbcb9722e8143e1fdf951387e4cf8e4711", + "architecture/2026-09-09-desktop-build-release-validation.md": "sha256:70d0fd717ffa31234bf33ec64e5b894e49721160a98c5e443d3d4306832da19d", + "architecture/2026-09-09-desktop-build-release-validation.zh.md": "sha256:c752ae306dc0ce6fb5783adc876902a43e522c902f1e3fdb29404da769f59ab5", + "architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml": "sha256:9acec631d186cb60f70f12755caadb8c6d0833a7a69405887aad39cf86b4deda", + "architecture/2026-09-09-desktop-immediate-window-and-direct-start.md": "sha256:e0258ef3830a0c0a8f900e3dea749af06c1ce3e3fda87527916e64def85f75cc", + "architecture/2026-09-09-desktop-immediate-window-and-direct-start.zh.md": "sha256:a9c92eb3e5d52f7ec7ea61c22ec4ec54cb48776305a92fe23e2ec6b46e93a485", "architecture/2026-09-09-desktop-in-place-profile.i18n.yaml": "sha256:08543070a32503373d38ca98b7ce96ee60c972302534030c5ba380f19e04fa00", "architecture/2026-09-09-desktop-in-place-profile.md": "sha256:82bdcad48207c5aecbc9695717aaaa4c8cca90c82ca58884bfa653b60f192290", "architecture/2026-09-09-desktop-in-place-profile.zh.md": "sha256:f105636c9160c64b12bd1822c8ec81ee744ba27c0b0ab6eeed870561ccd81ce7", + "architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml": "sha256:cb1a4bbb7633fb928dc98cdeb4c981d73b7120339c9c5826f0bf85867d634ba5", + "architecture/2026-09-09-plugin-management-in-the-web-sidebar.md": "sha256:0967da54312b4906f4bc44a1345e85283f04cedacddba7451e4ae86562c5a9b6", + "architecture/2026-09-09-plugin-management-in-the-web-sidebar.zh.md": "sha256:51591d15716af4ae7d5bb08497f2a89cd7c0d77b0d545322be00dfeab4f0c9e7", + "architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml": "sha256:bf6e5ab88c13788a750c0f290483f424d3c5ea975026792b7d255fc58d628754", + "architecture/2026-09-10-command-identities-and-composer-file-action.md": "sha256:f87b65f4099ebbe57731d5cbc631337ebb24ad1492858e3a111034287b1073df", + "architecture/2026-09-10-command-identities-and-composer-file-action.zh.md": "sha256:daa24d061b06392e0c8de69a4955f1e262461fc371f77c6a9451985c769e9628", "architecture/2026-09-10-local-office-preview.i18n.yaml": "sha256:a43a2370c7434293ef98d1901a29b7b4b03f400a108c119f3ec5f2f7ca8ca3f8", "architecture/2026-09-10-local-office-preview.md": "sha256:754cfb4dde9f1ee70e495095f12fc9e3c43af4a959250bb3a59ae44fd32fd763", "architecture/2026-09-10-local-office-preview.zh.md": "sha256:d8fb9a65f437b585f2dfa2b4aa091634eeabf571e98e881cf7344b7574704e53", + "architecture/2026-09-10-windows-native-installer-pages.i18n.yaml": "sha256:efb930d0a0e7288cfee1d6af0a24ddb8565acede343a3dd5f6be65d35f25d7ed", + "architecture/2026-09-10-windows-native-installer-pages.md": "sha256:8dbb36a9eab988e917b73ab63d974541177c432ebaeb9de901fa7261748c10c7", + "architecture/2026-09-10-windows-native-installer-pages.zh.md": "sha256:d24787380c44d52a511d05dcbcf7c8ef083d789b7effb9d484e5635b214fea24", + "architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml": "sha256:df4f00e58479e886bfb2f0db50e75d3b6d9c46990bba9bf838c3fa903e90d6ee", + "architecture/2026-09-11-desktop-electron-node-runtime.md": "sha256:c4097ff6dac6ce8e9295dfe9c268d924b6e7daf1d68031ae27d6f284205e2bb6", + "architecture/2026-09-11-desktop-electron-node-runtime.zh.md": "sha256:b8bf6b70a468b80e1952aa73c909a2e8c29ed16242bd583c7cb2707bac460988", + "architecture/2026-09-11-node-office-kit.i18n.yaml": "sha256:eaa46c9c097fd8ff307f51d7e108d58d46d4daa4e5152e7498893647ba18f1d8", + "architecture/2026-09-11-node-office-kit.md": "sha256:251575a2c550afc3f5d570249ada07902ca19cededbf59d3ffc5c96341b2d6ef", + "architecture/2026-09-11-node-office-kit.zh.md": "sha256:a62fcfe872ddd8952c0115c646d5114451316e4eb656bfb8e78c500e52b75174", "architecture/2026-09-11-wasm-preview-font-and-image-budgets.i18n.yaml": "sha256:af027c7dae53a181e4d73fd2309f69e9ae3c48df0d3b1dfc53cae274aa60d69e", "architecture/2026-09-11-wasm-preview-font-and-image-budgets.md": "sha256:44dd52839cdb67644c0dbcbc4beb6f36802628c4a8d17a0aad11db1f9be649ed", "architecture/2026-09-11-wasm-preview-font-and-image-budgets.zh.md": "sha256:8285432ee1676be8b79a05953c4b6ac4a7ca5d8e701173b3a2b533608b584d0a", + "architecture/2026-09-11-windows-directory-installation.i18n.yaml": "sha256:7c8b0b01e129b5c0bcabc0c9ea3d7e2b0927d7e39311aab30e7553395d03a2e7", + "architecture/2026-09-11-windows-directory-installation.md": "sha256:d09207a97c6a0161b46da8fa685f5acda3e9ed161279bf8cb6c211cec89b9a95", + "architecture/2026-09-11-windows-directory-installation.zh.md": "sha256:aee9c09908b5a435ae4708bdf718c90357db801f08efcaa274dcc6a087fa17a3", + "architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml": "sha256:8f0d4573f49c72f3bf420f51d84eaa549b832c9122a04cfeb53c02330c1f322d", + "architecture/2026-09-12-ptc-runtime-vocabulary.md": "sha256:4723d115dc3e997701ae0b3707475c40f9ab8ee5a71d605f494f0aac40c90149", + "architecture/2026-09-12-ptc-runtime-vocabulary.zh.md": "sha256:8441c37449949f11daf11baf4d868f9a2d053fc877ba59ea84581b23869aab25", + "architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml": "sha256:1d237f9f04bee161319c9af336e720464c1084693e6d9a9b46ac68dd3108065e", + "architecture/2026-09-14-sidebar-layout-provider-recovery.md": "sha256:fb98065f958ed717e8d3fbd358a22b8875e2e703de0346fab25f4189b96e6585", + "architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md": "sha256:22a9973412a25ffd040354657032d67ffa03d6111604c24951380408d5a19a1f", + "architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml": "sha256:fbd7cb2bcfc9bcb1b7b2037d779ebab5309e39eeb2b5b4ff2489cd3604ec7ebc", + "architecture/2026-09-15-narrow-pi-ai-runtime-imports.md": "sha256:faf98cde580ece829227d4b005faaef02a9fa157726814921ec51df9da6d6621", + "architecture/2026-09-15-narrow-pi-ai-runtime-imports.zh.md": "sha256:92707eb32cde044cc3b54278845c25d2fc5cf07835d90e33f86dbe954bc74ed8", + "architecture/2026-09-15-platform-office-engines.i18n.yaml": "sha256:c5dd1ea80f993b500113f2a8e8fa4ef99d68f7fb262e93ef7f8973fea12d8c77", + "architecture/2026-09-15-platform-office-engines.md": "sha256:2bb35c30d1eb6b6f6e71b6aeef02b2eb9a7e0fff33c1c4b187aa42f9470e9b04", + "architecture/2026-09-15-platform-office-engines.zh.md": "sha256:7ef82a66baf85ac87dd79c4d66f7ad829362377ada41c00fde9b05d607c57716", + "architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml": "sha256:d706d8e7d11b08cb2a518973a0e8f25aa654a750034ab5d584d52c4c62a8d9ea", + "architecture/2026-09-16-desktop-cos-upload-transport.md": "sha256:29770e4de3c1cefe8dfbd472e7b10b3ebe9e05ec394b014924f48a2ae7a52a89", + "architecture/2026-09-16-desktop-cos-upload-transport.zh.md": "sha256:11f1444578683a0c2f0335d3b82cb2272ad039049c89660584096b44534dfc41", + "architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml": "sha256:8d5538e44ec1a88b2f05bbc2ec7fac74bc71c836a2bd1799ceaceafd2c655ab4", + "architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md": "sha256:3c10f1e887338d7f12f1e101b61361d7da90f4955cc6e95db71882cc219deda1", + "architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md": "sha256:b1bcba5e4d8bc7af70d92c4d87f2388a5590eecd20e13c2e59af5f9974431426", + "architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml": "sha256:baf5fb499c2aa925d9049e3e3eef1ca990098b77b7c9237bd860deba38968175", + "architecture/2026-09-17-plugin-detail-page-extension-slots.md": "sha256:a27d98d16640f2337b4edbeea948e4c5ddc2c065f2c1a7a7f314bc8670b465ba", + "architecture/2026-09-17-plugin-detail-page-extension-slots.zh.md": "sha256:b7b39bbc1b3a3f9a4ac14020972182ab95802d22d7e1223680f867204106d05c", + "architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml": "sha256:3ed45fff7e2b518d0f192e27fa5553b1e7d18048afce9b922a1d8250f6fa51cb", + "architecture/2026-09-17-session-row-menu-actions-slot.md": "sha256:a250133ec9902f49f11dccf7c6c378fedacc38f0a4183406bd8f71569512dc76", + "architecture/2026-09-17-session-row-menu-actions-slot.zh.md": "sha256:2b18d5289c24f08c67a69d080097eacd25b15e5da89883a4a58c6963cfdba45d", + "architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml": "sha256:14b51a7d4eaedbbbf8d2f09c6d714284ffb368263b21fc2307ddd3ab7c440dfb", + "architecture/2026-09-17-settings-pages-as-companion-packages.md": "sha256:b46d1a896e495b1fb4f1d218c046fb6c2590aa7d8b5bf56453f4f07bb6bf17da", + "architecture/2026-09-17-settings-pages-as-companion-packages.zh.md": "sha256:2a52a11b8b49478fb72e4bee84e5a62db1b4725f6eb56ec80e2f7b88e3464c56", + "architecture/2026-09-17-shared-office-runtime.i18n.yaml": "sha256:0cea3ec197d6fe5b24c86ca70136a57254f26a3e82b02557ae271c135fde576f", + "architecture/2026-09-17-shared-office-runtime.md": "sha256:7f89995d3238034377b5e706062feb6282ab45642466da9275443aadade19bf4", + "architecture/2026-09-17-shared-office-runtime.zh.md": "sha256:5ef380c6d58e3ad41b3c603d6088d77c4fbed2e3006b61d048349e17b31ad43b", + "architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml": "sha256:9c720962cc80a23d9db4077ec9e415df8158b4915a76fd79ede22454d349af91", + "architecture/2026-09-17-web-feature-routes-and-route-gate.md": "sha256:4b6bcbd93f73b119684659ea307bf75ab9b1e2d9a600ba307bde8012a142a23e", + "architecture/2026-09-17-web-feature-routes-and-route-gate.zh.md": "sha256:e5bab09bd408697b58770d844d43e7ddf641a4e7a2abda3bd48e3639ea9b1727", + "architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml": "sha256:e4fddcb94e6332b307b6ab27195bafbf2d68ec066116c6e8c18b5842eafbb9f4", + "architecture/2026-09-18-agent-teams-single-bundle.md": "sha256:dcbc728cadfd5dcd6d477e001bac00042d560642b0d34f20da9dc7668108a004", + "architecture/2026-09-18-agent-teams-single-bundle.zh.md": "sha256:3c0d47cbe51e56e7b5d7443ea8c4bb202ff587e93ebe2f2e1edea1c91d18c5d3", + "architecture/2026-09-18-chat-navigation-performance.i18n.yaml": "sha256:58f7dc51a84d81911edf5beee432ee59b252371053ca94ddfa842198fcab00a9", + "architecture/2026-09-18-chat-navigation-performance.md": "sha256:ca2485d167fe400ef739dda0feeb28b60ff58e423fdd9c165c6fed52d7eb8dd0", + "architecture/2026-09-18-chat-navigation-performance.zh.md": "sha256:bd72c094e89b9f8841c1f2e9b781a83e2531271600795e015e261d6c0e2d7bc6", + "architecture/2026-09-18-localized-package-metadata.i18n.yaml": "sha256:0e38b93d1f2bbecf8878ef5335860358b8c588e652d50a40070ca3bc8ca73d8b", + "architecture/2026-09-18-localized-package-metadata.md": "sha256:b33d455682ce53528a7fd889ecde65e32a38f2e35f3f542dd3d0a2d7318bd4bf", + "architecture/2026-09-18-localized-package-metadata.zh.md": "sha256:76e348dc9a74dedff9a297be4399181399810505c45b50c76e8532092bd35ff1", + "architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml": "sha256:40082f24ce4597a6baca242be7aa5ef18cdd89114c74f94bc8c1909d90b50aaa", + "architecture/2026-09-18-profile-plugin-host-runtime-instances.md": "sha256:98aa3cb2be43ce32e16b172cf68073e83eca0df3a17bae2ab41d2623e6db6c33", + "architecture/2026-09-18-profile-plugin-host-runtime-instances.zh.md": "sha256:6e21186397d71f4ae1a741ae7b439df03856c6f8a503527ebabf4ea034402510", + "architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml": "sha256:88644a4c0a9e6bab3587c7140158fc4b30f7fe569a61bc219d82d1bece2d332a", + "architecture/2026-09-19-lossless-projection-checkpoint-json.md": "sha256:110794f7699261bedeafbf427e23c03f497dcfb4796e6fd2f3f3b704fdcb87de", + "architecture/2026-09-19-lossless-projection-checkpoint-json.zh.md": "sha256:42d8f294c85a245bf5dd8080963e752505229490e21fc5ca37a47eb13c818928", + "architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml": "sha256:4fb6ae4ada814d3c24bd46bab4e7bc9b0a7ee9cf1eceba643cc7726c0bda8869", + "architecture/2026-09-21-creator-skills-progressive-disclosure.md": "sha256:541565316120da3e395b0a45efd47b191f6d2ca6e2ea395ecdc203e7bda6f57c", + "architecture/2026-09-21-creator-skills-progressive-disclosure.zh.md": "sha256:02d43a9c82b2c25b1b5f9bd14f522756366f87580cc25da02b8a61bbfbc31d66", + "architecture/2026-09-21-live-config-inspect-provider.i18n.yaml": "sha256:23c7264731b942d7a498cd0af29b0a9aeeeb4c8039c84534df54867a0b4bcc5a", + "architecture/2026-09-21-live-config-inspect-provider.md": "sha256:cbef83e1e5cec2b87f0e8e29add18d6c5987ec542e2c4de41c0ce3ee4aad30b0", + "architecture/2026-09-21-live-config-inspect-provider.zh.md": "sha256:4a7dc1a42e111a50fa0d683ec3d7649d28b150dc9c193714972d60d817890b6d", "architecture/2026-09-24-web-default-schedule-composition.i18n.yaml": "sha256:8c3d5c614924d0e81f361a299427ac697aefc6189c41eb3f4151f5c42e371ca0", "architecture/2026-09-24-web-default-schedule-composition.md": "sha256:3a139593ade13f95cbb67094c1eec66cb0a2c14bcc154512799803b2a7d22ab4", "architecture/2026-09-24-web-default-schedule-composition.zh.md": "sha256:59908f7987058742d1226dc8a801d03c265c84d51172249ba51716b91f084f0c", @@ -391,6 +568,9 @@ "bug-fix/2026-07-20-error-cause-chain-diagnostics.i18n.yaml": "sha256:5a7634080e9921149565827e1cc3f5bea39b04ea171b57ba45c259a201f204be", "bug-fix/2026-07-20-error-cause-chain-diagnostics.md": "sha256:82d231e5632d0e582e26b54a9f65d0ce05afb7d6bb9bfc3d324737fd43c45c1b", "bug-fix/2026-07-20-error-cause-chain-diagnostics.zh.md": "sha256:b8c0669a5d0475a5ddffa644a17bd1009f525cf69edb36b7fade90014b97daca", + "bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml": "sha256:4705374c528e7d97a302e8b7e77d4c8efa41608d1b44e34e84aea50ea0345029", + "bug-fix/2026-07-20-jsonl-storage-identity.md": "sha256:831f2a52b455ec0b1dd7b98306a597fcbf5eee7313a1974e8b4407e6cc3bebf7", + "bug-fix/2026-07-20-jsonl-storage-identity.zh.md": "sha256:444f89cac97beb8718b80d93ed6ea20e942f078e3c4adfb2cc35497836bdbe54", "bug-fix/2026-07-21-compaction-summary-prefix-cache-reuse.i18n.yaml": "sha256:3093deb3286c628da23f738a6169044da63e04def5167319db25db5346eb3d17", "bug-fix/2026-07-21-compaction-summary-prefix-cache-reuse.md": "sha256:eb981a14d178470e9861df0d9f14ed0575d452485e5dd11b80f56578375ee4ef", "bug-fix/2026-07-21-compaction-summary-prefix-cache-reuse.zh.md": "sha256:e3105954814e83a52ef483b4b24ac260dd5c13d79861ebd218f8b7aa0e54b2bb", @@ -505,6 +685,9 @@ "bug-fix/2026-07-31-english-compaction-checkpoints.i18n.yaml": "sha256:d2727c1a67a615afbc4b21ad2a26936a298302a9cef9e666c17dfed4d31391a6", "bug-fix/2026-07-31-english-compaction-checkpoints.md": "sha256:54922365cebb173ea5bb0691a162e4d9743a146ca1eab8355ae6379cc6f5aa5c", "bug-fix/2026-07-31-english-compaction-checkpoints.zh.md": "sha256:d0ec253b79e3d698df2bbc37967bb49d35b4e553ce8f427cb502eea3bbc0e997", + "bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml": "sha256:1284f19db30c19a29b571444df59ae4f964bdd905b9d69e4b0f22b58f5f7d553", + "bug-fix/2026-07-31-fail-loud-releases-the-terminal.md": "sha256:a9bf988f05eda6bbe78a5818405603889be86d1d2870c8678b185ab3ad8d9c6e", + "bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md": "sha256:b986d5b30dcec8a141a44c438317fd5543b999beeb444b20e5619f01bd702648", "bug-fix/2026-07-31-fork-anchor-floors-to-event-seq.i18n.yaml": "sha256:8fc15495a7450ff24740919496764aec978aed0c3014c59d5ff8033a01f5bca6", "bug-fix/2026-07-31-fork-anchor-floors-to-event-seq.md": "sha256:e36d3c6af393d02e872ba9e0f99ab64dd64246bf6e91ad9e5ae8c9619b206840", "bug-fix/2026-07-31-fork-anchor-floors-to-event-seq.zh.md": "sha256:790b8bb755b9b5919eb200036e45fd133987399299b95d6a1bf20ace30a676da", @@ -535,6 +718,9 @@ "bug-fix/2026-08-02-todo-first-composer-context-order.i18n.yaml": "sha256:130fad68ea68a0d2dbef704288f4fd4b9ba6bae6ef7e376021631b62d249956a", "bug-fix/2026-08-02-todo-first-composer-context-order.md": "sha256:0d8c29d7e67990f85d07ebde9c44a3247188e9910c47376a772e8ae951fd7bbe", "bug-fix/2026-08-02-todo-first-composer-context-order.zh.md": "sha256:ac1d1b9bdd18aedd847d59624716e5ffd4aa0fc966ed93d69e8899ccdcedca88", + "bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml": "sha256:71e3f139cc6452f8fb93e84fc2c64723c6d5725d2f7058d4d06dd5d51ee9026e", + "bug-fix/2026-08-03-cli-signal-shutdown-escalation.md": "sha256:153f62218bbaeb0da3e1316e01f17701c5acb3792b30008608dc443bd50e8c6f", + "bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md": "sha256:df37e1d47e911771380ab1f76aa111f9e3a0efb465ec7cdcebd02af64c598129", "bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.i18n.yaml": "sha256:80114779ec809c23251be5acea561ff3c5959336471d3241414c59dfaadbf746", "bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md": "sha256:4e4c4e0e512c1bd14bb30ab5749f2bb7b8ef8986781b157f1a6dea0641ed2c91", "bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.zh.md": "sha256:b0f7708f29d078e511d03dbffc6ad5e16de1fad4e9ed36faf6fdeee08ddfc32d", @@ -559,6 +745,12 @@ "bug-fix/2026-08-06-api-key-format-validation.i18n.yaml": "sha256:83c9f4aca0f65020f0a456733512b77fb324d694d6c32b2f92efb8ef0542872b", "bug-fix/2026-08-06-api-key-format-validation.md": "sha256:0a18b8dccc5a67f8b6dbbc8f596e73cc95b797dc6280fb48455c2a0cb9229b76", "bug-fix/2026-08-06-api-key-format-validation.zh.md": "sha256:fa8ddc9b35d2f1b25407881797f4b407d906528a0c0ab3a99f11a16ea8e7512f", + "bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml": "sha256:5d1807702d5839d44ed2245f0ce388bc82e6ef9767b787c5b071923cbae8a493", + "bug-fix/2026-08-06-bwrap-private-pid-namespace.md": "sha256:b9995a92db182f6548a9a1b39751c6cf44a12e92eddc9198c886589893966954", + "bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md": "sha256:4a08284342485e073bbb3ca156dbf72013200abd60a1252676f20910f60abebc", + "bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml": "sha256:cf85345483cc151a478f1dfcaa306ad57cfd5a9cf2d169f8c3724d0c2acedaec", + "bug-fix/2026-08-06-host-backed-web-preferences.md": "sha256:b93dc4d6b02c073acef18a50f0f9b9a7df49c00186f55366223a9408384c4a83", + "bug-fix/2026-08-06-host-backed-web-preferences.zh.md": "sha256:43e37874dafc17dbf0c3204af9a7cb98717e7f3ef868eb201872edd93daf2f47", "bug-fix/2026-08-06-list-agents-residency-vocabulary.i18n.yaml": "sha256:816a582c4a71304b0499c18aadea56a180d876dd81c4b22f6c1798e2e37d21f3", "bug-fix/2026-08-06-list-agents-residency-vocabulary.md": "sha256:f8ccd05c93bf7b5ee389c50c0ec248c29b04bbed9cca0354354032bb9099cc33", "bug-fix/2026-08-06-list-agents-residency-vocabulary.zh.md": "sha256:5dbdc24587823be669a727104780dbbe59ef9a510dd89893b79a93d0ba8289ed", @@ -577,12 +769,21 @@ "bug-fix/2026-08-06-token-surface-unpriced-replace-compatibility.i18n.yaml": "sha256:dd13d2717a8555e8dbfd6c33938c1eea714ac57ed50e3db792381237b7d676dd", "bug-fix/2026-08-06-token-surface-unpriced-replace-compatibility.md": "sha256:b1b034bfd8dcf4d1181cf5848a41ecc3c66e9b55b576d5973bfee749af0b242a", "bug-fix/2026-08-06-token-surface-unpriced-replace-compatibility.zh.md": "sha256:df7994d75351aea633642bca2fde5e41d78ff6e090637c253cc5755d664f8bf3", + "bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml": "sha256:2dad33ed7c71b0b0fd7877405cd344a24eac735f015f0c285dabcc5045fcc33e", + "bug-fix/2026-08-07-cancel-convergence-wake-latch.md": "sha256:d1ba28894d50d52df9cf328f6ec2d79561b5aaa55bce6ad32a99cd4964320559", + "bug-fix/2026-08-07-cancel-convergence-wake-latch.zh.md": "sha256:0d0f93666ed2f8337eeb0f826eee5fff965d6c693828a51872502470702a57da", + "bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml": "sha256:c5215ff52bfcf1dbcd2d7e71aa8300b3a46437ce8c4afda9e7ce550de522ad96", + "bug-fix/2026-08-07-ptc-executor-collapse.md": "sha256:4456fc2f18a77b925fb3e8f9157f3a25d5b2b6b0a85638e970ddd4f86619ed17", + "bug-fix/2026-08-07-ptc-executor-collapse.zh.md": "sha256:74276aee21e43598d2374a4ea5402fdadcd7fda6b7e38fdc56c9fc6eb968e214", "bug-fix/2026-08-09-broken-preset-roster-rows.i18n.yaml": "sha256:945ca2ed436331d7b4f69a6eda26d7a0eff9ae03512842384f65ad433ad5f4d4", "bug-fix/2026-08-09-broken-preset-roster-rows.md": "sha256:4abc2668d328de3b7194fa76441acc79619cfa0653f0f985b1649f7b1f116320", "bug-fix/2026-08-09-broken-preset-roster-rows.zh.md": "sha256:7918e051c4a18bfef9905cf5cbacb9f761314673771fc9ac975a6a67a6856228", "bug-fix/2026-08-09-filesystem-absence-observation.i18n.yaml": "sha256:b0e7440c6527bdb4bec5f69073858f755f7a7ac1f6cfcb4d3bb8bc7f40cbdb3d", "bug-fix/2026-08-09-filesystem-absence-observation.md": "sha256:524b6d965587dc2a4c2c27440a20b189714f9112714df0984e6753553af061e6", "bug-fix/2026-08-09-filesystem-absence-observation.zh.md": "sha256:51a52dd1534c2db42ca07cd292e57eb46e18b1d1a8f458e6adc3efab926bf5ff", + "bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml": "sha256:9fcc8d4ef4f39fabfb70f060c81e2d56829f5d93c6a6aa7ee43e52209d84267d", + "bug-fix/2026-08-10-child-agents-join-their-parent-preset.md": "sha256:fe0dfb8a2a5c79bf31fcca116f137436632907e541a94749c9ea1bcff2a7a6a5", + "bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md": "sha256:cd51eda599e4cb13068cab8ebe09b7c1848f5bdc5ebd8564a04a5c585e26f3ce", "bug-fix/2026-08-10-minimal-preset-owns-rl-composition.i18n.yaml": "sha256:97df20c38d7794cdf3d0d8dbad23e72417953ab6444cf2ad4cf40e91d1c36b84", "bug-fix/2026-08-10-minimal-preset-owns-rl-composition.md": "sha256:5c35a0b892d47f637aec94dde869d2b3f1c22b238ae7c6dd9d3a59104d48827e", "bug-fix/2026-08-10-minimal-preset-owns-rl-composition.zh.md": "sha256:b69dd72ab2ae5bf3118e3bd14dd7cf351ce00a1bdc5f119c25c8b56435503bc1", @@ -661,6 +862,12 @@ "bug-fix/2026-08-15-persistent-bash-keeps-controlled-prompt.i18n.yaml": "sha256:9fb062cdd41c23ca2137de9954f962acea2c0286356ce8bdb6f9275840db41c6", "bug-fix/2026-08-15-persistent-bash-keeps-controlled-prompt.md": "sha256:099fd51594b18c256477908b834dfeb0adefae37951aad5d578f88194d3621a7", "bug-fix/2026-08-15-persistent-bash-keeps-controlled-prompt.zh.md": "sha256:d5e207828e95097bead324ca0df7b2c1d815e0afaf52d8d46e46e626206e7b27", + "bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml": "sha256:43a686f1013da9585c33e3557ea83b15bb26666edd924b1f4c0788f82f84831d", + "bug-fix/2026-08-17-durable-web-queue-recovery.md": "sha256:596875462a997146660a874a95a5407146efbf0f2a3e4575988aa307ec4e4e04", + "bug-fix/2026-08-17-durable-web-queue-recovery.zh.md": "sha256:b83fc7cde0f17dc8ae448bc21c8c664da8a5cfcc42c2c250ca9ab438695d54c2", + "bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml": "sha256:c983bc6a18e0f458264bfd495a6857d33128222a542c6e4b58769625468de466", + "bug-fix/2026-08-17-subagent-message-settlement-ordering.md": "sha256:e0c22753c581dae827ee8e7b9c5da5b45c095ac9ae2a05ec1820ee0bdf624289", + "bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md": "sha256:c9e495149e97972becef27cddb58ddfcde38840d1ee052f91633fde04adedcf4", "bug-fix/2026-08-18-rail-search-outside-click-self-dismissal.i18n.yaml": "sha256:23e9e6028f95c1dd6543875e80debf49b11b780b0fa1bdb5988c4f204f2a67d3", "bug-fix/2026-08-18-rail-search-outside-click-self-dismissal.md": "sha256:76f43ad63a0a8f93ed56239f5687025533fcbe012b44a726305f6b59fbbb1396", "bug-fix/2026-08-18-rail-search-outside-click-self-dismissal.zh.md": "sha256:d481e3378c57bb0465221a3c1922d606a3e04dd42c191db32ddc8119629709e3", @@ -697,6 +904,9 @@ "bug-fix/2026-08-23-win32-utf16-nul-truncation.i18n.yaml": "sha256:fd27aaf586f7dc7c25a20d8edafe9865f1e39b4f35a0a94719e07799bc06a8f9", "bug-fix/2026-08-23-win32-utf16-nul-truncation.md": "sha256:18802f9953bb420f71fc564e3669ad48341259eaf00af753915e090c9172b5d2", "bug-fix/2026-08-23-win32-utf16-nul-truncation.zh.md": "sha256:cedc8ef2040e63e1a98e5a5a3b8e25cc726bc3c7a2bba4d5a0199d39bd046d6c", + "bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml": "sha256:5e408a9e97ec6309b1ef2a7e0f74b5c3fee0b8c4e7f3c945b26a5061ed11386d", + "bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md": "sha256:895ee58e347fee69f7f06d6d5729944ca6b75939ffccb07a5567e86e985f9531", + "bug-fix/2026-08-24-alpha-routed-image-quality-ladders.zh.md": "sha256:74ac20d843e538f4cba6886be7e8ee1e61cb72b5151c51fa6356910afdb27f7f", "bug-fix/2026-08-24-system-prompt-section-order-ties.i18n.yaml": "sha256:f7a20bddd4544738ec0dbbfc52ea931f42317defa1674beb9a3c0daebd52fc2d", "bug-fix/2026-08-24-system-prompt-section-order-ties.md": "sha256:108a97346eb7a62f1ab01f48dbb9fdd965e8991f53e382b0f501b916af0e9e23", "bug-fix/2026-08-24-system-prompt-section-order-ties.zh.md": "sha256:3deaddfcf9736b3ff8d61b51093d7e46fdcc86103705033e4aa4c9d043794b16", @@ -742,6 +952,9 @@ "bug-fix/2026-08-29-windows-atomic-replace-retry.i18n.yaml": "sha256:0a0c0f6b9e679bfc35cfa87a0103c6115c89545bab52243d83aca6de4bdcf654", "bug-fix/2026-08-29-windows-atomic-replace-retry.md": "sha256:7706103c846a6d349a3b22025afe68e68e34f74ad74ad806bf171135577cee55", "bug-fix/2026-08-29-windows-atomic-replace-retry.zh.md": "sha256:f888c6a0a69483957372b0511ddd13c659400d8629a231a50bd8f5aa5f89b3e6", + "bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml": "sha256:9be5c51a9ebf3f688b034171ad2a1161d48cbbedba7dab4d4be97cc91c527f06", + "bug-fix/2026-08-31-win32-picker-path-string-read.md": "sha256:0b2c6f9974e2d014da1cdb97fa8c22a5a0832b650c746b39533063020c7f4632", + "bug-fix/2026-08-31-win32-picker-path-string-read.zh.md": "sha256:8fc8969713bbdd5951012bcc47f0e334b10c4351d1550157fc07f3f6efc8cd46", "bug-fix/2026-09-01-host-goal-pause-aborts-turn.i18n.yaml": "sha256:670caaf2216ee3f7ba7b86b6e7e475a258f7cfff83b675fede8b12f6bb147c7c", "bug-fix/2026-09-01-host-goal-pause-aborts-turn.md": "sha256:e4301ce3a7de253cf6088a035e34fb0bf5888a06552219e28afaa19a042673ef", "bug-fix/2026-09-01-host-goal-pause-aborts-turn.zh.md": "sha256:ec922ecc0b99907fcedd1c092a74382f8f295fb7f39aefc2e1155b79b250b4ea", @@ -751,18 +964,144 @@ "bug-fix/2026-09-03-hidden-windows-subprocess-windows.i18n.yaml": "sha256:2e5bcbd321006c22d6dc8e5de237f35b86839b48762c4d68a45334badf167b2f", "bug-fix/2026-09-03-hidden-windows-subprocess-windows.md": "sha256:2e26223d6b6cb7ffd098f4eb761bb44b669a1119b80d4ddff7d0ad03fb8ebff7", "bug-fix/2026-09-03-hidden-windows-subprocess-windows.zh.md": "sha256:30b64a935e3adce7b9114c580c427d058876928a10e97deb3981c58d517c768c", + "bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml": "sha256:867d4701c14caab704ba01a3f1376c5e5150e70d3902919a40a2cd6b79e3bdd5", + "bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md": "sha256:854942389b7852e3104b6c08bb8fac16d01c67edf2fbb00f3801fa71752bc4af", + "bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.zh.md": "sha256:525a7b143768f5f34b95f8f2354d3fd2f4b888e53547c253b6b7164277628805", + "bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml": "sha256:f39faea8d4b53d63130ac3c3a9a8bf973103fafaaa41977813aeff00907ebdd9", + "bug-fix/2026-09-03-root-marker-metadata-failures.md": "sha256:0e4ddcede9766cee593d8ee2c1efa5fdbc99cb38824e4991ae3b6f846db3ab9f", + "bug-fix/2026-09-03-root-marker-metadata-failures.zh.md": "sha256:845b1ec4e07d16c7a34967a69f8dc0c3207b69f36ccb37404305804b282eee0a", "bug-fix/2026-09-03-session-search-result-reveal.i18n.yaml": "sha256:ad9dcedeb25ab3eddbc51660935abaf6e6e92eefb66fd14f8852ba5f30e725b5", "bug-fix/2026-09-03-session-search-result-reveal.md": "sha256:ce8983a9ffa3d1b59946aeecf0e10177c8316c3621d5d0718a03e8eedcd05fd9", "bug-fix/2026-09-03-session-search-result-reveal.zh.md": "sha256:cd3ca0290f259a252e0b19fd0f15ac62232847be6f186e16f72652044a034bbe", + "bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml": "sha256:c0ca4d27f080f03c1c9813121f95a7e854b74bf54aebe5357edfc1c77b81769c", + "bug-fix/2026-09-03-user-owned-goal-pause-activation.md": "sha256:4c76b0398caeea365b746b5779185fb3ac4455bd2f7c52af96bfad7bca24c7c7", + "bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md": "sha256:58f0bf940ebb4ec6a927bcf38b7c11e44daf9e9477e58e6e8c29763042e35674", + "bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml": "sha256:ded9ebad47f2918e4a704e4cf5d7227d89f0409cce66f93f13784a86ad1c33bc", + "bug-fix/2026-09-05-continuous-client-recovery.md": "sha256:50c422baa388b6d399f38cbe77a0db02520147783668735b2dd70f1969737b35", + "bug-fix/2026-09-05-continuous-client-recovery.zh.md": "sha256:fece089698983b525125efc0f5cad164d350cc74144fed6fcf36e72dd526fcb3", + "bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml": "sha256:16d6bf730cb36b71c3cb1b2b4dbdee01b9e44d528db7c6622776ae795b1035f3", + "bug-fix/2026-09-05-nested-terminal-cards.md": "sha256:928fc633021f6e8fd838ebeb0d3f390e28709ea9f6471b6eab5386ddd8ce905d", + "bug-fix/2026-09-05-nested-terminal-cards.zh.md": "sha256:b4f2da5133df37c56966de2bab974fb4a6c5d16a2ed21fab407c9543934d5aff", + "bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml": "sha256:d882eb7f14d4b01b7b0b9de75854f1fbd43482b9a9ec5555ce983fe1e2400580", + "bug-fix/2026-09-05-patch-plugin-file-urls.md": "sha256:ea1a97eb4d13c287c13657e5635982ac640ebdf23e565356019eb228951b9b7d", + "bug-fix/2026-09-05-patch-plugin-file-urls.zh.md": "sha256:7dacf40b39c60081535ced11101ac0687b3c3bba36d08d5c42138979a6ba3f30", + "bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml": "sha256:6bac0459b641da9d96153775b8836177e133898e92bf2708ef7d89a45701a18b", + "bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md": "sha256:a018cf580eb661b8f31041c328741f8b7a90e7d29727282ab24828d45839f0df", + "bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md": "sha256:dabd08dbc7d37ea5068dcbb2368ab94df65c4ccdd38fb72279310b902f6599e4", + "bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml": "sha256:b1eb754d49cd48302d90aae5b8bdf958ff48b962becebd7a189ced4e71c29304", + "bug-fix/2026-09-05-session-reference-model-budget.md": "sha256:5917220ae120245f2c4ea9c639f6c3a6306c71e1ccf2d202f5d0dfc6731471c9", + "bug-fix/2026-09-05-session-reference-model-budget.zh.md": "sha256:dd2adc2f9277419f5e1c2aa3f7c8d1d2c4145534e28d38aa61eefed3d4230ab3", + "bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml": "sha256:ac129a891c713c87f812b2bfb9c86aad97edf955c6ae879e97d7f2f3d0eaa42d", + "bug-fix/2026-09-05-session-reference-spill-reuse.md": "sha256:644f547b95acbe85b180f5602d90d9e7a81a4d150bb86d39978c3a63182b9e05", + "bug-fix/2026-09-05-session-reference-spill-reuse.zh.md": "sha256:4579252a6813b9c8d865bcbbaf0fac1819be79527b9a1ca66240af94811a3836", + "bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml": "sha256:7fdeaca60371edda47e8df7ec09bf4476a5952aead0a0947af470395768efd4f", + "bug-fix/2026-09-06-environment-prompt-suffix.md": "sha256:6c255262a30e6a5f01ed372a25ad999f537323cd2147c7bcb56daa5566959139", + "bug-fix/2026-09-06-environment-prompt-suffix.zh.md": "sha256:5bcbc652d9e090a2b253404929663bee67dd6e161dd2d34fa0553d3f3c10fbf0", + "bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml": "sha256:9c406df8ab7e6c268d8c12e15c1d649a2e325c82303abbdf11a22fe7006a4067", + "bug-fix/2026-09-06-windows-python-console-spawn-wait.md": "sha256:8496aedf1f91c90c599a416fdcb4acb9fdf7098c174c6f19bd2ab02c2977789f", + "bug-fix/2026-09-06-windows-python-console-spawn-wait.zh.md": "sha256:b7d996203f28f0d8ad311313e82d708c61ab6a5c6d965fb0e465eac8c5f77999", + "bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml": "sha256:e110e2dea1eb8233b277a017260cbf5771bd3e228de2865d8b8bda07eb557fa1", + "bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md": "sha256:5544ee31ed17d6d69642ccb37008b6b59e7c55296a08996e1ce993df71422f14", + "bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md": "sha256:687fb87f5f1fe4c971d16eafe387f1c7f1b78d957bf23d2206842aa5dacac0b1", + "bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml": "sha256:99fd06cd024f132af98b8f502132c7b0b57f6d807a08daddc6a73d9513b02cfe", + "bug-fix/2026-09-07-typert-package-local-forwarding-imports.md": "sha256:f1d8d9e9f448fb3e6945347c6f268469e6293548c3653c2d80c4d9913f623f12", + "bug-fix/2026-09-07-typert-package-local-forwarding-imports.zh.md": "sha256:e18b1a4c8d901a0a92341611db2057ff70c25b269086a41577cc7ada1c6d505e", + "bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml": "sha256:34330a38fedaa54e2f9280c1db5f4fcf3ac32f022cc96f2dd35f3fd4c419c7bd", + "bug-fix/2026-09-07-win32-picker-foreground-alt-key.md": "sha256:6c6bc976ffd9111ea8ecb08d65b4aa04a78557da23ec32b02d4d943462fe9780", + "bug-fix/2026-09-07-win32-picker-foreground-alt-key.zh.md": "sha256:fc13a95bc7e0802844b93f005c7a2f8bea26018a8cf5bf376158c14b55217c18", "bug-fix/2026-09-09-content-sized-diagram-previews.i18n.yaml": "sha256:a4404fe8e35ca5b6371f9cdb58fea6724cf3e7737bb2db8a3a321d534454b17c", "bug-fix/2026-09-09-content-sized-diagram-previews.md": "sha256:5239e98e5c057d6f7e10c3f10a0f67273778d99b0c4f10b31a1921c3c6474b80", "bug-fix/2026-09-09-content-sized-diagram-previews.zh.md": "sha256:c1ee3790a9d5c3561e1d93d5e649afa904c7d19b9d77248c85ef16daccd953e2", + "bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml": "sha256:a8db240c2d71914772746e7a304642753ac3c9e46ac14f2ecb4a040ed6572a75", + "bug-fix/2026-09-10-built-bundle-css-exemption.md": "sha256:e08f2de5cecf934e6315d9f46d1730e89969eb9a053ecdbf632c4d1be6d76d56", + "bug-fix/2026-09-10-built-bundle-css-exemption.zh.md": "sha256:ef79bc1c085ec7e80331480eac85e1a07159a405a38919812db741309a3b9ddc", + "bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml": "sha256:4035bd4db3f5789fd5bcba4d7996f1bf063e49dbb266d87c11fd4c168d104ce5", + "bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md": "sha256:ba702f70e31bbfc5d3d588045fb15c55d84dd78cca16ca2a4e746eaf3d7459ae", + "bug-fix/2026-09-10-deepseek-image-token-calculator-v41.zh.md": "sha256:1dc46872d18eaba1d086da258093a2b156e9a1c412412c08d26dfbdf70518078", + "bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml": "sha256:39b8b16f67ffa1847d60173d60f6e51e7e3b5a4a7aa1c2764a3dd99fda52efb9", + "bug-fix/2026-09-10-deepseek-v41-request-image-projection.md": "sha256:aeb02ea272c0498e6c69dc12f26126b45228a93b81840064c98ae7469cd22a97", + "bug-fix/2026-09-10-deepseek-v41-request-image-projection.zh.md": "sha256:a2fbe2c9e2051bc96aac9e0e974b87649ae9674659e3b94a0a1e5d30acc84936", + "bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml": "sha256:b587c0cb7a88b53c978f5e08b141936a7fd5d39f0780962f904a57d359400599", + "bug-fix/2026-09-11-incremental-terminal-retention.md": "sha256:d1b86985feecf87453df151fd9cf33567043571de08a5f95e4f672d3e7d7c0d4", + "bug-fix/2026-09-11-incremental-terminal-retention.zh.md": "sha256:436a9df9610954720c851d586142bad655ab4f5f2bc4b9a0fba063c7a342a915", "bug-fix/2026-09-11-session-controller-fork-turn-cut.i18n.yaml": "sha256:4442529ced1c8e48d30f7b50c65ff03eb92dce9815beb438cedbf29f234bce47", "bug-fix/2026-09-11-session-controller-fork-turn-cut.md": "sha256:a996f24a896c7fc5d81e015db3eae27d8a46c853287572e815623ce35eae1b12", "bug-fix/2026-09-11-session-controller-fork-turn-cut.zh.md": "sha256:6486f0eacf5067ef9850dc32f1c27b2109a7d4e9e3c498366f5c38742bb53ac5", + "bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml": "sha256:545de747129531dbf30c8c51dc0b88ce0f047c73db5178a4f5ab414a4fc77b16", + "bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md": "sha256:3743f6d6c0b85eedadbc4a807a592e0d69f19fb2dfb961b2c5c0d9255bf27453", + "bug-fix/2026-09-12-linux-scope-direct-kill-settlement.zh.md": "sha256:ffe962220e1e4c6281b6b87958c37bb75a0a9571f4403aa568e074fd832177eb", + "bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml": "sha256:5ffb5f494f90fc3ac64295e68d5930b6c563e0a4136ca29ce082fbb0716c85e0", + "bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md": "sha256:1b62e7437a3a01b508cc34bf31b9df1bdc5dfd15ceadc933f9d91f15556679b3", + "bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.zh.md": "sha256:04404f826a1bb0caf218d67bf60a893a8cdf459452ea6f473d577f2f891f7e40", + "bug-fix/2026-09-14-web-diff-context.i18n.yaml": "sha256:72ef2f8207ca6c68562757df5a8ddb4a57eeb006fe7064a483adc58e2b57ad9e", + "bug-fix/2026-09-14-web-diff-context.md": "sha256:563edc7b3c7bfc4cd89d010a31257b9959b1301ac842c8ff7c86e07efffbf7b8", + "bug-fix/2026-09-14-web-diff-context.zh.md": "sha256:e6fdd89755e0ea1fd5ae0f616e3d7350862b0a8ffdae475e0eb09f01bd649b25", + "bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml": "sha256:24f59c361560b8add8a92094e610633822ec8359294a1d43577395050d7d24bf", + "bug-fix/2026-09-14-windows-lock-release-probe.md": "sha256:a6c7be3ebc5e8bbf8a0e24db5b0361bb9d611d8b4fb92b9e128c0a9453c7e58e", + "bug-fix/2026-09-14-windows-lock-release-probe.zh.md": "sha256:e83a094dc37251fe336a11cbfda1f58b74d120c2766c949c2f964d4c0ac3a59a", + "bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml": "sha256:0ca0c4cd470dde18a919f26b95a141dfe000d22c4d94eb17d8890c2f8405dd3f", + "bug-fix/2026-09-15-linear-session-list-cache.md": "sha256:fa5763c4c8200714260336f9d71e4063d1b754490b827adc042f7de89bcf0183", + "bug-fix/2026-09-15-linear-session-list-cache.zh.md": "sha256:20c44ab42c186a1a31bbed810f4df5d9d3603281a7c198d0a0c1d7ed36a23dc9", + "bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml": "sha256:8d46cb52e57637239c47f61dcbac4d93febba2a5502f15d4bf2ad8c2cb52518f", + "bug-fix/2026-09-15-messages-v1-base-url.md": "sha256:d0c880ccf6970f5de0bc053877981688961bce6ec5ccdeba2fc9b401753543aa", + "bug-fix/2026-09-15-messages-v1-base-url.zh.md": "sha256:489c9ae3c4e40aab1f0ca53f425d97c59445617b700336ef777304441a0eb713", + "bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml": "sha256:2e0410d2c0d7bc46fde37d50bcff19691eac70d3bc60eb6d56c974f6f7d1b3a1", + "bug-fix/2026-09-16-messages-historical-tool-input.md": "sha256:f6edab5bea25a8542a0f02329e17d0ce26d402f6463d73d75c26447ceab3774f", + "bug-fix/2026-09-16-messages-historical-tool-input.zh.md": "sha256:a8598cbdb0bf2c69766c8cb11ccdd2002c3828fd22af4455ae38c90cf6df4ae5", "bug-fix/2026-09-16-movable-mandatory-update-window.i18n.yaml": "sha256:e526ce14ae9557e24364e3686778b130090445ef2152d976b7cc93e76eac6401", "bug-fix/2026-09-16-movable-mandatory-update-window.md": "sha256:67546cea0b3de2b3e32957a508dbde097d91dd919fe067c8dbecb2c65b50c366", "bug-fix/2026-09-16-movable-mandatory-update-window.zh.md": "sha256:20a0aade6ee71c89e84d70e23c89bb51e8a38c674d103dd43022feabb8d27be3", + "bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml": "sha256:590b569ed41c6fef0d1813487c61b3061d3c9d54dfc030d3d02298a1cf093a0a", + "bug-fix/2026-09-16-session-writer-held-feedback.md": "sha256:1ecdcf65d2d86834f568f00b29a5b4c12e1968cf535c9b9205008a957c153cd9", + "bug-fix/2026-09-16-session-writer-held-feedback.zh.md": "sha256:b21f21820240827ab2e558915bd8ed534d021a7621d450d479f7c9b4a27e81d0", + "bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml": "sha256:d5069e64df9130dc6175fc18c493b12da9ebf535e2126518de86a0adcd3428dd", + "bug-fix/2026-09-16-windows-subprocess-console-visibility.md": "sha256:f87ed1efba55c0da05dea083029a0318c3553bee938ee6418bf81d6a12aa922a", + "bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md": "sha256:289309cb436dfc44479f341929d3567e6c6a114f9d8ced3258f56b880dfeca0f", + "bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml": "sha256:9ac4481dc959ca1234508febdde26257218239213c2da2aba1a74963808ec14b", + "bug-fix/2026-09-18-messages-input-history-compatibility.md": "sha256:000e9ae636b8f8ecb29f93dc6be490c9cc28ecfd7f23d032853e9abfd829f76d", + "bug-fix/2026-09-18-messages-input-history-compatibility.zh.md": "sha256:3cc425872e12bb0474470c316727ff1297948d07965a8b253b21e2ea1469d79f", + "bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml": "sha256:62b1ad050bf72ec4207d90a4a16815fa06ef4188276cc8e8194bcb6f7907e4c9", + "bug-fix/2026-09-18-schedule-task-tab-restore.md": "sha256:032487fe037f1ca1b3bfdc9e40e8ba137651e6be8de968cbddaabfd8691d31f2", + "bug-fix/2026-09-18-schedule-task-tab-restore.zh.md": "sha256:9b24f7147bd1e7bedb7cb51275ad62f9d4add1e586097d49319ebc6a70509471", + "bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml": "sha256:ce9abb2cc9bcbe05aa0fbdbfdda93666507fbb316f14ac333ffcf915a9464ead", + "bug-fix/2026-09-18-windows-directory-junction-listing.md": "sha256:1967cc03acf4c5bfa297bae05313135f28711880b669c2086064b529d2105c7b", + "bug-fix/2026-09-18-windows-directory-junction-listing.zh.md": "sha256:3b61ea836fefcf0a1266bda831cc0d1c7cd979b5ceb04dc22fc4b89b9ce0c298", + "bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml": "sha256:82ae9d30390fdbf464cb65be8b06cfff7a5a5b631c98974a8af0aa72ef697516", + "bug-fix/2026-09-19-declared-session-attachment-carriers.md": "sha256:27e9bd72280e5b6b8e45bd9051ea799e76ef9ec1d12c4197f7b3c045a0fbeb23", + "bug-fix/2026-09-19-declared-session-attachment-carriers.zh.md": "sha256:c17ee7fdb6df91574414a48601ed12ce9719c217754f266a942c77829dccf302", + "bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml": "sha256:2f2d16a3ffc443258dacc317250323b6809d9f722476881ff98244c35e657c73", + "bug-fix/2026-09-19-failed-step-tool-results.md": "sha256:5d46c0a22ba540c344e70e487c2dee8c9d9f9326c7f8a61d8151b0c2bb5f1adb", + "bug-fix/2026-09-19-failed-step-tool-results.zh.md": "sha256:6bbf5523c1253462ba605afcecdcfdf300967a5f9beafbbedfd0310b40919786", + "bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml": "sha256:53a4a048bc2e4ec81df05771768f6c1eb4c52069d52a5d22de0eb21e9450640c", + "bug-fix/2026-09-19-session-local-subagent-migration.md": "sha256:ab32d3fc829db5adf8a2b20c749f16ac7056f26dd089a8eae6a376d45a133d2e", + "bug-fix/2026-09-19-session-local-subagent-migration.zh.md": "sha256:7b5e119bbb2300067463ab6961a21df726c460aba0fbc4a65f45f00e055c88f3", + "bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml": "sha256:6590c530405d7f2e448ee914e183fb5772c9038c694767e39bb6934c9bfa5631", + "bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md": "sha256:f57e90276cce7a4dda6421e06e4d91ab343f97f27df61b361795da093895c743", + "bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md": "sha256:0d2c89ddc8ef06a9af67fa7cfe7db5b411842ed67729838449033c9211320d3b", + "bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml": "sha256:1e1f60d7ecf2f5b43938f34fb7e89dd19d834705f4334c72b2d927b9e447b74c", + "bug-fix/2026-09-20-windows-embedded-mandatory-update.md": "sha256:dc58c1d9383897fbca0c02e4fa5413ce02be107b748e369c982b98478eee8da0", + "bug-fix/2026-09-20-windows-embedded-mandatory-update.zh.md": "sha256:f865589923266a95d900a0886ebc9f9de7dab5edf076b7156e450eefc10dddb8", + "bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml": "sha256:ceb93294c9f3a52a96e3a89bb1100c133d96adb3cf0d0fdfd17681d95455acce", + "bug-fix/2026-09-21-multimodal-tool-result-retention.md": "sha256:2b897481ea18e10cb5d2bb8dc8343dc9a3285e6ebeafd59acaeb327cc622ec42", + "bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md": "sha256:f50bf94b839ba276633a0f5ec9e7bd97fe64353182887e3cd8c29e969b845c4d", + "bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml": "sha256:9171767ebefb2c35d3503f3489a099aafea862d1e8e5c589cd83faecbe3913d8", + "bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md": "sha256:f18ba0f49f380b8e7a2db8b192d747ea8dbdd3ba48c64bb215700ada1825a400", + "bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.zh.md": "sha256:b84845e71194ccbd201c1c50baf15d2cb979273af61fda0fa10b52eb5ae3f84b", + "bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml": "sha256:62796f58739708109ffa95d2002821cb762cde2899b775087120228d2bb11c2d", + "bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md": "sha256:668fef415a239c92d79afa959c2a152514e61a35c90dd5ff5c7d614a58f939f5", + "bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md": "sha256:4412c25451b1601d36a643361d266ff56dfa56373cccd934c9d60125b64d854f", + "bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml": "sha256:c200d65c48e3a952b333e60ce5c0f8cdc56ef3070e563ff2832ea79b27a40c9d", + "bug-fix/2026-09-22-input-echo-admission-ownership.md": "sha256:46467c3b30039402b856486e1865cf04350161cb889d92d76bc5cb28a4841df3", + "bug-fix/2026-09-22-input-echo-admission-ownership.zh.md": "sha256:9105e7df26571f8b43ea23167f7f116440c60ed0e9895d4f4d4d5fa797b1c6b3", + "bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml": "sha256:1a3223b7df7a279d1f86e59c49ee2104ecc8ba785aac7e01ca6498e107e03d74", + "bug-fix/2026-09-22-windows-open-through-shell-resolution.md": "sha256:9d6fc200420471a620c5e0feddeac8f5dd4ad04941d673f23133011bb36bbaa6", + "bug-fix/2026-09-22-windows-open-through-shell-resolution.zh.md": "sha256:9fb8c46e9079b8611ba8f881b182254721df8876b75d0ae2c6ce28d17def7979", + "bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml": "sha256:88e1d37847d6d7e2dd574d309315e4cd608c78275a3ea8ca281c1ccbb707902c", + "bug-fix/2026-09-23-bounded-pnpm-runs.md": "sha256:307781166ae3f168f7e3897876cd5492c2e04651ea4b03126f3ea2f9757f895f", + "bug-fix/2026-09-23-bounded-pnpm-runs.zh.md": "sha256:f5d0545ab8ac9c93af96399082ee69d26ccb7604ae919798e7e494ee8d473749", + "bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml": "sha256:0844226ea72ae31b013c51f7ec84b1cda6209b63e7fff6fbf5910f201692c60a", + "bug-fix/2026-09-24-exited-holder-lock-takeover.md": "sha256:7aea0a794b2e8566c624deb0ac5a43fffe11c52b17d2e15fe88ed412f3ad9487", + "bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md": "sha256:61f647916f08f0c601859770b1fbc079592374407e0ac0ae88366c8ecef1a85a", "feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918", "feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b", "feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69", @@ -775,6 +1114,12 @@ "feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml": "sha256:79592f96bb25713d01865f37972a6919b2bfb3b66368df0f275bbd59d09ebcf6", "feature/2026-06-18-acp-terminal-and-tool-rendering.md": "sha256:946d0c580705ef2e7c7ac1897ada074e72f2ec4209c1e7531b6eccf116e9aecc", "feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md": "sha256:fd815817925a038f79b52b6fab43abdb2d655db3ec07974ce1320ea3674f2afc", + "feature/2026-06-18-compaction-capability-seam.i18n.yaml": "sha256:a3740fe60e93bd3bae647d4cf5968670facff548ef7f496d475646019b5aac77", + "feature/2026-06-18-compaction-capability-seam.md": "sha256:d1dc23017107bf77a018ed40fbc47c243c9e839c3986404fa45aab57ee538d9c", + "feature/2026-06-18-compaction-capability-seam.zh.md": "sha256:e4497e04c350c3d94fc40c960bd6b6ab67c516347c44fd18cee7b94c2c5d7df4", + "feature/2026-06-21-subagent-capability-seam.i18n.yaml": "sha256:063f307493876ac7373ebecb3d93ed1a75ec0d6ba75af8f32c31656c444272a1", + "feature/2026-06-21-subagent-capability-seam.md": "sha256:19b28736f119415b7078d44a3a87d541cc2e2431ea89539c63b5149c03d0be60", + "feature/2026-06-21-subagent-capability-seam.zh.md": "sha256:ab8501071cf778097a40a02cb83b9302dc8d816bcc9c49019efee04c26cb996e", "feature/2026-06-22-acp-subagent-backend.i18n.yaml": "sha256:cdcf46b80055bac08245d5b0ae52752339cb751eda65becbbceb17eb7d980d62", "feature/2026-06-22-acp-subagent-backend.md": "sha256:abd48c09c7c909c8dd3f6861cdc076a56d6b19ad870fcaba5a27ed7db433cf5b", "feature/2026-06-22-acp-subagent-backend.zh.md": "sha256:7e702480d60bb261d7e01e807de42a1f81a62c85f8aa59c87774471eb00530ec", @@ -793,18 +1138,30 @@ "feature/2026-06-30-hook-protocol-lib.i18n.yaml": "sha256:3545b0e36ac0a8ccd02ff4b355747a7431530ae8c12d2efe1d516687dc4185b1", "feature/2026-06-30-hook-protocol-lib.md": "sha256:76fd45e8eec8faab06afb5393dfbc54555942f32e4939546d40fbf23622acf1f", "feature/2026-06-30-hook-protocol-lib.zh.md": "sha256:217ad562326adfb90129c2c87437bbd0aafada9c976d053bf142f616203f0d1f", + "feature/2026-06-30-interception-extension-points.i18n.yaml": "sha256:c9afc81ad51ce448b7f2fd478ed8e53ef76619582861b03a3f0ef0ba3cddb4ea", + "feature/2026-06-30-interception-extension-points.md": "sha256:10ae368b10d0960c9df52060308894e09b7183962477426bff5113d639af4364", + "feature/2026-06-30-interception-extension-points.zh.md": "sha256:e0dea3aeb21058b3057fd4a9b1084612b6df1fb503a908790ae2d7d4d763e0da", "feature/2026-06-30-session-store-fork-api.i18n.yaml": "sha256:874b1f53eff4003028210c3f598cfe77bee7fcd69b950b69aea3a4059fa0faef", "feature/2026-06-30-session-store-fork-api.md": "sha256:38508b6689e3cd253e968c0257f60f12b62544498ecc863631488f4a957a3eec", "feature/2026-06-30-session-store-fork-api.zh.md": "sha256:2cb9dfdcb304b5f73588fd74895c63a0a02c2c9ac23da967ebff608543222b57", "feature/2026-06-30-subagent-observe-enrich.i18n.yaml": "sha256:08c2478ba394429f46c1e87a9f055e88704a9000e5d250d5600c0c85124cb17f", "feature/2026-06-30-subagent-observe-enrich.md": "sha256:0630975c3e325975a932f58a65a178b79c624dc56ebd29e288e96f5a189cfbfa", "feature/2026-06-30-subagent-observe-enrich.zh.md": "sha256:b9fbb44a7d81f4063faf3baaf97c382a2f5106be533feb4de792ee57b766c1a4", + "feature/2026-07-05-dynamic-workflows.i18n.yaml": "sha256:6b7648ed9a1c4a8d242fc9c675d14d9fe58467812991889c5d525c2452e64d98", + "feature/2026-07-05-dynamic-workflows.md": "sha256:9389387f13e9199da0becf172b85ba4f3217492de7be04f176051a7421f7e5ae", + "feature/2026-07-05-dynamic-workflows.zh.md": "sha256:856f168bddcd700bf567196d92be9cb2f8a0ccba730be5a31277fd004f65d8d2", "feature/2026-07-05-skill-system.i18n.yaml": "sha256:ed37e752eeb327fc436f910f96f9ad0fd429e0c9271e2c9268ede1304f9ac2ea", "feature/2026-07-05-skill-system.md": "sha256:abee9fe973b9603f0a60e7efc034ab90910e3807d87862f8e7023b76624e09b1", "feature/2026-07-05-skill-system.zh.md": "sha256:92cc3776bb24b29cec24f80a40a5939ed407c9240b97c93bb1bcac445682c571", "feature/2026-07-06-explicit-tool-order.i18n.yaml": "sha256:126fefb60b6439a7bf962d9b4b028c269663855e72e56a204ef811e4f7a5fbb1", "feature/2026-07-06-explicit-tool-order.md": "sha256:3fda289680337fcd07067ff2461469dfb1649ac9610c4cd46f1fe0fc1795f9c2", "feature/2026-07-06-explicit-tool-order.zh.md": "sha256:37a1e54e9bfb21cafa7f664d0769d0c72c435ab4dda522c0cbff9c22be21f15c", + "feature/2026-07-06-sandbox.i18n.yaml": "sha256:44086ea7914a400e83847487efed66ba00f084c17f31087a5f1e7a662fc78edb", + "feature/2026-07-06-sandbox.md": "sha256:a2c499963c7b3e38d37cbd1879d47de263478b03565cc9d27f44895fbc4e9ecf", + "feature/2026-07-06-sandbox.zh.md": "sha256:09536fb6a3d3a5630f098a6e0e8e508300df35095d351597b21ac74cc3e147a3", + "feature/2026-07-07-mcp-client-plugin.i18n.yaml": "sha256:72681a8218c2c40beab5548a177fa433951f74d0d6010adaf9b4147e7c672ce7", + "feature/2026-07-07-mcp-client-plugin.md": "sha256:ac5dee6d0698b2b50ed8e83de177fd2d60965c57cdd1f90c6551fecdaf8d3341", + "feature/2026-07-07-mcp-client-plugin.zh.md": "sha256:4f68cc70349111cc972553bffa2325e88e21fd232b7f21eedb567e66b1bcceb3", "feature/2026-07-07-plan-mode.i18n.yaml": "sha256:c59b6a6c218d741cdef8edf625f1d015e409a39411fa64e65200fdebb1c49394", "feature/2026-07-07-plan-mode.md": "sha256:7bf1bb8e826edf68f0ec919dfd4f66955b935b46b4400d7de85fac3e4663edbc", "feature/2026-07-07-plan-mode.zh.md": "sha256:5b08cbcd8023f26744e481386177dd0e82423e8b0032829d0dbc22a92cced0cd", @@ -817,6 +1174,9 @@ "feature/2026-07-08-repeat-tool-guard.i18n.yaml": "sha256:e4382e8d25f23d728f1869c5195723698842dfe5422443a22f21fa8ae9b2c63c", "feature/2026-07-08-repeat-tool-guard.md": "sha256:95df2c423624c5c56e0b3bb76ced49e91878c7e5096cfef0faf4b20203d8a2aa", "feature/2026-07-08-repeat-tool-guard.zh.md": "sha256:cbe63d163aa19fc5a63233b05c8f6ed9a8a0ca215cf6f337a3c1e8e48af11b28", + "feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml": "sha256:f2384d4d7dfea1f8800a451c8dd6cd5bf6433740967abacd708a94767dd34027", + "feature/2026-07-08-self-referential-cordis-toolset.md": "sha256:3138d424cf695f9a63ba10475b2440198f4a881d62eede2aea2215841e9b0e48", + "feature/2026-07-08-self-referential-cordis-toolset.zh.md": "sha256:3ea9e3a050458e7cf8490f6eb057f2cf2b26c2baa6983ad3e086c8dfad1ac83c", "feature/2026-07-09-bash-backed-grep-glob-discovery.i18n.yaml": "sha256:8eb6ec1312c55b93c2d51c6e54585d6f4d5bf6db444fb9c336681848676e3638", "feature/2026-07-09-bash-backed-grep-glob-discovery.md": "sha256:908521563012a17d312de664f380444aeafffd401d2200ec97e7bf004bac9ee7", "feature/2026-07-09-bash-backed-grep-glob-discovery.zh.md": "sha256:52eec49e454357a7e0c3f27c8aaa63840b5822b5d103f49ed48a5c4f25081b00", @@ -832,12 +1192,18 @@ "feature/2026-07-13-session-query-tracing.i18n.yaml": "sha256:8dfcc41c31d13474e0f8e00bd2983354f860f2d8e8b3a255f5448bbac13ee368", "feature/2026-07-13-session-query-tracing.md": "sha256:76af305255f569b86a4c4fc00cd6c8a058926d507a8f5a56ca60b8211ebbf853", "feature/2026-07-13-session-query-tracing.zh.md": "sha256:3681be5241766179ca1a01754a0314c9f8481f6f91c60fc95eac9d3a18398c0b", + "feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml": "sha256:8b69facc97b136628fb8ee969c24b0db67cdae49e636ef37276d24cee2bb3078", + "feature/2026-07-14-cross-family-fs-sandbox.md": "sha256:73a3dca7a2568825581ff9619265221f3aec29935ff8811adb201de3858c3031", + "feature/2026-07-14-cross-family-fs-sandbox.zh.md": "sha256:f45eca13be281ff5792fbf51292c89c899f7e706a53de6d7f4f312c4bcdd2838", "feature/2026-07-14-time-context-plugin.i18n.yaml": "sha256:670c093817c77e093562e02f43984d42ed44ebcced7c91d09366839e412d05e1", "feature/2026-07-14-time-context-plugin.md": "sha256:618b121da38a8b610bcadaecf121ca823b2c8c13598c012b350c214b82fd238f", "feature/2026-07-14-time-context-plugin.zh.md": "sha256:1e9eee8ba427a6f2ee08c79e2fcb33c0948e67a80758fdf9f8c9f7dff9aea361", "feature/2026-07-16-durable-per-step-time-context.i18n.yaml": "sha256:7521670265eacbdb875dadd45ad806ce2ae9529634998ce902c275ab18142052", "feature/2026-07-16-durable-per-step-time-context.md": "sha256:2fd420bec9a40f7e2e4c7399176a243d193ec5e8e25328da1204361eecb1d1ec", "feature/2026-07-16-durable-per-step-time-context.zh.md": "sha256:2c383c738f42e786a3d97398dc44dad91596a317eb44078243f1e5ac372ad512", + "feature/2026-07-16-harness-level-loop.i18n.yaml": "sha256:0c42d83eafa5c1520cd0b22d04d30ddc0cb79043a71fd5280fa5426d14de9418", + "feature/2026-07-16-harness-level-loop.md": "sha256:fa2a98c59138fc61fdd11b29a04b624890befa5500077f220924f978135dc4e9", + "feature/2026-07-16-harness-level-loop.zh.md": "sha256:8bd0c9e884ed5dd2f2511391ebd3a3bcc806dbc33afb16aa69bf89e0af371167", "feature/2026-07-17-dedicated-full-screen-tui-front-door.i18n.yaml": "sha256:c04ec462f846887f349ffeb48aef8febf50f06903e7177ffd7679a694f6c1a57", "feature/2026-07-17-dedicated-full-screen-tui-front-door.md": "sha256:feb70faaad016965e8cc1bd7339947e48937fb3e8ea1425dada581073396f50c", "feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md": "sha256:71e4a4abf01e5fc46f7da60e0a66bca659c1ee3c4ea2c4bc20587844f33920fe", @@ -847,6 +1213,12 @@ "feature/2026-07-19-human-goal-command.i18n.yaml": "sha256:29ee3554aa2335a3d85ff4743a1b427ae192ab428070ad4378615ac85877baff", "feature/2026-07-19-human-goal-command.md": "sha256:9cfe4bc4c3bd05e59dabb3344139e29237fe9ca9804b3cac776043bd0c2943eb", "feature/2026-07-19-human-goal-command.zh.md": "sha256:b29b38fd6536dd1578ef441cf587d5467b9a03eaba335d9cbe17a5e32f6a24fd", + "feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml": "sha256:bb9abedac42e3d5c3c346710480ffd5c65a0161c17f06deda85dc45d0c368777", + "feature/2026-07-19-persisted-same-session-goal-domain.md": "sha256:7568ed133fdb64d8d13aae9f99d03b7391693365f388f1bc39609c3dcced78b8", + "feature/2026-07-19-persisted-same-session-goal-domain.zh.md": "sha256:f4bb202df863bd35f2a0e7ecb3b9204bd236baa74e35d20c301bfa9718ffc829", + "feature/2026-07-19-plugin-command-registration.i18n.yaml": "sha256:dbf7426c745a410dc2a91ee05a75b44db2d14e15fe773cc53be096e9590eab83", + "feature/2026-07-19-plugin-command-registration.md": "sha256:adebeb0f86965e70b0d2c2eba8bef73862e57fc7f5dd775063572a9c5579474c", + "feature/2026-07-19-plugin-command-registration.zh.md": "sha256:a084b9d4f272f407a43c65791272138e158c71041692c1787140f5a3d5d33d64", "feature/2026-07-19-same-session-goal-round-driver.i18n.yaml": "sha256:cb7e2c23199366aab1c2399abd050d94ae5eab5b0430d0e96d9de4c522331a49", "feature/2026-07-19-same-session-goal-round-driver.md": "sha256:fce94c819f720a972564afe7a1dfef1905c5f088154e5e13bfe5507e613265cc", "feature/2026-07-19-same-session-goal-round-driver.zh.md": "sha256:cc32045a7f60f1d0c56a6b5c61f7b57f5651dbb3394dfa9d810462740f748867", @@ -868,6 +1240,9 @@ "feature/2026-07-21-dsh-system-prompt-source-path.i18n.yaml": "sha256:22efaf3237425fecbac1b40a444454e0fc244a3c85c2f6a14535de22ea777719", "feature/2026-07-21-dsh-system-prompt-source-path.md": "sha256:5fa554932c62a8bbd5a619581710d7f8b6b65d79ec1e340129cda96d279c5ae3", "feature/2026-07-21-dsh-system-prompt-source-path.zh.md": "sha256:995cd593074881c72510a6af3ba80108bbf986d49508cce9f698c2fcb493fd23", + "feature/2026-07-21-follow-instruction-symlinks.i18n.yaml": "sha256:f2975c4d214b821ad09aedc63496646c6c719e3bf6f0dbf666ec600d451008ed", + "feature/2026-07-21-follow-instruction-symlinks.md": "sha256:a0d20549c736c719f896ed183377b06928036d36627bed9ee0fe578c1ab8a85f", + "feature/2026-07-21-follow-instruction-symlinks.zh.md": "sha256:9e2251cdb04ea42fd042c5288ebfc2619ea02a61854242f9970b94f2a78b0a98", "feature/2026-07-21-instruction-load-all-dedup.i18n.yaml": "sha256:8355cb3e30dab043df91579c9f9300f684bcc70ea36b211b6879acc6d6faba41", "feature/2026-07-21-instruction-load-all-dedup.md": "sha256:ccc4db9687273ed12a9bc6ae5a35abc05051ac33fc3ddbdbd2ec0fccddb35e19", "feature/2026-07-21-instruction-load-all-dedup.zh.md": "sha256:b55d85db8e7409360e0751c643ca444a871d6142e804f9dd30d39d8218ef5578", @@ -925,6 +1300,9 @@ "feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml": "sha256:d046fe7d735bf6bfc0e6cb8eaf8961db5b7368e2d21519c1257c648eb9060a5e", "feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.md": "sha256:88f400ae4eeaee44aea8ac45bf574f8346ea473fffe86885fdcce4ca4166c010", "feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.zh.md": "sha256:32f7e6302b19ae25a2d04cc9c02eebf95ae4592ecc9321a7168a6a8a627d7be2", + "feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml": "sha256:d1f81486550c16bc5b70166c8bd25781136fe9490a7486aaa110d13367b754f5", + "feature/2026-07-23-session-telemetry-otel-revival.md": "sha256:40b809f925772abe907287bb5f12bdd3d01a5f42a5aea86bde4e0e5f048342d1", + "feature/2026-07-23-session-telemetry-otel-revival.zh.md": "sha256:da254a254dd6ba7dee7da793dccbb9c7f490f71e9625c0a0d1f286f2d97cb9f5", "feature/2026-07-23-trajectory-step-cell.i18n.yaml": "sha256:fe2e935a0affdef877902a40d9861ef5f55b30f40650469f6a52a4d45a92793f", "feature/2026-07-23-trajectory-step-cell.md": "sha256:185e3b87174cb6d2f2d2271fd2a74b1517d03e8570be602570d027bf6002d106", "feature/2026-07-23-trajectory-step-cell.zh.md": "sha256:51f46be43d2f5c4f78a05ed9aeec92d1f33ac988f45cf24d35528e9c43828ef3", @@ -967,6 +1345,9 @@ "feature/2026-07-25-session-list-browsing-and-manual-order.i18n.yaml": "sha256:3430aba7bc6d8cebe158ecf292ffb6384b9d8c66553f41843e91da199030fbaf", "feature/2026-07-25-session-list-browsing-and-manual-order.md": "sha256:1f2e78f24617844ef5b8f2906c4d6f14ef9b2d5a52223baec3493888676e9f0a", "feature/2026-07-25-session-list-browsing-and-manual-order.zh.md": "sha256:04052b9ac92da61a941959f148af5ba91ee7b15fa5205ff4103f25f8bd431e48", + "feature/2026-07-25-subagent-policy-inheritance.i18n.yaml": "sha256:77aace611a0c267145ee9d354f11491d51861b53e9f50a4bb6cdb2aef63178a6", + "feature/2026-07-25-subagent-policy-inheritance.md": "sha256:7a4a97dcd0963d045c6966d9090141dad5f9fa173ba672c3130bd514a7d7d1be", + "feature/2026-07-25-subagent-policy-inheritance.zh.md": "sha256:94ace13010b699189a10ae7afd019743a043f421adaf89eb1f43119499f17cda", "feature/2026-07-25-workspace-ui-product-flow.i18n.yaml": "sha256:8a51ba028c2568471ded13a7eba9e1d41393e0f4194b754b84e398d92bc76edf", "feature/2026-07-25-workspace-ui-product-flow.md": "sha256:3799f3ecbba9cdc44a2df420c41f291ad1220f1cddc84ab27bde518823c74095", "feature/2026-07-25-workspace-ui-product-flow.zh.md": "sha256:0f2db4d277cc0eb000d190d3dc35ac99b946a6eed602b7a081b85714ae5a8318", @@ -982,6 +1363,9 @@ "feature/2026-07-26-ptc-dispatch-ui-foundation.i18n.yaml": "sha256:06ff1aa77e9ac138f343341fb5e5defa87dbec037be06680bfe7ad70c6ddebe9", "feature/2026-07-26-ptc-dispatch-ui-foundation.md": "sha256:d06ab3a41c4ac57bb651f709fee6abf1e443dfe88c1ad1c09ccb8fa73a217014", "feature/2026-07-26-ptc-dispatch-ui-foundation.zh.md": "sha256:ef1b781407e6e0bf4a3e0eefe00ce8fd9bd93ed9cafbb886401d5c99d7e23980", + "feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml": "sha256:b1a18ca690e8ae18388bd761f56169c3e8de119f882bb25f949fa7bb46efa5a2", + "feature/2026-07-26-ptc-live-parallel-dispatch.md": "sha256:3d0a8590695b201b61cba9586b61f0b507ad3ffa5368b75cdcc27ec00c92cea3", + "feature/2026-07-26-ptc-live-parallel-dispatch.zh.md": "sha256:7b9fca86a2ee75a3e70511dfa69b92a05d89997953a77ac971418836a7ae5f56", "feature/2026-07-26-todo-parallel-in-progress.i18n.yaml": "sha256:2a6b9075e538fa158189ec725e75cf1e39fbf4e800f04a6dc95612a40d1e7582", "feature/2026-07-26-todo-parallel-in-progress.md": "sha256:73e630a7a3d689bc01c2f77d74ed6929e6b50d20780931a90452cd0dd9b5c11c", "feature/2026-07-26-todo-parallel-in-progress.zh.md": "sha256:3f95dd6d3b3fb7c934a9be48336df829871a818374e7c63329e934c91836547c", @@ -1021,6 +1405,12 @@ "feature/2026-07-27-web-session-search.i18n.yaml": "sha256:511e0ca45eda11423ec8407438d0cd2cb144ff23fdafbaac0c90f8a7160f35e1", "feature/2026-07-27-web-session-search.md": "sha256:8c5310b450fe722ceb1a90da3352514a2822ab7a58d4414a787c1ec8bc475ad0", "feature/2026-07-27-web-session-search.zh.md": "sha256:48352ac09ac255e3761526ea688df799475a5609ff88ebeadd106aeeaad10bc4", + "feature/2026-07-27-web-subagent-conversations.i18n.yaml": "sha256:6f0f08a3e717c0c71595a659e12e3667d419166c7ee1af345f7b04be413e70ca", + "feature/2026-07-27-web-subagent-conversations.md": "sha256:927d4942d88169046fe13a262ec47ad9e78677b0329192c7583481ea88a35b35", + "feature/2026-07-27-web-subagent-conversations.zh.md": "sha256:4adecbc84324df5b5e8b13778203b94b66a209c63b4e16bb0d452ed77c4c019f", + "feature/2026-07-27-workspace-registration-deletion.i18n.yaml": "sha256:2586fa408650940b15a430dc5990f49fe7bb35e1d4368ada540f3e0e7315e969", + "feature/2026-07-27-workspace-registration-deletion.md": "sha256:f894bc7ac29d96bc3d3f2057131fa905494ce518b41fa3b0ee2833c8eec24444", + "feature/2026-07-27-workspace-registration-deletion.zh.md": "sha256:7da95c573e2213833118cc0eeb8da41a737d806faf1b71b14001c1f583202ad4", "feature/2026-07-28-cross-workspace-resume.i18n.yaml": "sha256:b8a356e297d187a382aa3575a9093ac256f9027ac7813e8bcfba3c03284dcff2", "feature/2026-07-28-cross-workspace-resume.md": "sha256:50a663e01be46d2f5d205743a844e2e4610c46d80626d2dc76a42e8a50300ff2", "feature/2026-07-28-cross-workspace-resume.zh.md": "sha256:2679654a6ef71200b919175d3c3c69d5c105401f83c60bc000b2e321a56e1e43", @@ -1036,6 +1426,9 @@ "feature/2026-07-28-sdk-max-output-tokens.i18n.yaml": "sha256:7e6e899a848e8feabb1a2a9cdc7adca8d79c5dcbad70ce9b6993dee27fd0675c", "feature/2026-07-28-sdk-max-output-tokens.md": "sha256:8c354d085c164e2e14209d7bf192adbd4dee9953c9813c0c28c9f6f732c803f2", "feature/2026-07-28-sdk-max-output-tokens.zh.md": "sha256:24323876389ddfe677dc8667df64a82e97ed599d7dc4fd140929ee91d65e28e7", + "feature/2026-07-28-skill-invocation-policy.i18n.yaml": "sha256:a891aa634b61d655055ea58d232eb9f783099d137e56f17ce05939bb54d6354a", + "feature/2026-07-28-skill-invocation-policy.md": "sha256:62f9e82732c761cbde7206e5ab1029b2d794bdb26d10e522646490ec7966530f", + "feature/2026-07-28-skill-invocation-policy.zh.md": "sha256:f22c1a90730a38ab389cdb99c2590d1d0ad18e9454a1ec9f5fc7876648934a8c", "feature/2026-07-28-todo-plan-clears-on-next-turn.i18n.yaml": "sha256:d9ca84732d19f8d84fe7003d09cf437b572406490f52a23ecd2841239bd1eda6", "feature/2026-07-28-todo-plan-clears-on-next-turn.md": "sha256:af16dd33b87cf31d097e8e1292c590461a6e4428f80b21580c724c61ae602b51", "feature/2026-07-28-todo-plan-clears-on-next-turn.zh.md": "sha256:ece7eb492ef653ebb693903d7ff6609201f02fbe4a2ddec47a26da6e75cb7f4e", @@ -1078,6 +1471,9 @@ "feature/2026-07-30-plan-review-presentation-intent.i18n.yaml": "sha256:5a68291220b29a4e1588ddbc205d0f72f142cfd166389465390ac402aa0a9a2d", "feature/2026-07-30-plan-review-presentation-intent.md": "sha256:2ad115ac571ddc44e620d0a452a634027d0cf5efe7b42e121d080ebd8517e2fb", "feature/2026-07-30-plan-review-presentation-intent.zh.md": "sha256:348ed63e665296a69b93fa6d6eb39abc9b2b31f0f87b28b83925e9ee6898e167", + "feature/2026-07-30-queued-manual-compaction.i18n.yaml": "sha256:249173b2861f4c47851f1caf8a0bbf4341f685a063ffd399052f35a494af5344", + "feature/2026-07-30-queued-manual-compaction.md": "sha256:9d7c15be020b4c4b81ddebe10bac7d2d4daa08c84fb4304dd666202b6f23b102", + "feature/2026-07-30-queued-manual-compaction.zh.md": "sha256:1aa5725b0bbf6cec148233eace4d79572a4dc7efb08d9db24dc05dbe659c682a", "feature/2026-07-30-search-render-card.i18n.yaml": "sha256:f6cd5cb7ef8bb0e649f7f130eef60dbd05f484a77742f57b3dc70116549af062", "feature/2026-07-30-search-render-card.md": "sha256:3d4efce36de933ccae1a34ec49ff6d8d086e644946cf07587ac16d793f326593", "feature/2026-07-30-search-render-card.zh.md": "sha256:2dceb2079912016ee2be018b3ee073126952289fde2b40a11565cbcb074be05c", @@ -1108,6 +1504,9 @@ "feature/2026-07-30-web-read-card.i18n.yaml": "sha256:6d833572067f197e9914cf50b91bd52a0b217cdadc86a8bce32f561e1334eeb7", "feature/2026-07-30-web-read-card.md": "sha256:3a0d3fbdb4731f2b395927d0f6bc7e7d4d9e6a1f4d1f7cc82761ed0c9d27e427", "feature/2026-07-30-web-read-card.zh.md": "sha256:2bf2675f87ed5758331161ed0d0d327d899575c1698458d33b1513710a9decf2", + "feature/2026-07-30-web-remote-markdown-images.i18n.yaml": "sha256:31df060ff17a50cf7187c2153800d56ff96436af5ce737c8d174ef7d04e0b671", + "feature/2026-07-30-web-remote-markdown-images.md": "sha256:5da1da4b92eace0ae30147471ec453356ecd8d09b03eeb3aad11f2e25be59eb3", + "feature/2026-07-30-web-remote-markdown-images.zh.md": "sha256:3aea839bdaba9ac2cbcd7f894d7b7f4a9e8f775bb371ab999c11299a24c18c38", "feature/2026-07-30-web-result-card-frontend.i18n.yaml": "sha256:14733b85dca48c338ce38d9b0c8d3e5bdf6a3e3936de245c2ef8c86c77486fbd", "feature/2026-07-30-web-result-card-frontend.md": "sha256:2f4731bc35e6e67030f0cb440a592403089b7b237b72dcd649e0fceef76e09e1", "feature/2026-07-30-web-result-card-frontend.zh.md": "sha256:5aecc2df79c7df169b508071354115e3198aeecb24923e531c3be4651c35352c", @@ -1123,6 +1522,9 @@ "feature/2026-07-31-browser-derived-initial-locale.i18n.yaml": "sha256:ecd8114e6d2d6b407d2a79e719486ca80950ff2f8b16b41f6db1b0c6044755d1", "feature/2026-07-31-browser-derived-initial-locale.md": "sha256:696b56929237dc470295a78ca1b00434574194c449eea108db82949b34036c14", "feature/2026-07-31-browser-derived-initial-locale.zh.md": "sha256:b0632048bdd6577fe04ac281e6a1d956befb07024a6de5cb2cc7d7b2fd63b83b", + "feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml": "sha256:f0460cc416fa546001890e30e59f4ccdc072a15ff71516bf322ea0e91263ab42", + "feature/2026-07-31-even-out-shipped-tool-rosters.md": "sha256:c226342e18d1463f7d79d6a32ad4f6cdaa9a5c6ef875764aa1c05b43460a93fd", + "feature/2026-07-31-even-out-shipped-tool-rosters.zh.md": "sha256:d8d9a8ac22a96724bf63c4601108b1c7c74926ade678369676691b515f9669e6", "feature/2026-07-31-experimental-subcommand-gate.i18n.yaml": "sha256:d223669bebbf6ea65b4ec636e8e7ed618eff389117335946be713897151c6968", "feature/2026-07-31-experimental-subcommand-gate.md": "sha256:8fdee37340f7e72397cf2f440a2ca70639a987d07f0c2102e02e79c0fec4bfeb", "feature/2026-07-31-experimental-subcommand-gate.zh.md": "sha256:bcdec0f82319670a1d1de54a27b103b5e2d86306b884a5f9f415b89cd5a373f4", @@ -1150,9 +1552,15 @@ "feature/2026-07-31-web-cards-toolrow.i18n.yaml": "sha256:f9a6ab72a77934cdcc02167c7313f08d7e9925362017b34bed7ad56c8c70fbaa", "feature/2026-07-31-web-cards-toolrow.md": "sha256:5058f7cec4497d1cb0a5c8e77b88fddacac6eead034f3edec88e8514919b8a3e", "feature/2026-07-31-web-cards-toolrow.zh.md": "sha256:ba84ef2e1be61211ab5ba6950b78ede3d3a979f252bc068d3e04e2c025f7bc03", + "feature/2026-07-31-web-default-search.i18n.yaml": "sha256:ed8b6bfc28383640d3a6d52fd8ac691920222ff892f5c011592ca1e2f6ee6282", + "feature/2026-07-31-web-default-search.md": "sha256:3349b01fa16214139d93c3f70eb159cb51e53c7c10a8659a769ae6de9425832d", + "feature/2026-07-31-web-default-search.zh.md": "sha256:2571749b67229636db4462f2263d03b7c39ca7a1a6122fcad5c3501f60cd90f5", "feature/2026-07-31-web-telemetry-default-mount.i18n.yaml": "sha256:9763b57b3373fd9082ccd10e6c8a80d676ba9598b9c6d58084af7b21122b1cdf", "feature/2026-07-31-web-telemetry-default-mount.md": "sha256:dd7a7c1e246f414acd77d767326cc14ed875351229d03e84d2bb7de9ec5a8956", "feature/2026-07-31-web-telemetry-default-mount.zh.md": "sha256:eba00332fae8c0374a002ead25d1f35842c4239c3f9343b46fba6c99b2e3e804", + "feature/2026-07-31-web-workspace-file-links.i18n.yaml": "sha256:6f652e5e6299b382fbddb26789451ddaa6d372bb8bd839bbf74c6cdebd82a7d5", + "feature/2026-07-31-web-workspace-file-links.md": "sha256:32c660b6b3cffda3866626c2e442c07c009831b2f9baa0769b8c35628a91ca9f", + "feature/2026-07-31-web-workspace-file-links.zh.md": "sha256:04f6a1ede2ad8e347b6a9e6592c7a97c77838768e73d67e09f0cd41d1c2d989c", "feature/2026-07-31-workspace-write-surface-default.i18n.yaml": "sha256:ca2131ba0c95b7fd2f3472c9f0acebf1a54bbc0b494deef2438e62e45715d4e6", "feature/2026-07-31-workspace-write-surface-default.md": "sha256:b96292c03ea7f7f0dcb6d2f9f7921706020881d86e71f73c9209b414071c56e1", "feature/2026-07-31-workspace-write-surface-default.zh.md": "sha256:53c5212239f3ca8d5f975b6aaa4c127b8ccf5de3943ff4f54aedf89129e619b3", @@ -1165,6 +1573,9 @@ "feature/2026-08-01-windows-pwsh-default.i18n.yaml": "sha256:7dbec4f48f94f96af3c957f24345880a6c09eff699d0e7e506b5ee72744cd505", "feature/2026-08-01-windows-pwsh-default.md": "sha256:7a00686bd786c14422fe1b89f79a26ea5869d34767220946d1092f32218dc16f", "feature/2026-08-01-windows-pwsh-default.zh.md": "sha256:fe6d285f7fcd1ae6f238d7256daf4bfb164f9cbe568610177e38e153f760478e", + "feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml": "sha256:e577b7308b3e074cbe6bc0074d398e51e6bcbfbf18c785cb0e04f3d984418080", + "feature/2026-08-02-pwsh-tool-bash-parity.md": "sha256:ad4edd7556eb0097b6d7a7e15e13697045d760822b10a19e8db311c3cded6a70", + "feature/2026-08-02-pwsh-tool-bash-parity.zh.md": "sha256:99c59e955f7ccd0b6c61d27a43fe9db20203047192fdb3ccffdeab24af6166dc", "feature/2026-08-02-session-search-not-shipped-default.i18n.yaml": "sha256:ca274275b57975561a30c65cade508f57fde1a7fb38c8c5e0527f99200f5078d", "feature/2026-08-02-session-search-not-shipped-default.md": "sha256:4a7c6cd9fbe156fb5bab839bf065ba7241bb72ee3a959886e8b265156dae7138", "feature/2026-08-02-session-search-not-shipped-default.zh.md": "sha256:fb258724d7b43eff65f1176bc74a922b3158f32d1ca48236b2d59ed7fbec7986", @@ -1183,6 +1594,9 @@ "feature/2026-08-03-web-turn-run-time.i18n.yaml": "sha256:aa0ed89476908932ee7f95c95be67c49751f595c604afe107eaa8f7a857842c7", "feature/2026-08-03-web-turn-run-time.md": "sha256:5274b10edd0d73f90ddc3720e0b69fe15ca6abe8b4c88bb661c73d6e312d8761", "feature/2026-08-03-web-turn-run-time.zh.md": "sha256:e9e6664177adf24f6815882dd4aa4076f83dca8a4f3bc333f56f18446d2f8304", + "feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml": "sha256:caf44f5765a7846751f587e2d77aa2f187a910b8ca60adb2245b598ca1fa567e", + "feature/2026-08-04-claude-code-and-codex-subagent-backends.md": "sha256:e5bae4e1374a4040bb8b59eb3ca9f019330b77cae6cf8105bdf1c8c5bdac06eb", + "feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md": "sha256:f67ebe95a92c649766674ed6a52e2ba32e8469338d69a0375d297c5715a0052f", "feature/2026-08-04-pointer-revealed-sidebar-scrollbars.i18n.yaml": "sha256:36d632e657acbcf966d7124984b8da9ea9a42e922a47b73fe2ed4f4c071382b4", "feature/2026-08-04-pointer-revealed-sidebar-scrollbars.md": "sha256:ff5bbf8b485074dd72bb86e2f3c5a2a779e8156a46cc06cc8d79b0fea57142b2", "feature/2026-08-04-pointer-revealed-sidebar-scrollbars.zh.md": "sha256:ace1304545f6f282107530bf3bd7b05b376bd5347237899c57d61aca1b125797", @@ -1201,6 +1615,9 @@ "feature/2026-08-05-composer-context-meter-breakdown.i18n.yaml": "sha256:fb90a9e9f27dec45a5a7a18d577f3678104ca7fbd14a4eb33ca721e84ce7e27c", "feature/2026-08-05-composer-context-meter-breakdown.md": "sha256:1fc3dc02e8fb0d07b6d866f825f80d38b3e414d58de60d14df7ca6c849ce317a", "feature/2026-08-05-composer-context-meter-breakdown.zh.md": "sha256:098ad3c97be6a9887ff3dfbf77f42fd253b42a10df96d041c3e3993bc1ca166f", + "feature/2026-08-05-durable-web-schedule.i18n.yaml": "sha256:d5888ebd538ca291bb4a2052e4aec7306135e92f469e87fefb640a508385a517", + "feature/2026-08-05-durable-web-schedule.md": "sha256:891859d93b1328af74e98bf61671c8ae4e00d3102f190745665818a898ed971d", + "feature/2026-08-05-durable-web-schedule.zh.md": "sha256:1c67b2d6b8eb049fcd3221c6d490f5c34ebd68fc2516ea7228fbcdfd3ae28f45", "feature/2026-08-05-feedback-gated-session-telemetry.i18n.yaml": "sha256:e357cae2bb409f3360a61227b470b3c53d32a748604cef57593486dc3603d625", "feature/2026-08-05-feedback-gated-session-telemetry.md": "sha256:91a5e0c59c134e573133bc09e2d15d0d69bb5e2c42b90970deab42059c204238", "feature/2026-08-05-feedback-gated-session-telemetry.zh.md": "sha256:27b1bcaf901e9284a2cc3e12958a785fee53c6ba179b6e7bc92db51c9d406bf5", @@ -1219,6 +1636,12 @@ "feature/2026-08-06-bundled-dsh-badge-skill.i18n.yaml": "sha256:4b568d89976a71b7b3864e13b36925bf055479213739ffd4ac81614e00e93e36", "feature/2026-08-06-bundled-dsh-badge-skill.md": "sha256:7b67f7c09b7e2b2ca756983a8951dad3a15786b8cd3adc6e819f316c67d31b2c", "feature/2026-08-06-bundled-dsh-badge-skill.zh.md": "sha256:dcc0acb2dca596196ac034a8e86a644131c5b7fb09b184ec9d8493f93019d2b1", + "feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml": "sha256:dd2d1b1d841f7675930856433b4eeebc2fd4b9a942d19703f691441b7fc885f4", + "feature/2026-08-06-continuable-subagent-interrupt.md": "sha256:f9f7ae452658bea9c3cb14bf307ab1595b302c0b48b2ccd823a7bc97d4e63dc7", + "feature/2026-08-06-continuable-subagent-interrupt.zh.md": "sha256:daa0c77d7b5e2c5eea9444cf4ee1f60d41bbbfd472902b58b018334235dcb129", + "feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml": "sha256:e628bf5160096fa745f6dcbef2dc402e8d5437a8f98446c961691473a23754ee", + "feature/2026-08-06-manager-owned-subagent-settlement-delivery.md": "sha256:bca40d28325d5c7f459a4d0b2a89c3cd25fbe3131765feac1fbb8c3b769920a0", + "feature/2026-08-06-manager-owned-subagent-settlement-delivery.zh.md": "sha256:d95a79675a8cc564184f2952b4d52c5c3b7ee9a80bfc7b0a41fb14ad94950b24", "feature/2026-08-06-mcp-client-auto-reconnect.i18n.yaml": "sha256:63f0156346ae66d402c4494deac585021b980ab3baf9263222e0ce49a7ca04db", "feature/2026-08-06-mcp-client-auto-reconnect.md": "sha256:b70436035ef9f320e345a17f91e6083d50dc87c523b08114fa98ba1ee5d58372", "feature/2026-08-06-mcp-client-auto-reconnect.zh.md": "sha256:2de73b49d485d514e44d4be85c4faecf797c2c407e2d87cdb1399ef6e21e770d", @@ -1258,6 +1681,9 @@ "feature/2026-08-08-user-explicit-skill-invocation.i18n.yaml": "sha256:cb5c01f4571155956c549c755c73a94edc5869c6a3a5d248c1cbadda4014880e", "feature/2026-08-08-user-explicit-skill-invocation.md": "sha256:a5a38d76a74a820625b32f75868cb05def4a80c371f991876d0a9499aebb3c2e", "feature/2026-08-08-user-explicit-skill-invocation.zh.md": "sha256:1c725a82fd47d39169182bb5c8ef29de4bbf7b1c069c455b9dfe423bacf9956c", + "feature/2026-08-08-web-background-job-display.i18n.yaml": "sha256:272fd6ebb1724b93901bb11e06e1faf021f5e8c2fa773f3dd6f7201ab7180c55", + "feature/2026-08-08-web-background-job-display.md": "sha256:794ac7f72a2dcfe5afaa96325c3b79172ee8d44d706bff57078f7a61715a5344", + "feature/2026-08-08-web-background-job-display.zh.md": "sha256:994531cdc2da1e53460d239e76a40a0bfdee0af94805815812362da62e13deea", "feature/2026-08-09-parallel-subagent-delegations.i18n.yaml": "sha256:d86ffd44b2d3e45018ffd63a3dcbbdd9001c9f9a2658b327d2bdff08662e0c36", "feature/2026-08-09-parallel-subagent-delegations.md": "sha256:0397fe0685f41a4b898557efacbbff5b8562d6d94088aeae83ade6a2aaa92cb8", "feature/2026-08-09-parallel-subagent-delegations.zh.md": "sha256:dab8373f0f8f38e70cd34a64e24a8fcdf77600e1da2d1b201bcbc36d1359094d", @@ -1273,6 +1699,9 @@ "feature/2026-08-10-minimal-read-image-tool.i18n.yaml": "sha256:2311b6453b90a1f81d4fcbdaa723ab233cc0998f34393956364c8b92bc4d1213", "feature/2026-08-10-minimal-read-image-tool.md": "sha256:debe6b570d11634f57a80eaa5891e4fd1e7a5aac19f10fb7dd1683c316515150", "feature/2026-08-10-minimal-read-image-tool.zh.md": "sha256:572d080340682b354cb1f8a0f74af153d6542bd3dffbe3b72b32591e5d5d3baf", + "feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml": "sha256:7bb8db9a9c146e86eb3182fa6d0dcc6328976abee33f21c6d8abec566f1d6087", + "feature/2026-08-10-subagent-approval-pinned-never.md": "sha256:65eee3490cd260e1ce33f3a28cbce94c0bb38d4d35a0e588578755a0118e1967", + "feature/2026-08-10-subagent-approval-pinned-never.zh.md": "sha256:ce3852e312d351c6fdaa6f3f9faa24fdcc659e6cc163bcf7842cb40eba5836b1", "feature/2026-08-10-telemetry-default-off.i18n.yaml": "sha256:4d49b6ad0199c9128bb0393ccef75b2b46eab15a16d5117417fdd49c6e8e0252", "feature/2026-08-10-telemetry-default-off.md": "sha256:def8531ebf8cc88f07bb7169f949321132bc702ffbb0458acfacaefd5abe5674", "feature/2026-08-10-telemetry-default-off.zh.md": "sha256:0b6b85c858897e7e01aa0c54112a4a1644e548b584561c1d8b9e879dadf2c738", @@ -1285,12 +1714,21 @@ "feature/2026-08-11-background-first-continuable-delegation.i18n.yaml": "sha256:0cb5636ca94f5793503c1769ff5353fc8bc944e3060269e80349ef1144d3228e", "feature/2026-08-11-background-first-continuable-delegation.md": "sha256:6c7877c9ecda5c1591ff58da46094b6fef006a79c76bead4a2fe2b1c6d70a3d1", "feature/2026-08-11-background-first-continuable-delegation.zh.md": "sha256:991b14b8d40fc06f1beb98185734824f8ec0664452722886a10d37d26dc25fd5", + "feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml": "sha256:bed76033f6957eaec6606d27de115fd8dade36674ba99a207061fd0d3d4532a8", + "feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md": "sha256:7f7179e17a1653023d4eede1b72343df4bbeb4a99a8bced54908fef76ff4b4f7", + "feature/2026-08-11-background-job-completion-wakes-an-idle-owner.zh.md": "sha256:7a4f4ee16eb6a3b8ff61415b3cf8d396c24e0d378e352ab182e27111340dd953", "feature/2026-08-11-collapsible-ask-user-question-card.i18n.yaml": "sha256:9c0873bbb1437bcd5025f5859e1dc447a6b936f19c2c2ad2250521a3aa773a12", "feature/2026-08-11-collapsible-ask-user-question-card.md": "sha256:4f3b3f5d7020fefbbac8a3c36a97642721ef14a0476a7d8117bde8a128d25f42", "feature/2026-08-11-collapsible-ask-user-question-card.zh.md": "sha256:e7186c92f77d337a875f981ae39b16331a1c5466a948415bcacfe108ad98fb63", + "feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml": "sha256:3064fdb56c343b1cf82c59793a48d2905f175f65070f4fa4f20725e6aff60c96", + "feature/2026-08-11-deepseek-request-user-id-header.md": "sha256:61b79747f5735a210806bfc63563c367407a8bab890b2ff897079cb0e30aa2aa", + "feature/2026-08-11-deepseek-request-user-id-header.zh.md": "sha256:77ca75f537432cc15533668cdd0aa245519e2660146a9f487f6417e95945d812", "feature/2026-08-11-message-feedback-web-surface.i18n.yaml": "sha256:4dc65040403c97e6d7d7768877ece068a19ee39678a3734d070a3601a8c61885", "feature/2026-08-11-message-feedback-web-surface.md": "sha256:891b718b8d4c7410c783a4799985021db40f4121d367f668ed14bb74babf9be3", "feature/2026-08-11-message-feedback-web-surface.zh.md": "sha256:3620be1a57cbd41823b1ffb2fdd836c9cb8cc96f14fc6afb206fc52d0a7d0fb4", + "feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml": "sha256:7a65a90d5ee78a53dd50bdd6b2c337b173e578775e0f438dc400e18e9a7219b9", + "feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md": "sha256:cc14db8c9528d4330cfabe4602c61f9ff84f4df652650b928b84acc7f3a1da93", + "feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.zh.md": "sha256:aab3af21b5e12166fc0d0d8c168136d2f2e2c36d576af291cd7e747cda870655", "feature/2026-08-11-web-attachment-display-alignment.i18n.yaml": "sha256:e0259ebc08e1cd6ffa91bf7d89ddb96fcdccf3adbd370d3fa1b0b083ae57b752", "feature/2026-08-11-web-attachment-display-alignment.md": "sha256:977554ff2ce926580fa1ae0bc5c2e50ca8e29f6a5902d98c43b1f286e2661d6b", "feature/2026-08-11-web-attachment-display-alignment.zh.md": "sha256:72f8548f910e2e58830e6d0ffa6e76a4bf4389426479534ab172a8c87a24b974", @@ -1327,9 +1765,15 @@ "feature/2026-08-17-web-system-prompt-opaque-body.i18n.yaml": "sha256:f571ff4ff529a71a6220795df3fb27006ecb41f11bbe2adcbf2176a2fb5dae34", "feature/2026-08-17-web-system-prompt-opaque-body.md": "sha256:93bc0347dd1e0c9ab5d4222c67afeaadbe18375654718a8302b7a5738233af1f", "feature/2026-08-17-web-system-prompt-opaque-body.zh.md": "sha256:6dcd226723afc5c3559b1ff4db6ea2f3544aa5ecb85105cbc752c8f3afa3f2b6", + "feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml": "sha256:8593d0712e2c3b2f08d243336d88c45545780582251eaac66ec95296f45adbcb", + "feature/2026-08-18-arbitrary-seq-session-fork.md": "sha256:e691b277a4661c3065de0b708fc333469f6fea47716b7c68cefe37d6291ddda0", + "feature/2026-08-18-arbitrary-seq-session-fork.zh.md": "sha256:50589978102b44075528112a2a7bba734a1aad34beceea07fa5acb49c97f2d55", "feature/2026-08-18-conversation-adaptive-content-width.i18n.yaml": "sha256:145f22377e231439665b83751c7e4dd924b0197414bebda188b88f533e5bb47e", "feature/2026-08-18-conversation-adaptive-content-width.md": "sha256:0644f856ac475d43a9bf28c8091e3bd20a95ec13a6261f366af95cbf43e5cdb5", "feature/2026-08-18-conversation-adaptive-content-width.zh.md": "sha256:28246084689dac867ea7b1b2b75f2f38a8406ac97786fa993f9c9b2925b0e6a5", + "feature/2026-08-18-model-selected-subagent-routes.i18n.yaml": "sha256:b84074905a49054fd68fe78df2ff4533488452e98957a96a5d4c414a1b2ac38d", + "feature/2026-08-18-model-selected-subagent-routes.md": "sha256:be595e8bf4ddade5ccaaba4378fa415b6f804cf54a8e224113a6b4274e220154", + "feature/2026-08-18-model-selected-subagent-routes.zh.md": "sha256:42cb10a62de8984c8512c2bcd1784a275a3fe4a0495945a771202afbb04fed93", "feature/2026-08-18-pi-ai-wire-compat-surface.i18n.yaml": "sha256:b0b0c921bdc335aeee3dd86a68fd0a73b1958d8a245fb77947c28083a5c3daca", "feature/2026-08-18-pi-ai-wire-compat-surface.md": "sha256:964d578d20deca2d3a9e6e49fe9cea4d554ddc901cefa7f823558a9d7d91dc4b", "feature/2026-08-18-pi-ai-wire-compat-surface.zh.md": "sha256:55c946465f8c58254c4c13401206990f79111ef4f2a067863f31e0157e8dd63a", @@ -1357,6 +1801,9 @@ "feature/2026-08-20-tool-card-image-results.i18n.yaml": "sha256:a0f4307de3bf37a582817f1ff052c1ee599c7bee95db8647867508a3729eac09", "feature/2026-08-20-tool-card-image-results.md": "sha256:f29668d5b53284ef5042624f499b9e90ed7680f01c5e1a2e15842453075cf08c", "feature/2026-08-20-tool-card-image-results.zh.md": "sha256:15867fbaacfc01a485104e8790236b647b8550d7f65dbbd349a0b1c9554731e8", + "feature/2026-08-20-unified-image-request-pipeline.i18n.yaml": "sha256:fde2716644c06ee8c147b8548986db28f32e430fde2d1a96f74d3d8dba5eef3e", + "feature/2026-08-20-unified-image-request-pipeline.md": "sha256:096e73eb304d3b137764b6fff341171bc945a39f9125b04f928a58680c73bcf8", + "feature/2026-08-20-unified-image-request-pipeline.zh.md": "sha256:577784fe77ed0f55d9c9847a019eff05407d636a686f93b6a8ee3186d1f356da", "feature/2026-08-20-web-streaming-fence-highlight.i18n.yaml": "sha256:ab35f0675214e80dd6566bbb3f452c9eb43e70bc56bbf01030728176bdb2e075", "feature/2026-08-20-web-streaming-fence-highlight.md": "sha256:4c672a677bd70f89141d3ac9668274b71ba83be105c2db8bb8c6dbe3123c2af1", "feature/2026-08-20-web-streaming-fence-highlight.zh.md": "sha256:236abfcb846a34bf9b869e553f51e354ced7262cdfc5c0cb3595573d956beaae", @@ -1369,6 +1816,15 @@ "feature/2026-08-21-out-of-process-subagent-minimal-diagnostics.i18n.yaml": "sha256:dbe9c81bb02ed6d5dd9a9fd41630502908fa841babf9fc7db108682757a081cc", "feature/2026-08-21-out-of-process-subagent-minimal-diagnostics.md": "sha256:4689c6eac9083908e7c3f8c818c318598c3776ddf8438d49468e7400c73dc4c8", "feature/2026-08-21-out-of-process-subagent-minimal-diagnostics.zh.md": "sha256:257089b0def9fc143fd015e4450e440b993ea57826fe41af4df2b68f63548d1d", + "feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml": "sha256:a9b85d41d4a7684d2756233011ba1d094b9eca609ee75d6f45ce14db18e996d9", + "feature/2026-08-22-fire-and-forget-webhook-sessions.md": "sha256:260f72c6b5217263f6d253703e33e4f0963064013c7c5e55ad3c4ccdcef2fba5", + "feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md": "sha256:4f2e1142534adf97b8bd6707de3669ce055b63f3ee1b0361389d7cde16943f79", + "feature/2026-08-22-standard-acp-automation-controls.i18n.yaml": "sha256:94b39e0462afcdf2f98f249ad4254d66cd34575b4f0b45ba9391f2149e8bc172", + "feature/2026-08-22-standard-acp-automation-controls.md": "sha256:5c0741f7d0df7941facfd7c9a7165c2243231631109cd5cd124c9e758ef5316e", + "feature/2026-08-22-standard-acp-automation-controls.zh.md": "sha256:425c37a943a3e9e754c0c9d7cccf68db12f03d37d3acd639823afb5a31dc3999", + "feature/2026-08-24-activity-observation-seam.i18n.yaml": "sha256:c7af2927abc1e8b3d116722ae6db7970899934279f3e8934aeb109273415bf9a", + "feature/2026-08-24-activity-observation-seam.md": "sha256:aec28d01de9a61d10e99461f2dec0d35659605548d2090957e8c07a832cd3a92", + "feature/2026-08-24-activity-observation-seam.zh.md": "sha256:d9a4c723b5e388a9728cf7ea88d8a98c8ea400a6e718ac0e4a9aa38c4b7ebd46", "feature/2026-08-24-folder-reference-pick-vs-drill.i18n.yaml": "sha256:94b1bbf6a536318a41051757b07767876cbafbf63c419664c9a220a7426f608e", "feature/2026-08-24-folder-reference-pick-vs-drill.md": "sha256:0677902082634f1dfc536d3530b17dae1a555ca223f63875a1850d587720d74d", "feature/2026-08-24-folder-reference-pick-vs-drill.zh.md": "sha256:071791690d272af7a220c63469e29412aef582a5028ac9aed307f1ef0b507705", @@ -1381,15 +1837,30 @@ "feature/2026-08-24-web-per-turn-token-usage.i18n.yaml": "sha256:a7795b217a41945da095c214f66775d9af0c051db655bf6f35502c5f5232de39", "feature/2026-08-24-web-per-turn-token-usage.md": "sha256:6ed076816d928dc5510621e7ffbafec35b6a94721609ad4faa01cd5ec6cee732", "feature/2026-08-24-web-per-turn-token-usage.zh.md": "sha256:e14b827943a8901c188a74aab1d14624a134942fd61a216446ff3e72b2e5ab2e", + "feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml": "sha256:24d1e844ebc2f1d299fdddd734b323e80410d33ea15b832a9be2a654583e534f", + "feature/2026-08-25-feedback-gated-telemetry-default.md": "sha256:ab08d9d27dc243997bd49b4b70c5691fa48ea50ea10ebe8ac7640ac896dab4af", + "feature/2026-08-25-feedback-gated-telemetry-default.zh.md": "sha256:8dbff071744c6a3e89a1b0667b8c8501ac6663cccb13de049942dd094d95e8a4", "feature/2026-08-25-loaded-turn-chat-navigation.i18n.yaml": "sha256:86bd02d56cf95ac465d0f6e326ccf75432ffb9c3db47373ae6a795195cff9ea7", "feature/2026-08-25-loaded-turn-chat-navigation.md": "sha256:0ee1cb6da76b93e467d747aa8ba07697ba648cb213ba52202c51ee1e85dff1a8", "feature/2026-08-25-loaded-turn-chat-navigation.zh.md": "sha256:5d3e567027fc65241856bf9f9cf0d73a20d53214c49bfbc884ab2ddd19503918", + "feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml": "sha256:a7c8b4ff0a01018682f0d4bac62ef8e07a6574f07470c3659775a35c92650a74", + "feature/2026-08-25-promote-open-anywhere-plugin.md": "sha256:fb3187ef75a17cead8d80b2e1fd047a273b1859c162665172aa28beeddaf118c", + "feature/2026-08-25-promote-open-anywhere-plugin.zh.md": "sha256:380beaad77040356f5e80e48a466a77b99cb36003991f33f39028c2c59126ab2", "feature/2026-08-25-unified-task-list.i18n.yaml": "sha256:fec0d4a3ecf59aafd440bc801c686b14d63152c937f51bebe0da61ff615f6f33", "feature/2026-08-25-unified-task-list.md": "sha256:30bd4e338a3de2f59af98d512c7878928c61846e6700d6012fc45e51c0bf94df", "feature/2026-08-25-unified-task-list.zh.md": "sha256:88fce0a52b5387ea98c1f68742c03b2f475c5465c680befb32c587066e52cfce", + "feature/2026-08-26-human-job-kill.i18n.yaml": "sha256:18041cc6a7922829c03e85b93c9a00f273ecaf8d41222d219334aa7bba469b0d", + "feature/2026-08-26-human-job-kill.md": "sha256:e5bd32590d0346f2a53982f5038d4c3faa76e8c44f557b14958d52da481de6be", + "feature/2026-08-26-human-job-kill.zh.md": "sha256:34bc94d8177bc225404cbcee677e729732370a0003aaaaf3ce838d229e415abb", + "feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml": "sha256:d52fd4bf02d0e666d5f0ca94ad6f08e25c306b6568999218799fef9cbb173953", + "feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md": "sha256:01c7923819f65166f00a33ceb73203a10882304a0855beea8597b22c12913d7a", + "feature/2026-08-26-shell-execute-projection-and-jobs-at-start.zh.md": "sha256:7711d814135cf1b8365bc62d0f66f0d7c78f9013d6b8332f5077fad4804b1da3", "feature/2026-08-26-web-trigger-menu-presentation-polish.i18n.yaml": "sha256:69179f3056c6d5fc35119e83dd7a5af30213502dac3c1f2d4fd43eae96ee2ecf", "feature/2026-08-26-web-trigger-menu-presentation-polish.md": "sha256:c54e3bbe528d6be711541c61e777e6e4ed4fbc1374df97d846b015d3693679f4", "feature/2026-08-26-web-trigger-menu-presentation-polish.zh.md": "sha256:7db49018eebbba539838d734a80fbb330e2b73fea3b343900591a3605c6c03b5", + "feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml": "sha256:03b234430788465fcbbe39097f50dbf976bf29e04310e7681908c5171390ae90", + "feature/2026-08-27-continuable-subagent-human-inbox-control.md": "sha256:99ff61580f921e68eb6cfecffc21f6ad01507a2fc8ada21b1ba85cf2237397f6", + "feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md": "sha256:f4a73e449207a9779335dbc71ee8a26e1f1088d06bb69dfbdfced012cd1a7933", "feature/2026-08-27-web-at-mention-discovery-and-row-content.i18n.yaml": "sha256:f67e6607f99da020ce7994c35163ade3260d9058c3f77ea68119c13962375ee3", "feature/2026-08-27-web-at-mention-discovery-and-row-content.md": "sha256:e25c5cfb9ad194a68e4db958a80006ebc18a2bfd1c567beeb255e8700e61d80c", "feature/2026-08-27-web-at-mention-discovery-and-row-content.zh.md": "sha256:0fc7079645f0a8bff5048f2709650c325eca7d34109e33807ca4ed770b931dfb", @@ -1411,18 +1882,162 @@ "feature/2026-09-01-web-superellipse-corner-smoothing.i18n.yaml": "sha256:50afdbe5b5e19889918af6d86ab3218c05205be35938b6d33d158c60777e3b58", "feature/2026-09-01-web-superellipse-corner-smoothing.md": "sha256:b1445101c49e74bbcb4f607af850cd6df105d4034828d0dd47081e8079148f15", "feature/2026-09-01-web-superellipse-corner-smoothing.zh.md": "sha256:1a278c417c0d7de3b4c3c35061b419303b4a1a0707831c283d8f862ab9b6fd23", + "feature/2026-09-01-workflow-run-in-background.i18n.yaml": "sha256:e5f90b518b4a83123c5f0d79daa7ec78702250222b59e59ef43ac99ee02cdb79", + "feature/2026-09-01-workflow-run-in-background.md": "sha256:01af9fe9133860dd430b5f3432da49903ea463fe63c77b26fb04256b1e396390", + "feature/2026-09-01-workflow-run-in-background.zh.md": "sha256:d8033094bd02008c39057d0f17b5aa518082894515a4607fa840732581fe5873", + "feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml": "sha256:2b4f9a415582511cb3435058d23dbddf16d5db56d782452cecf3eb9fe23375d4", + "feature/2026-09-05-sidebar-text-preview-and-file-tree.md": "sha256:fd89d15aa9cc34f1df29175bc4a693060b3d9669b14d970499cf77f9737948dd", + "feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md": "sha256:a6db6afb7b958f19b8cd87cd02777427115a4d1805ffb4ca7e32378ea86ef16a", + "feature/2026-09-07-model-switch-notice.i18n.yaml": "sha256:8e34098ff4a64c700a459de4ad92fab10bdf25949e5f22da7307fcf4f3bfed1b", + "feature/2026-09-07-model-switch-notice.md": "sha256:08ad2adf67e8cc6bbe2cae886114116431ef3f0ced1105735d8ad3de3609a369", + "feature/2026-09-07-model-switch-notice.zh.md": "sha256:e94d9081b71b77f3e61fd2d079819fcf2626c29af42b3b3911d2e8fb6aafb9af", + "feature/2026-09-07-public-web-deployments.i18n.yaml": "sha256:661f49adce78e5effaf974c35df0318cc748f18db26270dc66671897359e9155", + "feature/2026-09-07-public-web-deployments.md": "sha256:43235c1f03263aa6fef293f8c9e9029cbcc9047e6608248a484b3de78f51dc22", + "feature/2026-09-07-public-web-deployments.zh.md": "sha256:75bb3cd11985587af977f317ea9d84bd3ff11f86178d8ccb771cd20ef9d031b4", + "feature/2026-09-07-session-prose-local-media-display.i18n.yaml": "sha256:112021d0496fa6b93ef48a48bb8194b53d487d317c372dabf35877ccde553111", + "feature/2026-09-07-session-prose-local-media-display.md": "sha256:a699a317847439d1767abe99cf9df3b5305716aa2d254e36c071fbd2819c7621", + "feature/2026-09-07-session-prose-local-media-display.zh.md": "sha256:1eb2a592ff2bfc8ae7a2a26e55e551d0e113e0ee776068aedc7fabc9dc1bbeba", + "feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml": "sha256:9134e03075092df67648657d3408ec1298c92143ef8642e5cbf9d02a497ea918", + "feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md": "sha256:2f7275adc1fece4d1f7fbabf10cf9f9e4649459270babbc27701fbec0731e416", + "feature/2026-09-08-desktop-uninstall-preserve-dsh-home.zh.md": "sha256:f5cd28777c3f3cb1ea492c79110136e28a5266e2ecb0df4b78629dc05d1a853d", + "feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml": "sha256:e61596b8fb87c3d987f216fd7cc9ee2e3ad050fd7bc4e4a2392b5fc2545bc113", + "feature/2026-09-08-feedback-dialog-and-categories.md": "sha256:96138861c734efa4d413b59392872f03b885684e01c0918c684ef81994a36d14", + "feature/2026-09-08-feedback-dialog-and-categories.zh.md": "sha256:d37573f5fffb18e2a78c0e43e31940ef18d92cfa34fcc0c746daa6f77769b0e4", + "feature/2026-09-08-file-display-through-filesystem.i18n.yaml": "sha256:8140cd7da2e09400925c2d59e0d693a54a5bafc010f1c3fb805753b6c1777ece", + "feature/2026-09-08-file-display-through-filesystem.md": "sha256:314c2b702d09125726e58f82c132fa6b0cb0667586e5427851e0f98b69035e26", + "feature/2026-09-08-file-display-through-filesystem.zh.md": "sha256:c6c37f76702dd85039980f9c33d8ffd66dd7f94d9385a2026b74c68a4950e22e", "feature/2026-09-08-web-explicit-file-delivery.i18n.yaml": "sha256:99daae539cc8fd7376ce0265538bee21e1e33f3c0d77c8cc4e011f94b4e9568a", "feature/2026-09-08-web-explicit-file-delivery.md": "sha256:bb416b1e8be081e6cb6af17792eb1a442172ff114c3a3577e5ef06a77eb57093", "feature/2026-09-08-web-explicit-file-delivery.zh.md": "sha256:00a642380e1f6ac9d5cd840e021f4e3e4ae68a289cb6344eb3dcd73a6fd81f4d", + "feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml": "sha256:95b740c9e6a0df3f049497922a59b89560717f15c1cad9a309847a967913cea5", + "feature/2026-09-09-headless-machine-readable-run-surface.md": "sha256:f01efb97c83ecbff18ba2b9b8f596b73b7abfc97a60ea127ef2c898e90a69509", + "feature/2026-09-09-headless-machine-readable-run-surface.zh.md": "sha256:e77e0dbbaade572897a695aab0263cdd1b0b32523664b935e8ab561a16efee92", + "feature/2026-09-09-present-filesystem-access.i18n.yaml": "sha256:f1af87dd81c4ed600781551c95e44d43a682b1ef74ee9c562a8f44e2e4b7fb69", + "feature/2026-09-09-present-filesystem-access.md": "sha256:6082d5e98202548349de4ef207cfd23d7062ddf974d9e4d475b2fe0cee983f77", + "feature/2026-09-09-present-filesystem-access.zh.md": "sha256:9cb3f3a296400fe068d5240708467f52af8c3b8451029adb963f3ab9e96bfedc", + "feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml": "sha256:cd78d5a13aa4dd2d3f362cfe835e95b7807342d33d472329707d6d44b284f23e", + "feature/2026-09-09-ptc-trajectory-code-inspection.md": "sha256:eed99f370093d6e74da1e8305a4ba360197c8cc99507160b0dd826f293b3fc7e", + "feature/2026-09-09-ptc-trajectory-code-inspection.zh.md": "sha256:47b9b697d6e2f9998ce2de13c3cb1275aae90df4f08842b53f94b0e77ac524a2", + "feature/2026-09-10-composer-reference-previews.i18n.yaml": "sha256:bea1a56fe5f17e9b17d2c86df29795f1edae9515e8e0f7e42f9e0e76a2d47781", + "feature/2026-09-10-composer-reference-previews.md": "sha256:5e906383feeb74eec869142eb6bcdd7e609dd9b725196bc8cbd28419a51d0fcc", + "feature/2026-09-10-composer-reference-previews.zh.md": "sha256:3c59ee73ce23ce2f78d7469768a60edfb82d0ded9ff1ec67b8cf09ea420a666f", + "feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml": "sha256:f4a32935b406c3b95fd6ffa3dbf58eb97e30cb777b41f465977afb31870d43b2", + "feature/2026-09-10-symmetric-message-feedback-submission.md": "sha256:4d45f477e2156d2960cc20148dbbb23881a3eb4b88a80007cc677a6e714d0f76", + "feature/2026-09-10-symmetric-message-feedback-submission.zh.md": "sha256:fcdee10706a36bc0a93ce8305a5904e0f31d3bd0e9a8b4377d593665f29fe165", + "feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml": "sha256:59210d82843b2d0d595a01841468258fb919ff59a71a588bec5cc868e85943ad", + "feature/2026-09-11-desktop-mandatory-update-client.md": "sha256:2de5a6353d3859b67f87d6b83dbf01d3b2c281a590f7a3c559f3cf0e1b1977e4", + "feature/2026-09-11-desktop-mandatory-update-client.zh.md": "sha256:cd9963b8887b5fdfbf9f3b8216b7a4dd3d4f492d488fd533dc136c935f4424aa", + "feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml": "sha256:c2d220b173e9241786dbb10a4539cfbab66b649e221c523b54f9c710bae77f20", + "feature/2026-09-11-sidebar-document-preview-polish.md": "sha256:b3d40ec827efc0a14f0c8422a4d4d61daf175455f4220457d56c2013c95e059e", + "feature/2026-09-11-sidebar-document-preview-polish.zh.md": "sha256:8f42f7464747d6950dfa1aa9cb2c639b6c00bbc88f7a98cf9f3f72166c5bed8f", + "feature/2026-09-11-turn-changed-files-card.i18n.yaml": "sha256:d47ef4502c689e8bce34a8ccc364dcf25da75e5658a04580102d200373884653", + "feature/2026-09-11-turn-changed-files-card.md": "sha256:f72bf15af0e755018e049ed171a17eb874de4654fb5da5c3df8ee6dfde123cf8", + "feature/2026-09-11-turn-changed-files-card.zh.md": "sha256:4514f2acc3e8a31efba53faccf271afc379a8787f5b75d1187c74c78c8638446", + "feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml": "sha256:7fdef40f42bf71c18f318ad01cdb3aef49931b73a134f72d9f7afb9457c7c57d", + "feature/2026-09-12-mcp-resources-and-instructions.md": "sha256:01d894a58399c97447129854e56c98b8d9fc4445828a603bde0eeb7388bee090", + "feature/2026-09-12-mcp-resources-and-instructions.zh.md": "sha256:fe37fa9930e0525eca849463418bc308daa4760b963a7dda0f1525a8b0a0c0f8", + "feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml": "sha256:9b105b6ec3fd32668fde36ed66922047c127ed5c5a3c018054879cbeafd74170", + "feature/2026-09-12-mcp-sdk-protocol-negotiation.md": "sha256:1684f5f510aa16e18b5a31728a833a1c659ed73472b372ba506cc0c2ad3f8e87", + "feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md": "sha256:df270f54782e04e799296adc7422741ef1368c8a1af6eebde860713dc3516233", + "feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml": "sha256:b0a819d5609a6da9bae4c0688f5c017a4038b11eb0a7894c08f8a5b9395c5400", + "feature/2026-09-13-macos-hidden-titlebar-vibrancy.md": "sha256:218fb7f1ec8ab9b89e8c9833bad2ec298d21f395b7b774f9027f3bf3ee3e9cd4", + "feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md": "sha256:4d6497cbb37013571e5106b5b6d527a256853c2a65aacbec655c0bc36a001421", + "feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml": "sha256:2f4ee5ae876eae013367a9064418060712579c6f3d588c3cc87f2b7877c2b1e6", + "feature/2026-09-13-mcp-resources-in-profiles.md": "sha256:a2065c7c76b480e55baaffa99fda629f8be3dee6b6c27339a9cb5605a34175c2", + "feature/2026-09-13-mcp-resources-in-profiles.zh.md": "sha256:7ce6ebd2605ced93c7dbf41d9194b929da505afc2b837b0646393f0da2a0c183", + "feature/2026-09-14-composer-selection-keyboard.i18n.yaml": "sha256:a74286188640b4d972236a48f8e63a2f3e6bb86956a11bc863404e7b97a80ef9", + "feature/2026-09-14-composer-selection-keyboard.md": "sha256:12dd9d98ef24c1aecb8a5d9b3a33560b0ad9ba8ce6b54009a28279fb21cc8b93", + "feature/2026-09-14-composer-selection-keyboard.zh.md": "sha256:60b836c4312112b1025b454f71ad1f1f3461e317146be72169401f140d337304", + "feature/2026-09-14-docs-mermaid-viewer.i18n.yaml": "sha256:6998620aad52a0ac9e97f19bd60849b98f80ee1383ab9d01ecf749ac00f1b99d", + "feature/2026-09-14-docs-mermaid-viewer.md": "sha256:631726774b109d7ba785e315f9fa8f3c1a00275841e5cd0e05b8c1ce0fc2524c", + "feature/2026-09-14-docs-mermaid-viewer.zh.md": "sha256:a0d86f58c02241d76b0bf55246257d48e327cb4d86337858b3f66784245d7e2b", + "feature/2026-09-15-bundled-office-skills.i18n.yaml": "sha256:62c22569b76bf277204d4b65e0d31ba52dc2f7be6328479260a4dc2d8b9446bc", + "feature/2026-09-15-bundled-office-skills.md": "sha256:03f06db94e64dabbbebef7809125716cf7432fb2e591951382f0555d56b248af", + "feature/2026-09-15-bundled-office-skills.zh.md": "sha256:d068c9ad97f5e5acdd43fa79df6070ebd958e992e942d7a839bc4cf322ffcf7e", + "feature/2026-09-15-changed-file-diff-preview.i18n.yaml": "sha256:2c081d604cdbe7b6f10524fab53f2cd441e3ad95acbf60376c2b99d454e23ffc", + "feature/2026-09-15-changed-file-diff-preview.md": "sha256:f33c69856a1388d16eadf844a45b0b7a2ada494f9808c90f3dbac19da6d2ea0b", + "feature/2026-09-15-changed-file-diff-preview.zh.md": "sha256:34ef8b3f3dda685a762f5c369046ce0d75508ec72636fb753a9de0ea8c09d4df", + "feature/2026-09-15-continuable-activation-capacity.i18n.yaml": "sha256:8e6efac1e651494d95b79d3fe19f951b00c0cc93d5fa9ac2f61f297b54fc9320", + "feature/2026-09-15-continuable-activation-capacity.md": "sha256:f8aa7fa33e6e041303bd2b10bc02a818a1959febc40a7e9bff1778896eea65df", + "feature/2026-09-15-continuable-activation-capacity.zh.md": "sha256:bdcf5f79cbb60a6b4fb18b8a374d480eaa4ea5a1efb8b29ae702354b68c12221", + "feature/2026-09-15-docs-page-markdown-actions.i18n.yaml": "sha256:d8d4c14d9695f4fe6594241e745d2c234097b5f7ac14c9e7ab8eab146bd84919", + "feature/2026-09-15-docs-page-markdown-actions.md": "sha256:8603dec264c063bc7230c06b25f94648c1e58e457f19ba43c305ffbefa2d386a", + "feature/2026-09-15-docs-page-markdown-actions.zh.md": "sha256:887c75edde0bc72ab7a9f90246a6e58957fc8d1e89aec809dd58d21167218894", + "feature/2026-09-15-markdown-file-preview-links.i18n.yaml": "sha256:66ed21095a5bef11c9494a860563c76179a21e8d46f2f91c62f881c621220570", + "feature/2026-09-15-markdown-file-preview-links.md": "sha256:359bc0b6a9857a007d0a777116b382b4a37beb8d30e83f8027c216fbdb188a4e", + "feature/2026-09-15-markdown-file-preview-links.zh.md": "sha256:481173c2ef3c89c897c10ff9a04c7985f37864ac85c24ec19136952673515d57", + "feature/2026-09-15-profile-command-shorthand.i18n.yaml": "sha256:4d6828e6ad004f6a2a04ecc0880db463935b1b25da72291bf3f1e687076c713b", + "feature/2026-09-15-profile-command-shorthand.md": "sha256:3a63eec420feaeeffeff08f8f9a196370fe5ece03fec9ef32409b08aaf50fe4a", + "feature/2026-09-15-profile-command-shorthand.zh.md": "sha256:9854d560eac99464a950ce75ec38e9d673e7d14202c4988a8386f38cd0d22ffb", + "feature/2026-09-16-desktop-onboarding.i18n.yaml": "sha256:3d1a4e885976542ed0ced2211fa7469315fab3be7b7f0939bff84b6da3a4ccc9", + "feature/2026-09-16-desktop-onboarding.md": "sha256:43cbdb6f3c76528bbaa557e3f073bbe838534962b11c92fbb4c984c5c6efdef2", + "feature/2026-09-16-desktop-onboarding.zh.md": "sha256:93bdce12c1e633a779ed2bed04f98f8663e2963ee60c4c7ac6a96f4cb51812fe", + "feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml": "sha256:7598f4913d0680b7e741922d7f916a54563ccd2d88c19e3c417db839bde434d7", + "feature/2026-09-16-open-in-default-app-for-sidebar-files.md": "sha256:dd7dc092057fcf14fe13e2466c43b340e5e3313b690432cf34d1368ac79e8792", + "feature/2026-09-16-open-in-default-app-for-sidebar-files.zh.md": "sha256:78bae90e71ed96538674f3922c9099c2e7737e2bb7fbf3e53408fcf857b45957", + "feature/2026-09-16-sandbox-same-mode.i18n.yaml": "sha256:f577daf8d010700f5d710fbeece704e9e47ed23c5e059f8475b7a9a52ed49de4", + "feature/2026-09-16-sandbox-same-mode.md": "sha256:79dc06f0eba002ea63ae45fb0db94d9290705acd7eddd4949ca7edb08f4b1f7a", + "feature/2026-09-16-sandbox-same-mode.zh.md": "sha256:d35ebc9332ee4359719476cf5c3cbe6287472b25f64420b03a0b93ad627074b9", + "feature/2026-09-16-unified-model-input-controls.i18n.yaml": "sha256:461a590e6b48cd47ff08ade47fe16516ae376c2ec2bc840bbecbbd3e7dbeefea", + "feature/2026-09-16-unified-model-input-controls.md": "sha256:efb160355e049dfe01577184eb31089509089e59029cffcf867d3ac6a742ef5c", + "feature/2026-09-16-unified-model-input-controls.zh.md": "sha256:01d2ae7f3ab6ad04b10d7e45776d803a5297e460082db15b11d49cbaec763a55", + "feature/2026-09-16-windows-desktop-titlebar.i18n.yaml": "sha256:44ad967a12e038e211d7fb003067e3ab4de32f0d2f3e0c87cc737fd91dd3e89a", + "feature/2026-09-16-windows-desktop-titlebar.md": "sha256:fc89b10a01cf60604a5673e13568ad83c004015fd4c68258224fd76bc51dbd21", + "feature/2026-09-16-windows-desktop-titlebar.zh.md": "sha256:6819d877344fd663007d60dc2b9ef1ae2df3b775e372cff1bf0eb57673ffa6ae", + "feature/2026-09-17-developer-tools-settings.i18n.yaml": "sha256:460e27d604ae4591b90ec42e0cbdb837c64167e98922846ca409147207831130", + "feature/2026-09-17-developer-tools-settings.md": "sha256:d28584d6e48ebb46ecbfbe33335fc3925b97304a21168eb515ccd80a68998411", + "feature/2026-09-17-developer-tools-settings.zh.md": "sha256:3c6f8821d3fad6a64f2137dfd0b9eabdbbab73e980d3326ac162443cf5acaf1b", + "feature/2026-09-17-persistent-plan-cards.i18n.yaml": "sha256:94c0ed809ed33d42db0bb3e3d4d199675ce1e9fd6a64aaccb48d2a8dd894309d", + "feature/2026-09-17-persistent-plan-cards.md": "sha256:73aa4c21f9c7354a70bed9297ec29f13f7741d2dbe80dde48ed4c415ce0093c6", + "feature/2026-09-17-persistent-plan-cards.zh.md": "sha256:0c203310e3252b1daf74191919a0d397e21833dd7e91fb84e45f3c59cfbcabdd", + "feature/2026-09-18-account-quota-top-up.i18n.yaml": "sha256:229eb2558f18ff3a74e2121191508278d5e54839357041ce802c2f1c5b73658e", + "feature/2026-09-18-account-quota-top-up.md": "sha256:ab05f80ceddb7623aa858b73bb080904ef8d8fa9dcd9668c4250c5f8dd6a81fd", + "feature/2026-09-18-account-quota-top-up.zh.md": "sha256:087ae3ebb29b0c35f8b6034b95a6959b247db9e4731230fe24bdfeed82a469eb", + "feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml": "sha256:a31526750048ff52a2d6d766eacd69b3ae197060a95dfe80f86731754e2750b4", + "feature/2026-09-18-session-pin-and-sidebar-archive.md": "sha256:aa0cfb0433bf9c1c7aa9c7fe12a174a7e40853962a51797be2ce05a88416a372", + "feature/2026-09-18-session-pin-and-sidebar-archive.zh.md": "sha256:6fc2cc4b379d4f190521ad07016b6fbb80bb6049f16bc141c76cd27b30db2017", + "feature/2026-09-20-default-workspace.i18n.yaml": "sha256:32aba99894da919541ac5c623279b7e064c5953f349618dd2647781adbe6c454", + "feature/2026-09-20-default-workspace.md": "sha256:5edd5b5817304a129e60e5f51b2b881dfc23b0c3fefc8ff9e99eba942e272f21", + "feature/2026-09-20-default-workspace.zh.md": "sha256:3fe991f3a9cc8e776b087860d3c88310764a3610b2860b6f4e7f7e097f240759", + "feature/2026-09-20-developer-tools-default-on.i18n.yaml": "sha256:376367023d1c1f4be03a47e877155da0ce66c73f0decb76a3596f8d93f801d8e", + "feature/2026-09-20-developer-tools-default-on.md": "sha256:3e68fd94eb938710995c0a4969f9759c4efa008535c4ddd3be669b36fbe5a891", + "feature/2026-09-20-developer-tools-default-on.zh.md": "sha256:10282ef866c7b15b964acacb712e21decf939c8ae366a7335fa8ed6c172b1031", + "feature/2026-09-20-focus-ring-brand-primary.i18n.yaml": "sha256:0e554bd3a08cf3001bbf3cef9b22c772789fff9d2fa53d7e6ec29f06bca091ea", + "feature/2026-09-20-focus-ring-brand-primary.md": "sha256:5ecd6dbe750157072d05458ed686db99616c7240676a1a7e99b0e6fc97962385", + "feature/2026-09-20-focus-ring-brand-primary.zh.md": "sha256:de317ffbc065ea8493677190ac7bd49b5348c271d84b942f3670308f776f68f1", + "feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml": "sha256:580c46a3a07b3fa1ac2b6e697981aab12acef098386f2c7b10b8d8b5de433b22", + "feature/2026-09-22-unbounded-completion-wakes-by-default.md": "sha256:743ce3d187a79bd25d421ffd6905efa431b479c97cb195f71d62a1ea96e84b3e", + "feature/2026-09-22-unbounded-completion-wakes-by-default.zh.md": "sha256:79b72786a0ca10c00d1a01b61218481121a49ff966ed6e2f9aee52b15a263f2d", + "feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml": "sha256:9cfac88e040171968bc9984722c3b5c14924752dd42063ddf9cfc121a864afb7", + "feature/2026-09-23-language-neutral-default-workspace-naming.md": "sha256:2beed7185f99545c274e8e42040b36e65a02955ee925a0ee97917ab1962bbbbe", + "feature/2026-09-23-language-neutral-default-workspace-naming.zh.md": "sha256:81e19236ddeb23b4d27c533ce7f9ecf61ed3b7cf32aa5ef9c75fbf071bffbd41", + "feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml": "sha256:1731d7423796d12c701e96db989a68ef8e6249b46d40d4f0654d9b0d6f9f7b2f", + "feature/2026-09-24-fixed-rate-floor-one-minute.md": "sha256:03a711602feb0c0e73fdd8da3d7823654169e4129f8ac667676c0f65a4ae9b5e", + "feature/2026-09-24-fixed-rate-floor-one-minute.zh.md": "sha256:0e4361fffbec14adc621629646aeacf35924b6856587226dc7de25fed6f110ab", + "feature/2026-09-27-desktop-cli-runtime.i18n.yaml": "sha256:b9dbdfbcc54777e784d8813b76f25286a3ff58116fbd9a5417c8ed6994866c0a", + "feature/2026-09-27-desktop-cli-runtime.md": "sha256:82df96d366be2ba35d01b19b9cd483ca489a9c940083f9298e48be64b1190d4f", + "feature/2026-09-27-desktop-cli-runtime.zh.md": "sha256:8e23c5fe07468a16d5a50d7e66aec9ba273dd2a3d3535e1eace7d94732d31f23", + "feature/2026-09-28-desktop-login-shell-environment.i18n.yaml": "sha256:6129edeb0a7ecf087911193ca5a15223a730ad01e30d2ead7739d01f589fafa7", + "feature/2026-09-28-desktop-login-shell-environment.md": "sha256:cd133b1ef1a178828d30b9db176a91597eedf9e67802551356f34d3e0f80206d", + "feature/2026-09-28-desktop-login-shell-environment.zh.md": "sha256:1422667a44aacc51736f31b328a8e171a39cb6b5168d6993e980e4324b23b98f", "process/2026-06-11-doc-sync-enforcement.i18n.yaml": "sha256:33b6d5874427bd7a2bd82e7e2f4f482b12448b2464aef15a9c57975edb48554d", "process/2026-06-11-doc-sync-enforcement.md": "sha256:aa2fe83d519fc30d48dff19e596e83c8922aacc9e063e14fe2cc35b769b9100e", "process/2026-06-11-doc-sync-enforcement.zh.md": "sha256:698017bd35f030fdea3eac51df9e43138c48140f504739d687b7251d13fced2b", + "process/2026-06-11-quality-gates.i18n.yaml": "sha256:2819e83235b02b58afcdc03e4543509a343052b6bbf1e57c2999124d6348170a", + "process/2026-06-11-quality-gates.md": "sha256:94bfd4add22e5c9e1c140317570d1cfe5d877dca5c0ec2c0ef7e36883e997907", + "process/2026-06-11-quality-gates.zh.md": "sha256:1becd113d173b1db3a4c1ec4367232b0b91f0881e8718c2d1ec9012f835b3e2b", "process/2026-06-11-tsdown-over-dumble.i18n.yaml": "sha256:22791adb84a4b6c545173d4f1708eea51151d57e426d875e0e5423be9b6e0212", "process/2026-06-11-tsdown-over-dumble.md": "sha256:8d3c35dddd8869cc3361059dfe4b7b8ab6716d29dda232c97c2f37e92c841dc0", "process/2026-06-11-tsdown-over-dumble.zh.md": "sha256:cf11c651c13f5ffef5474e7795006ba3653c5eb08eae75a879be6499353455dc", "process/2026-06-11-vendor-cordis-as-source.i18n.yaml": "sha256:b02b3c0469de87acd3abbe7352d50da208b0e049752543b1ada0140890218d9b", "process/2026-06-11-vendor-cordis-as-source.md": "sha256:e1e9b70e9439eef26d68428b0829e1d07b6dcecd5601a05754189770586f8a20", "process/2026-06-11-vendor-cordis-as-source.zh.md": "sha256:ade0a6196e0b0188a2ef19217b32f980d7e129f6252c5c10115e5582d4c4c32f", + "process/2026-06-16-pnpm-over-yarn.i18n.yaml": "sha256:a28f429debb0d61009189887ee1bf4493ff1d769e28ba48c521b39a54a03aee5", + "process/2026-06-16-pnpm-over-yarn.md": "sha256:4c557c353d3483fc42c0ab8694b45b1cd76961b3ff9036de78cd7bfe06ab7d29", + "process/2026-06-16-pnpm-over-yarn.zh.md": "sha256:32010231566de2a33511dc2c75a67677ea161dd73354013c71542c6e0e20daae", + "process/2026-06-17-ts-build-config.i18n.yaml": "sha256:16b9ff92757b590b506d476df2f9a7f095a3e6b87bd1ad82b532ed912bf1f1b2", + "process/2026-06-17-ts-build-config.md": "sha256:26e8447b6a1172573e7c09563687a0a4ba2c24e58403efb23c1372b3b13e595b", + "process/2026-06-17-ts-build-config.zh.md": "sha256:71ba73e43869de8bfab11c232405205f27eef8289aeaad9d385e95b0718dd124", "process/2026-06-18-markdown-cross-link-lint.i18n.yaml": "sha256:8d3e9fc65db2d4cee347a077923c664f661821026fef837c4eda923baad765dc", "process/2026-06-18-markdown-cross-link-lint.md": "sha256:cdbe7ba8995f7430c7e2cef59ab7bb3a61992147408fae41c5dcffe4e60a2261", "process/2026-06-18-markdown-cross-link-lint.zh.md": "sha256:6bc822d10549cc687dad42da379bfca56b5140fbee6d7787a206fe39a82aad7f", @@ -1435,6 +2050,9 @@ "process/2026-06-20-generated-cordis-catalog.i18n.yaml": "sha256:5250aaec698b25bdf5e3a02f67e531793fc968f9c38348f0c8bd11418571967f", "process/2026-06-20-generated-cordis-catalog.md": "sha256:1f3190b759bf1445b35f25f2f18ac1c8b16d7f2e9263e2627bf3c94fac54d275", "process/2026-06-20-generated-cordis-catalog.zh.md": "sha256:5d46da71bd73bba62ba15a9f11b21da422dbecc8ed68b6f530ceb302e1935ddb", + "process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml": "sha256:af1e17658781f40dca00bb692935484789b2173d47e75da9716e3c7970227857", + "process/2026-07-02-bilingual-docs-and-pairing-gate.md": "sha256:1a3b1248e71c04c8805a1332dcf68f31f90a7b43d1533bc3f1a90dde45a0a67c", + "process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md": "sha256:194a1589e957e6f62a04fb6af54a0dccac4d3cedec817749083d1e9ca4a5ae9a", "process/2026-07-02-tool-schema-catalog.i18n.yaml": "sha256:6a6dcd025c320c7fe50d99982716c6724ba8ccfda7170f9541fb70d53da2e008", "process/2026-07-02-tool-schema-catalog.md": "sha256:43536908e3698bf75e77e204a9c72425b2289a67c18a3b905986351832334d5b", "process/2026-07-02-tool-schema-catalog.zh.md": "sha256:de2da2ec2828e769d4dd282bf7c16437562a9155111726a981842e58891aaf53", @@ -1444,9 +2062,15 @@ "process/2026-07-04-cordis-jsdoc-completeness-gate.i18n.yaml": "sha256:92ab8aca8856324acb383927d4500d2f2b3b8ad64d24bd855632035a52671032", "process/2026-07-04-cordis-jsdoc-completeness-gate.md": "sha256:a9404acbfeaa83f032d17414627b2cce5e280cad8651c6ff078a2ea6af458285", "process/2026-07-04-cordis-jsdoc-completeness-gate.zh.md": "sha256:e8f4737ecc0925e3dcd60f8319d9cd0fd24de5c7468e1ede4438748d3ad378a1", + "process/2026-07-04-doc-tiers-and-budgets.i18n.yaml": "sha256:c8d25e4a580aa150a3d6621321d7362c7a8987a579edfe217bd5190496c6a4fc", + "process/2026-07-04-doc-tiers-and-budgets.md": "sha256:58b0f425701d9fa59f9cd28783bd4301b8f294be2710fc1837475d36e406edf3", + "process/2026-07-04-doc-tiers-and-budgets.zh.md": "sha256:968c3023664f46484ace8a76b642dd30babd7def0a383c03b5000e3415358313", "process/2026-07-04-persistence-log-catalog.i18n.yaml": "sha256:23119a0769808279d7caef9e84934cc53b6a86580b703c205fa47ad181a2bf76", "process/2026-07-04-persistence-log-catalog.md": "sha256:fbb44218b77fd92f7b0cc7af2559200547018ceb5c34aca560371227c8bf5898", "process/2026-07-04-persistence-log-catalog.zh.md": "sha256:a39ee31bec043e97ad6b7e8433d4c1d24200642049e371ac0167a2092a6e5286", + "process/2026-07-05-uniform-agent-note-format.i18n.yaml": "sha256:10b8510da2112acecb6fe89197ba573a5b2128b92455d8134f7e5a5ff8fad038", + "process/2026-07-05-uniform-agent-note-format.md": "sha256:55fd64ea57270e44bfc8aed830f4be63835684a5b603d9f82b9f10eab38d711a", + "process/2026-07-05-uniform-agent-note-format.zh.md": "sha256:437912260ffc6bd6d00dcb60a8a4f5c6279cc87f58fc2ff2b69e674a91db1975", "process/2026-07-06-export-jsdoc-gate.i18n.yaml": "sha256:e96b33ffe7ca4eb891e4757ad415334a02bda0a1659dc0e7db8d39c83ab54c27", "process/2026-07-06-export-jsdoc-gate.md": "sha256:a772ca941c1e3dc29820cb8287bda1d44a7a4ec51dba71513101f5ff8b329819", "process/2026-07-06-export-jsdoc-gate.zh.md": "sha256:223ad583f4b165fc48f5fa2764f971bf194c4fe92be0a7361b37f6bc87e71198", @@ -1456,9 +2080,15 @@ "process/2026-07-06-parallel-github-ci-gates.i18n.yaml": "sha256:0f6ece268d9a51bc20cb8eb929f26d8838761603a64eb08dc24521198f10da36", "process/2026-07-06-parallel-github-ci-gates.md": "sha256:6249bd7396ae7f2d0dc671879ce21cefab33a47ace6ef17a25a70e8650b815af", "process/2026-07-06-parallel-github-ci-gates.zh.md": "sha256:cf7edb9bcf97ab1d4e452330c0df0b127a664509ec3f11597ace3eabeb663a5b", + "process/2026-07-06-parallel-pre-push-gates.i18n.yaml": "sha256:388013a92962c3a4b8367c8b08ddd32c6000c21c1268d1ee6736c95f436e7241", + "process/2026-07-06-parallel-pre-push-gates.md": "sha256:83b7e2eecdf48e339027bcce438acd04bf59b46c6847915db22d56c7df7db533", + "process/2026-07-06-parallel-pre-push-gates.zh.md": "sha256:85b7afad1a6dfd678b74276db7600c5e291fd410bf449727ca119053a78418b2", "process/2026-07-10-readme-known-limitations-gate.i18n.yaml": "sha256:57635340ba3a9d1a0d20e0c6222fb153488dd3f198e5d67fc2cf107645d537b4", "process/2026-07-10-readme-known-limitations-gate.md": "sha256:ffb5969f1a4e9196d18510cda3905bcf6ca26509fd0106c0bc6e7dac63806b0d", "process/2026-07-10-readme-known-limitations-gate.zh.md": "sha256:9b7f9fa5b1b171f7961487e303eca55b7e50f9a558cd91942991376d1d77f57c", + "process/2026-07-12-package-model-experience-contract.i18n.yaml": "sha256:ce3051cdab39a0a91d838fbb605b29bafa842ae14ff44ba7308d4537cf73c9bb", + "process/2026-07-12-package-model-experience-contract.md": "sha256:1f902dd4820252e50a9175169816f7acd80b8cae6f89ee8bf8601517c0d469ca", + "process/2026-07-12-package-model-experience-contract.zh.md": "sha256:f646e0acaf8dc947a0631d4e6e1b520d8e311db976ec8ab329e70de4a4899dd4", "process/2026-07-13-documentation-site-projection.i18n.yaml": "sha256:3711a572c20d906bfe7d6ddfe6edfc2fbb2a6bb4a46c65ab98e0c68d248977fa", "process/2026-07-13-documentation-site-projection.md": "sha256:638e257222fa5d21d33b2da3450159edd6fbe60b98a405aef79a6df42dcdb12b", "process/2026-07-13-documentation-site-projection.zh.md": "sha256:d51155d83bbc95ba490b632498bbd57fbde021636537d6d8bb8768b4a33a1742", @@ -1468,12 +2098,21 @@ "process/2026-07-17-run-ci-examples-from-built-lib.i18n.yaml": "sha256:0a2e97095be592e2c603f23d9b3b8959c4556b5636fa2c6e4539be18cfcc7a6c", "process/2026-07-17-run-ci-examples-from-built-lib.md": "sha256:d7e8901c67e9850b05cef65132f193b8be566be40d0f54b28b11043871955263", "process/2026-07-17-run-ci-examples-from-built-lib.zh.md": "sha256:9bb720b65a0d5933b5804dfbe0104c831a26695687b7f1a72f4ed4cfb4bd4415", + "process/2026-07-19-remove-generated-agent-note-index.i18n.yaml": "sha256:7799c3c7cea8ba50ebcef10d2d18b8efc0ee5f3998bb8735f2ab71b206814332", + "process/2026-07-19-remove-generated-agent-note-index.md": "sha256:aa606ddb01f8aedc1d1bf01548cf28f01d941f83e8a89d0639bd326a233e9c70", + "process/2026-07-19-remove-generated-agent-note-index.zh.md": "sha256:abc0b441702d3b830869904985f1c5d38ddba710752d9bd3b8d3f48507c1c141", "process/2026-07-19-require-agent-notes-for-non-trivial-changes.i18n.yaml": "sha256:3f02660aa672f62e03f29c23878c1f4809be37994e96074e649556326a273956", "process/2026-07-19-require-agent-notes-for-non-trivial-changes.md": "sha256:b10a2b7fb24daacb1e0eb41c926686c5ce5ebc7afb6c3fccf872a0318c4f9dbd", "process/2026-07-19-require-agent-notes-for-non-trivial-changes.zh.md": "sha256:71b7d8aec3c6c75a17d41f910db405ad63cbea03fd4db74f40851adbd0ca67b3", + "process/2026-07-19-web-styling-system.i18n.yaml": "sha256:121bd48abc9a1b676969781a65f96a30e7bd60346a9722367119183e38a2f0bf", + "process/2026-07-19-web-styling-system.md": "sha256:e704d9a131222d5feb10fe5d4147c21828adec9c8d6f8b6308c75bac46a57eba", + "process/2026-07-19-web-styling-system.zh.md": "sha256:b72b08a9e592dda92108c18a7f6264fae95b666fe69ad080744ddd9d57f57d45", "process/2026-07-20-generated-cordis-core-api.i18n.yaml": "sha256:4405f216d4beefdbb33f521dc8126be4f8d3a664abfed3a4cdad04fef84ab0f9", "process/2026-07-20-generated-cordis-core-api.md": "sha256:eed4dde36984f584c211bd00fa88ce4aef9ee296a0814b46a8d759f953a90660", "process/2026-07-20-generated-cordis-core-api.zh.md": "sha256:74f4316abc78500097f1b25bac06b460cb20a7dd3d5f323da875cc0575148991", + "process/2026-07-20-gui-testing-system.i18n.yaml": "sha256:5d52062a38abc8c286c604000eb26d7fa678ac0643223c39028762d7e7f4fe36", + "process/2026-07-20-gui-testing-system.md": "sha256:729a52af7c2809c5b5615fb040f2753a214ecbba16005517d6bcdd02102c5ae3", + "process/2026-07-20-gui-testing-system.zh.md": "sha256:44687b48e81513ff83bf423a37bf5902b9fdc15169a1b7243c9d536959ccdfb4", "process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml": "sha256:1dbe70d21dd510bec4f2f56ae39d0fdc7290d5648280ca0b67224cd23b3a02a8", "process/2026-07-21-doc-sync-through-gate-scheduler.md": "sha256:b3eb3f2395ad8f1b77f44aa3fdac79856e5d0b6b4873560d0cc87b63de2ea2e0", "process/2026-07-21-doc-sync-through-gate-scheduler.zh.md": "sha256:e262e02c3d08057b83b0d29281eadb92723f0fe5b3f54424528f47be137bc760", @@ -1549,6 +2188,9 @@ "process/2026-07-31-installer-adopts-existing-checkout.i18n.yaml": "sha256:e7d072856cd3df3b717d94647be4ea05db087b114df4b11d68a171677b94c970", "process/2026-07-31-installer-adopts-existing-checkout.md": "sha256:d97e39d07bde30d534417a406fc6c56ee6765dbc7b671888d17ef2cac9a7c6dc", "process/2026-07-31-installer-adopts-existing-checkout.zh.md": "sha256:ab445493c1aacf3f8dc091bdbd765ad56c551ae613e0d8156610f0c4614a7431", + "process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml": "sha256:234f854f218ef169ce13cb73d9769ec4fab6be9014e4e15c8808fd61ba486968", + "process/2026-08-03-package-anchored-subsystem-pages.md": "sha256:0acc25292c75c0ca6e09e15cdc8da67c2e2807dee65151c46b9f98240113b5d6", + "process/2026-08-03-package-anchored-subsystem-pages.zh.md": "sha256:b6142325cd3c3fd4b9c199d1e6583db37df6547bb2f8cd3406e53bc92ea3bde9", "process/2026-08-04-forward-only-pr-issue-status.i18n.yaml": "sha256:af23e203a66a95674154899410e2f420d1d0685dbf856c24cfccdaa547a17925", "process/2026-08-04-forward-only-pr-issue-status.md": "sha256:2d31077da47d95ab3ddf64d5efc6b1b8fb7c7709d39aca4a825ef9e9d382d501", "process/2026-08-04-forward-only-pr-issue-status.zh.md": "sha256:b61f865b7a8a0ac901250a3edbb92ea73177067c4c25448c7088925c2caeccd7", @@ -1567,6 +2209,9 @@ "process/2026-08-08-automatic-translation-pairing-merges.i18n.yaml": "sha256:5398ef0c48258d3f3a9881e2b34fc4e2086a0b8084b8d7100798dbca6cc39887", "process/2026-08-08-automatic-translation-pairing-merges.md": "sha256:fb1f3df3246151da920c14fdf195b0cb3bf595a2eed639c84baf70e3f56d36ce", "process/2026-08-08-automatic-translation-pairing-merges.zh.md": "sha256:9ce7ff6f511ce973637f001b119ce323a5efe0be57cc11d12691aaa36199a8b8", + "process/2026-08-08-browser-gif-evidence-chain.i18n.yaml": "sha256:9d60bc9bf1bd432fcd0bd9d237f7277dd8ab275024a22574eb7f2b8a5dc4255e", + "process/2026-08-08-browser-gif-evidence-chain.md": "sha256:fc6fe17019c2a1d4709282ac8a52daecd779a8f048af9143477fde0c975ae8ef", + "process/2026-08-08-browser-gif-evidence-chain.zh.md": "sha256:d5407676dbc15e47ca3aaac87fc0b85c76bfef2e290bb931629c82259e3aae66", "process/2026-08-08-lightweight-routine-documentation-translation.i18n.yaml": "sha256:f3b44e6148941de9840ef54be60e60ed6ec32ee04b42a1a472e38838b6aa7b14", "process/2026-08-08-lightweight-routine-documentation-translation.md": "sha256:264fe2ee2338053a11fe0f0d5ccf12772897fe9fd4f4a7e8a1d6ba226f853e14", "process/2026-08-08-lightweight-routine-documentation-translation.zh.md": "sha256:3d309e2fc34a6a99bbce4891c32d681e0a23b233791b29a550884f3e753d593c", @@ -1576,6 +2221,12 @@ "process/2026-08-09-chinese-contract-terminology.i18n.yaml": "sha256:25cc7d0b0bb1947931858c0b9bec7d0adb3af247d276da8c8fb8e052b632b1d8", "process/2026-08-09-chinese-contract-terminology.md": "sha256:e32145c8f467d5a43eb93a16af24d4dd45bff84acf682d768b9f49eb347efb15", "process/2026-08-09-chinese-contract-terminology.zh.md": "sha256:12efa9c57002d71745373e9afb2f1bd047888e8b45df6b115719b77bfeaa2dd2", + "process/2026-08-09-committed-artifact-citations.i18n.yaml": "sha256:006f514e9190a3c9db7b8a26bf2166f5606315480910fc0195674f4adc6213ab", + "process/2026-08-09-committed-artifact-citations.md": "sha256:629fc78ac4759a4564f0a8cb64f2a9763903831c83d7b92fa358c0ddbc0b942c", + "process/2026-08-09-committed-artifact-citations.zh.md": "sha256:e73d575e08e998e0ff36f56412363cd8ad8f6b51cf36cb550ef3f5c3da00c10e", + "process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml": "sha256:1da77ff03a2d70467756301f00976eb861067623a3b81462f5b545074419ea0f", + "process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md": "sha256:fcafa32f6f4e0efc76ca700e3bd488422e350d98f6cee7c7309b39fbf33bac7c", + "process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md": "sha256:b86d111e4ad2768920089f4d638cc77ccd584182ea1a67a3f412ea83e6c1ae7f", "process/2026-08-09-md-fragment-anchor-gate.i18n.yaml": "sha256:1fed8e5c83099d128819afcc1f80a3727f0fb69ad939865b21c18c27a7db31d3", "process/2026-08-09-md-fragment-anchor-gate.md": "sha256:60b887fa78d6c0e6176cd906b8b10695a230ae8c5286a93b6f5c7048f3770874", "process/2026-08-09-md-fragment-anchor-gate.zh.md": "sha256:8cb7c156697cc1d3018b20ebc7477479cee6ce51368297fef394db572fbe7e09", @@ -1603,9 +2254,15 @@ "process/2026-08-17-readme-assets-on-cdn.i18n.yaml": "sha256:6127ebccb5ad673f539b6043bd12018182de5eda1ddcf9701e8b3cf8fdc75da4", "process/2026-08-17-readme-assets-on-cdn.md": "sha256:c1c7a47f6eda815e13a497b15ce4226e2e021202f1c9b61c2b7330c5ede9b1bb", "process/2026-08-17-readme-assets-on-cdn.zh.md": "sha256:e55d6d236076755ddfd79cb03d3942b8a083c29d586bf04f1344a6a925c02d88", + "process/2026-08-18-in-job-partitioned-coverage.i18n.yaml": "sha256:30ec278b2e07e8926ad1cc1decdccc2dd86f4be24fe287dd4fbfc371730389e9", + "process/2026-08-18-in-job-partitioned-coverage.md": "sha256:c4926b0f7fcda67af0a96a012525f8dab3778d5893df10a71f3d403b8436c83f", + "process/2026-08-18-in-job-partitioned-coverage.zh.md": "sha256:c1f2fe9dbd4f0ef709a726ae9ae9a039af45923e44e660f00b82570aa87627ff", "process/2026-08-18-localized-bilingual-links.i18n.yaml": "sha256:ae4e71d3893050019de59ab0499e500acc0af59a9a1546efe05cbb77e1a00784", "process/2026-08-18-localized-bilingual-links.md": "sha256:de8afbdc1aa64b51e4df8332b52e87d1622fe3dadb1a5b1cb33b4fd3713eb43a", "process/2026-08-18-localized-bilingual-links.zh.md": "sha256:89eb74665d5a38a87209dfc172696cb530f6152b2511c9cee675f3c649514575", + "process/2026-08-19-remove-knip.i18n.yaml": "sha256:15c9a9686ea225f4e825b79fb1d1687fa47076e8d08e8e3712a15b8ca745b33e", + "process/2026-08-19-remove-knip.md": "sha256:4aaf058ee75062ff9894381cf6af45386dac42b0425141cc840ccf1a0aa54e66", + "process/2026-08-19-remove-knip.zh.md": "sha256:8d0ecefb1876f3a3555b70958f531284c604fc8bcb9d6df4b045cfccee4b94fe", "process/2026-08-20-doc-site-raw-markdown-twins.i18n.yaml": "sha256:de18336460253fa8c3f0729a8961b229c039692aac3f4ec3922ea8f0fb97c7a7", "process/2026-08-20-doc-site-raw-markdown-twins.md": "sha256:a630777a349b1af4a6006aca086ba540ea8613522c3c211b6145c88945303457", "process/2026-08-20-doc-site-raw-markdown-twins.zh.md": "sha256:90b5c927dec2238e06daa03b45eecbb8a10c50288c99816718aa0b41f8bf9ecb", @@ -1621,6 +2278,9 @@ "process/2026-08-27-gate-runner-fail-fast.i18n.yaml": "sha256:b6003a8a7232ae05d4cc5a24d4f93274f6b05e81dd1cdb1b59767e194aeb2897", "process/2026-08-27-gate-runner-fail-fast.md": "sha256:c0445303d6bcedef055c7b87c513c1c81d8ae151a18462f40d7293be928c083c", "process/2026-08-27-gate-runner-fail-fast.zh.md": "sha256:633e8698d7f0aabee210da2d0b6a06da857a60a8bab05943ae6bb993ba6e96cf", + "process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml": "sha256:019b36c00081036e64482b6628c1c5f8fae3b09a133edc4e6e1426e62f2c05d7", + "process/2026-08-28-ci-node-compile-cache-data-disk.md": "sha256:37cba5edef71ed81eb28ee927ebc5b052fef69e81812bad359682017f9cddfd2", + "process/2026-08-28-ci-node-compile-cache-data-disk.zh.md": "sha256:a111923c3ec4891ab815245b919dacf16fb58e7f65d792221ed714e5d1974fe9", "process/2026-08-28-test-temp-dir-self-cleanup.i18n.yaml": "sha256:b801b7be8503f620b78e6231fa756eb73bd7ba9706991b831b7ae6e23b093cdf", "process/2026-08-28-test-temp-dir-self-cleanup.md": "sha256:66e62abf171f1393f0b4cdf4c1d932f6bae0ca660d0948d780e355bb4c1bfe87", "process/2026-08-28-test-temp-dir-self-cleanup.zh.md": "sha256:c6f96a3f429196e96ac98722b42d4f0a77840bc36c9694773bb57fdea7d765ac", @@ -1636,15 +2296,96 @@ "process/2026-08-31-windows-coverage-flaky-test-budgets.i18n.yaml": "sha256:9f79d6164ea4100e5dda7f5fd301ec190ec9f601b4f50f713a6a34055e4f3be0", "process/2026-08-31-windows-coverage-flaky-test-budgets.md": "sha256:f08e98e002781b8b45af0b16c375aabc8ea66195733b4552a3586f9029e6210c", "process/2026-08-31-windows-coverage-flaky-test-budgets.zh.md": "sha256:784d200d253ef9db7559d5d1a444f6b05a798ec9ed66e518b973b24084606f3b", + "process/2026-09-02-project-local-issue-planning-fields.i18n.yaml": "sha256:34160f28bcf28a361a9f4f0b4b6899a3406182519fd672790a0fb511fd6f371b", + "process/2026-09-02-project-local-issue-planning-fields.md": "sha256:04adfabce5e6fa76dd57caa3efa5ad4c2f3d0fa7ba3a29c7d1ef11e3b34b0d18", + "process/2026-09-02-project-local-issue-planning-fields.zh.md": "sha256:6ab779ea76cfb5cf627bcf9a3a683114d502421657c32a98626d8cb3a8b487a9", + "process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml": "sha256:dce48ecf4976e4a97bd7be52a372db4b805610036cc86342c6e00610c9d9f854", + "process/2026-09-03-semantic-issue-templates-and-policy.md": "sha256:4ed1e2ea971b1d9ce17dc5cafafd5bc05b0eafa9fc3a42bd5e64d8d78816271c", + "process/2026-09-03-semantic-issue-templates-and-policy.zh.md": "sha256:a32d39ab4f67feb158dd47c7ea41b45c1b3fa70ec8120fc021fe9426c3f7715f", "process/2026-09-03-workspace-version-coherence-gate.i18n.yaml": "sha256:ac442dee172d396395b8516d6d38c4fcedbc855d735c86c378aec5c1098ecd8d", "process/2026-09-03-workspace-version-coherence-gate.md": "sha256:37b63a78506a8741eb04826dc1eea7e3a64c19fd4494c2d7754054a5602d308b", "process/2026-09-03-workspace-version-coherence-gate.zh.md": "sha256:cfa2c8fe6cf1d4665121a987861b70a91b5b41eb99e7ed192c85760a916fddf6", + "process/2026-09-06-evidence-driven-performance-skill.i18n.yaml": "sha256:07f1f47ddb8d3be3a1722ab557df57c06dff3e8880369cb5a9f35071813f7cb3", + "process/2026-09-06-evidence-driven-performance-skill.md": "sha256:a9cf80ed7eef6522238ae95bc30fb53ff99f6f36d530219da28a57a0cc35e51f", + "process/2026-09-06-evidence-driven-performance-skill.zh.md": "sha256:a29f666ecabfed02db86b1185cd7cedc30b85bd09a35124cb30425e5d1d3af45", + "process/2026-09-06-master-only-platform-ci.i18n.yaml": "sha256:fd1c43f07afae4db3da65fed5d706a5914b11215ed5a786179c65ea29449f158", + "process/2026-09-06-master-only-platform-ci.md": "sha256:dab44d4e1f8f3ada17696eee51d20b7ff2a175e815f02aebc1ed889e9c6aa5f5", + "process/2026-09-06-master-only-platform-ci.zh.md": "sha256:82756ff15a7ed421ec12f832afdffa25791a68fece55054e75c4e813e8a734fc", + "process/2026-09-06-node-compatibility-selfhosted.i18n.yaml": "sha256:ee65edeb6f9894168b715eebf2081e94e76d0043bc49dd0172fd38bb3618eb6a", + "process/2026-09-06-node-compatibility-selfhosted.md": "sha256:999df24ef381dc1787d86333a42c1ed067b8b012c5e6828ab2382f8289283c23", + "process/2026-09-06-node-compatibility-selfhosted.zh.md": "sha256:d24b7169aab5cb895086dfee6bfb4cd5520c33f93007ae59418b56376de9e78f", + "process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml": "sha256:f14fdf81c5526d304a2aae4e6697e47ebf59b206cd7ac51739d6ecbc1ff776ed", + "process/2026-09-06-preview-hosted-runner-sizing.md": "sha256:022c99e4bbfd4c7dc4a1571c4ab496f7247f40a2af01c203c174346ee52b84cb", + "process/2026-09-06-preview-hosted-runner-sizing.zh.md": "sha256:cd9fb42b9427be757de9e142f44b7ba34993ccba3869dd6482995b78c12f7bdc", + "process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml": "sha256:7f70f15f3338f4576e30346606e87d63ce921a6d9ee14f5f6344cc0c4b2aa63d", + "process/2026-09-06-release-rehearsal-selfhosted.md": "sha256:285963db1990a4efff5b172dcc3cabb85fb23b115ba93172dddb6ef410b1d414", + "process/2026-09-06-release-rehearsal-selfhosted.zh.md": "sha256:99b970010871d5d8d93811c48f8d9f2a0f3bd188b79ab0443eba54b43606b501", + "process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml": "sha256:26a439d7094eedfc8a96645986fa7de744794eb59edd6180007dbc94709f5bde", + "process/2026-09-07-selective-issue-policy-evaluation.md": "sha256:85e133a762934b569b3a0688876998b7e8aa16ce94766be7405cdd5afffd95bd", + "process/2026-09-07-selective-issue-policy-evaluation.zh.md": "sha256:1856ad51653d78d96e53589b6a7cad9ab8c58327e07a3e501d2d1d9ab13e9978", + "process/2026-09-08-browser-third-party-build-inputs.i18n.yaml": "sha256:d524e9f1ebada68a7f21e06cd3150ed12cd8cc60ab57c523971b91bb693b4e81", + "process/2026-09-08-browser-third-party-build-inputs.md": "sha256:6f93b90da079c3074b0f6211c18b8ff6b6a11f570fabe4f35295730ddbcd75fb", + "process/2026-09-08-browser-third-party-build-inputs.zh.md": "sha256:6183487cde6f19fcd119f89cb99a30f9ad915899e75c61f7690d716bec9a4ac4", + "process/2026-09-08-package-index-link-discovery.i18n.yaml": "sha256:7aecd400985dbf80d1bcd9d189de9862e804ef46b0aad028b72a4aa8b83d37ae", + "process/2026-09-08-package-index-link-discovery.md": "sha256:9fc9711b4482c973488b09666ea71be39c0c6ee2f5972a97cfdaf12141d4c973", + "process/2026-09-08-package-index-link-discovery.zh.md": "sha256:bd2d989497eefbe13a4b4f5414fec27b0d5dfb7b8d37c96e4026514eac4d03c0", + "process/2026-09-08-playwright-video-gif.i18n.yaml": "sha256:cdd39beaab65698324443c2b7b3c8e1612db045b5b8acecfc1803f9ab9075c0c", + "process/2026-09-08-playwright-video-gif.md": "sha256:529e0d83e8c63d35ac9c67bc98199ed93de85e665edaf55643a34011b3b0e7d9", + "process/2026-09-08-playwright-video-gif.zh.md": "sha256:77cb1728d7888c8ba234267eca43309a50e1ebac1bd0656753951a3eab9d63b1", + "process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml": "sha256:2d50494e8d4a4ccf16e71d103b2746d888abb69105679e5a1898e5a3f925d255", + "process/2026-09-09-blocked-weighted-approvals-remain-pending.md": "sha256:e378410391541f5f6167bc7640fd8a50960cb684f44c19cac15568551fc59dd7", + "process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md": "sha256:7290ce4a0466901c036a752c67c709430549f09beebbb597a74bfdb39b95642e", + "process/2026-09-09-cancel-superseded-ci.i18n.yaml": "sha256:ecde5514794a920182e1d67326ea31af9142d21f7926db4a75026ac2d6f67d40", + "process/2026-09-09-cancel-superseded-ci.md": "sha256:18e5b6eb20474cb595ebf2798fd643a639e008cccd25dfcdf1dc57d84d5e21df", + "process/2026-09-09-cancel-superseded-ci.zh.md": "sha256:e6517e1bd2f4868c58d6092f96bfc886c3b343617c1410db6e68135fae765954", + "process/2026-09-10-approval-review-workflow-identity.i18n.yaml": "sha256:5b8d34b8c89291513b279684320c230ce248c0c215aaece954fa0d43b9b28a2b", + "process/2026-09-10-approval-review-workflow-identity.md": "sha256:92c7ca27186df04807405a252ffd1719329ff00ea5efab5a278e018f80daf172", + "process/2026-09-10-approval-review-workflow-identity.zh.md": "sha256:4a14f2af256af5b0b7538e94f4955c27b50b4275003da4ca3efe9d328302d855", "process/2026-09-11-independent-libreoffice-package.i18n.yaml": "sha256:e8d2fe75e05ac53438513a7c51dab6510bcecf644019391ee6650e73961c0f8d", "process/2026-09-11-independent-libreoffice-package.md": "sha256:1292b409b7b4cbc4420868de5e3b7417b3c855889023ccfd9a61d824bf3019d8", "process/2026-09-11-independent-libreoffice-package.zh.md": "sha256:384783da1614c7e2ea84ea513013af6b20e9d4545a03cf7fa068415078126932", + "process/2026-09-11-production-blame-approval-weight.i18n.yaml": "sha256:2cac8dc664ce516add33f440a523a92e15e606ebf91e702bad5540bc201351c2", + "process/2026-09-11-production-blame-approval-weight.md": "sha256:4686461419672280e26f6a977ecd37a3f11427e8da480637cb88b08313444361", + "process/2026-09-11-production-blame-approval-weight.zh.md": "sha256:5508910fa53e9498b5e71225ef7125adcaafa8d03656d467982c011fa036a554", + "process/2026-09-12-experimental-publication-denylist.i18n.yaml": "sha256:f73c3b09b33222a83631eac7097995e9aa59abb81395618d569adf58336ef6cf", + "process/2026-09-12-experimental-publication-denylist.md": "sha256:eff60a29cb52c0b8d351c47daac870acd6961152be0bdb72e1fa181401cbb482", + "process/2026-09-12-experimental-publication-denylist.zh.md": "sha256:c9ba62885c62ad921ae4c9441d7bcfe145d4b277d89919152c792a3b3df4e5f6", + "process/2026-09-12-maintained-repository-references.i18n.yaml": "sha256:c7d07e9aa14dd326622a3b1db1c14bf2f5c0aabc21177311377fc28765269840", + "process/2026-09-12-maintained-repository-references.md": "sha256:c6d8c0d0a363dfdcabc9686a602f6474c5c90bba6a794640b68cdd05fca3f84b", + "process/2026-09-12-maintained-repository-references.zh.md": "sha256:a114db858aca836b26cd534bafc7866df490f32dcce54019cb513c7f246b058a", + "process/2026-09-12-publish-all-experimental-packages.i18n.yaml": "sha256:1dbf64b7f1581162327681c2562de5913519f8790e27e6502fb365e3617522b3", + "process/2026-09-12-publish-all-experimental-packages.md": "sha256:e092adab7cd930b2122a7b3e6d00a1229e2895c5ca3aacfdce0ca8658d7e5efc", + "process/2026-09-12-publish-all-experimental-packages.zh.md": "sha256:11dcf6542a10ec0742943789ccdb135689e5bc9c66dbee4b9b4caf71771e223f", + "process/2026-09-15-pr-approval-delegation.i18n.yaml": "sha256:1eb0d88236fbf3b396848b93f6c0120d0f7ca9eb56661f430e8dd9e87f22a31f", + "process/2026-09-15-pr-approval-delegation.md": "sha256:f46b4ed1091e39e1651cb937200c4c9bbe7301d9d2b0b80504cc93d0e3cb22c7", + "process/2026-09-15-pr-approval-delegation.zh.md": "sha256:cc5f151063f6d76adaca46d018c5872e5e470a214658e792c6b9a10628649de5", + "process/2026-09-16-author-approval-credit.i18n.yaml": "sha256:153b25430cb9f8d7ec4e78d0c5f3497c72357a613a03df2ad95af7e03cff59fd", + "process/2026-09-16-author-approval-credit.md": "sha256:b825eae5475a0d6538510c4c41bc02c32382e38375bfe03cfc465bda7a1ea66e", + "process/2026-09-16-author-approval-credit.zh.md": "sha256:e1b7ef5152751e6a9693bcf76a966f0c3df5ce8bb0abe1694a9314258458f153", + "process/2026-09-16-desktop-release-version-derivation.i18n.yaml": "sha256:d78b7e7249e7a9554f8fef2ef20fbeb0a362fec009c1f6f2cfb083f3e56c6d8c", + "process/2026-09-16-desktop-release-version-derivation.md": "sha256:4c96e733912cf8336d8d761c18dd5d82e38ad1064538e43c8b5a5ff0aa37c30f", + "process/2026-09-16-desktop-release-version-derivation.zh.md": "sha256:d1e5c4396e4c1333cbd9dbaf9a515ecb78fa20750641b94f92f96d8a2988c3a2", + "process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml": "sha256:d0df5a9d65ea515bf760da995f1287aff7d7eb73fd78651f05541d8465716f94", + "process/2026-09-19-evidence-led-simplification-surveys.md": "sha256:454486b67608f423e61745ec421db49b885f0911423cdb741078b53ba6341ec8", + "process/2026-09-19-evidence-led-simplification-surveys.zh.md": "sha256:b246b894c71d1e18f5c749354400868ddd302b5f47a6395067c951bb9a12eda6", + "process/2026-09-21-desktop-build-version-as-input.i18n.yaml": "sha256:99a9a228425c2ac6bdce1fa2b6bac51a29a1aa0a1339e20b35c08360ed75cd98", + "process/2026-09-21-desktop-build-version-as-input.md": "sha256:8b4640553a98d7b3192a55a676457e790cc73c9b5f88b31d8e1c94c4e10421db", + "process/2026-09-21-desktop-build-version-as-input.zh.md": "sha256:68c74765e736237e9651a8ecb1ae4a37958d1d63cdadbe7dc77aef9c698a1a58", + "process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml": "sha256:ac8374e853140485dcb1f098d393a63a2b989999efe25598749ec3eb205fcdcd", + "process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md": "sha256:7fdfb41c50ea88946c5bd00cf38ed2d0759095f74f3c4b4b5cbd347d975b6deb", + "process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md": "sha256:2cf9a9396ccbe7aa1b5c27923600053078d7ef30a06b5300dcc00b27b1a2a952", + "process/2026-09-22-workspace-release-ranges.i18n.yaml": "sha256:1bc6a5caa47c3b651d1504c0fab46450962bfd1346f8f28dc9e45a29301856a1", + "process/2026-09-22-workspace-release-ranges.md": "sha256:0a6726dc1436be81a1b60ac01ad3e2cccfbbdd7c4c0b29dc4b33255dbeb38dfb", + "process/2026-09-22-workspace-release-ranges.zh.md": "sha256:cba9386e83ccdee54306bb8693ecae8395f40a91f63e43dd89ef6276d7fa13e4", + "process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml": "sha256:ec07cef0e6b9628c81ae62ff772821b7dc659c07bd01a451a561b37a96f423e4", + "process/2026-09-23-section-keyed-translation-pairing-records.md": "sha256:990d5c3c0d3fbcf43327997ece13879119a1e36a9841f7797a8a2a675c932f57", + "process/2026-09-23-section-keyed-translation-pairing-records.zh.md": "sha256:befcb9915ff0020d1153d596ed85b0a09a7cac6541ec7af0ac93ab96dc62263f", "simplification/2026-06-19-drop-mutable-session-summary.i18n.yaml": "sha256:43fe5daadc1491f94a3e34595182cad1591f76b64e1f13b48811b80fd7e53b94", "simplification/2026-06-19-drop-mutable-session-summary.md": "sha256:01647a5a14aa4e195328d4d39c6d80eb0723739e5a543115314716b280a93560", "simplification/2026-06-19-drop-mutable-session-summary.zh.md": "sha256:22389e0c29158b1f7a5a43ed6073f8795bb87cf51c04cf83be055359cd65c9f5", + "simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml": "sha256:85493212298b580429b50c69e88acad8514ad192fbcbb41217d6b39811b9d4fb", + "simplification/2026-06-20-collapse-trace-only-session-events.md": "sha256:331381082aa03bd152de6c1ad9e9ed055cc862f13b8d51bf0f45005a6d87d64b", + "simplification/2026-06-20-collapse-trace-only-session-events.zh.md": "sha256:cb98a71e80e4d320edb2b946cedc8a0f8ea1b332ade9c5a1ad02047dd4e038f2", "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml": "sha256:ad3d1263cb0051b885173bf064de62065e2c646ccaae2d7250723da3b4eab90c", "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md": "sha256:8fb061d51c8c23b47d2367814bab3623c6d5b972f38d207a273caa9030b579bd", "simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md": "sha256:2ffeaca91f82844a5616d6dcce6b4af514bb8a7c46f78e47f668b204ac6edc04", @@ -1660,6 +2401,9 @@ "simplification/2026-06-20-remove-agent-boundary-mirror-events.i18n.yaml": "sha256:449d72fef793fba249462bcc61a87271c17b1c709ce6ac51dc0907e1cc72f4b5", "simplification/2026-06-20-remove-agent-boundary-mirror-events.md": "sha256:a0603180ce487894ffb7f27341b9aa7bd5ce7f0e02c7dc0dc3178ec6ec1423b3", "simplification/2026-06-20-remove-agent-boundary-mirror-events.zh.md": "sha256:c3a58df81e6f94a7a303c36e00a74c30896630a409f0d6bef9a2af6b79a5f435", + "simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml": "sha256:4c6328127093bf5594d8fd369bba0db1be960c4a6b3c2b55d37df9919123fa10", + "simplification/2026-06-26-fsspec-style-fs-seam.md": "sha256:641a854ba4c478ea853ea87fd50128c51088c93308c18efc063571ce338ed205", + "simplification/2026-06-26-fsspec-style-fs-seam.zh.md": "sha256:d0ed1bb865d4183223a8c3e085fe4a5e8c6df1e392dc83b79b89c74087c7d09d", "simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml": "sha256:eef600eafd70a576b2ac16a74f5dd5010ee601376008a90be10f1da56d746cee", "simplification/2026-07-02-remove-stream-chunk-mirror.md": "sha256:0c22a896260c6eb8991cc8babd8172f7b6889fc41bb891d748f34650b3eee5ec", "simplification/2026-07-02-remove-stream-chunk-mirror.zh.md": "sha256:ae7a3c2450b16fdf2f8da9e1f83cd987bf387671ecc8ebb76d2e7541695e7ee1", @@ -1714,18 +2458,27 @@ "simplification/2026-07-20-retire-readline-front-door.i18n.yaml": "sha256:48b8573d325d280b65e7debde660140e7afdf1db793d4eb32c839c635121a965", "simplification/2026-07-20-retire-readline-front-door.md": "sha256:f632cd22fd81cc470992ff4e5f695a118a2aaed53748a860da7e9a78ca99ebbd", "simplification/2026-07-20-retire-readline-front-door.zh.md": "sha256:3612b25120a87f0e1c9d075c9e3c4b6a9449f38c8b219dd1aef506c6e6f92d98", + "simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml": "sha256:653654fb7b8453ac507defd923af0ce647bc653e1469f3e755adfd5f687f1620", + "simplification/2026-07-20-unwrap-injected-content-envelopes.md": "sha256:522c1e620c3c60a081c23951fda87065b36050fe442230eebcb907fe2ed20f1a", + "simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md": "sha256:d3b9bd6afe99da7f946035708aeb1eea309a95b2aaffd464f3d1d65e5b97ff5e", "simplification/2026-07-21-tui-remove-cancel-command.i18n.yaml": "sha256:17ee6e9a3db867b85d8399879c40552a6771b5d7585f7b58e33601428a1309e3", "simplification/2026-07-21-tui-remove-cancel-command.md": "sha256:e90ad809b5ea241a653641f7331893347a1a0be7c677c99cbfc6bba8c907ab19", "simplification/2026-07-21-tui-remove-cancel-command.zh.md": "sha256:94d388753157eb498b9a8dbd9050dc07e5ee893e9b5a07f4c265b2e8e66f6338", "simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml": "sha256:633975e45444f179e5fcd258d3c4bce924975583505fa97f18cff21861a88ca2", "simplification/2026-07-21-tui-todo-write-opt-in.md": "sha256:7c4c0818f5cb5b1a506dabb71a56b7d79b811e4b912d492865f1404f4d1ece99", "simplification/2026-07-21-tui-todo-write-opt-in.zh.md": "sha256:2c121b8ea03182f7854e7d834b07967fdb6790af6a2a38c1d24bb0ca968496ba", + "simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml": "sha256:a6d42441fd22ce93593affbbd1b184639241d8a18d01a9c65e65c0e31b4df2d2", + "simplification/2026-07-22-plan-specific-collaboration-state.md": "sha256:a522f55b2d45ad726a3c9900ba9824089b53885ca655e17c405bde06875ab9f2", + "simplification/2026-07-22-plan-specific-collaboration-state.zh.md": "sha256:30e4e66fd8d3c01ad4fda6c2bb49361135915f053fdbd8965d09c4f230ab9965", "simplification/2026-07-22-tui-titles-from-session-title-service.i18n.yaml": "sha256:b02b34f76599eef44ef6cbacac36345716bed52eafe8f30e5300d654a93eecbb", "simplification/2026-07-22-tui-titles-from-session-title-service.md": "sha256:c84b34c2c4700f78e3060e64f49f16625bab002e8211dcd6fce431e7fd076392", "simplification/2026-07-22-tui-titles-from-session-title-service.zh.md": "sha256:99df66f452004df84efb215e3b58bf73e39af0538ddf038bbed4ac6b65c630ea", "simplification/2026-07-23-collapse-persistence-flush-state.i18n.yaml": "sha256:117b3c55ccb34b83334fa446042c044914953c541a7880183986888a44ac2049", "simplification/2026-07-23-collapse-persistence-flush-state.md": "sha256:f759dba2bb805ac2857cd9239bf4298776228b3413b52765fcf60831be438db7", "simplification/2026-07-23-collapse-persistence-flush-state.zh.md": "sha256:78dea969e20b7f8fa8d3fc70ba7015266ab38def7c8762d1c14f30130c6ac06f", + "simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml": "sha256:4d2cdd1fd62c71bd617da06abef4802f44c49b38f2a571dedb5d4913f2980003", + "simplification/2026-07-24-agent-loop-observable-state-machine.md": "sha256:3b7bfcaffb9b29106f062075b641db566e4bd97467d9f00df37838891296399e", + "simplification/2026-07-24-agent-loop-observable-state-machine.zh.md": "sha256:b4e99f5672a78cf38eec406f6290d885b8895beb94450f50aededacabb55b929", "simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml": "sha256:7acf002ea8c1533f052c7bfc0c4e3da013ecf43c5872866a3ee4a8c2691c5e33", "simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md": "sha256:f18a913096b7defd2192c4bac888a33f68075c3662703a0e28a6146897d17777", "simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md": "sha256:ff48a37673c97059536fe5b61aff746133eac682145550badb049eb5c83b097c", @@ -1744,6 +2497,9 @@ "simplification/2026-07-27-intent-named-subagent-continuation-operations.i18n.yaml": "sha256:29460e03376a7c7f4f9c8f0db330ee29da0db2035e8218b3b6350e1797e43b3c", "simplification/2026-07-27-intent-named-subagent-continuation-operations.md": "sha256:234ec4c6deaf66b434b3dfdfd247b5aa68898fa1fb05faf9f9399fe055a7c8d7", "simplification/2026-07-27-intent-named-subagent-continuation-operations.zh.md": "sha256:dd1e8b92ba7d2990f7f43401bcebf21b8847bd9bb4422cf1722cbf285da6cb05", + "simplification/2026-07-27-request-error-retry-action.i18n.yaml": "sha256:2ec6e24c20c32396f79c75c5fa484b97ef9a77b06a20c0655fd26c4a1cf7e154", + "simplification/2026-07-27-request-error-retry-action.md": "sha256:353fc93ce3f37c191b73f1f6ec56225c1993e8e983af2934fd03050ff4f6d5cf", + "simplification/2026-07-27-request-error-retry-action.zh.md": "sha256:24e835ae54c60bc29eb58e25f491d485c12d396b1f20bd5e4dd5a852f966eef2", "simplification/2026-07-28-local-json-tree-renderer.i18n.yaml": "sha256:52903d45c69f0996f86cd45b9fe9ec6fe3912a0d27bbbf2f126c874ec6ef5710", "simplification/2026-07-28-local-json-tree-renderer.md": "sha256:86f90ba8e6c5963598f276e11a7dc3a621cba42ead5b2d48b8ead6bd1b9fbc47", "simplification/2026-07-28-local-json-tree-renderer.zh.md": "sha256:09f839200b46d328e36bff424db4493a3ce07e7a264941887359cb931874a223", @@ -1849,6 +2605,9 @@ "simplification/2026-08-27-persistence-export-and-pre-release-trims.i18n.yaml": "sha256:1c6779c70d8d327844d69390a449b0c4f0603364666b7d30c93cbeb01899d3ec", "simplification/2026-08-27-persistence-export-and-pre-release-trims.md": "sha256:715ecdf9be8eda840c8ad37e56569d91aeac8128a171cbc783aba898cea240fc", "simplification/2026-08-27-persistence-export-and-pre-release-trims.zh.md": "sha256:244a2225316ad54edbeaeef558cba8a01c68f6f366e22abd5688553c77b71629", + "simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml": "sha256:49a8f569a6b8b04596c846e6ef27b19d5a9a0e6f04bf0559580927966b0747a1", + "simplification/2026-08-28-omit-unneeded-invariant-companions.md": "sha256:d981d9b813f7b317c41cc636e44d6bf0cd79dd71971285db3843ef2f27be47a3", + "simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md": "sha256:c6a18d0be3bf03fcd4febc33d863d3c993dea24e5e2e2b3a93fda4ba646eb5e7", "simplification/2026-08-28-remove-hero-input-glow.i18n.yaml": "sha256:68af9690c3463de8e658dbc14194edb438ebf75c8788bba740332d11b0420819", "simplification/2026-08-28-remove-hero-input-glow.md": "sha256:1d492977342fae8542c2781b0b3df8a95388ce97bedf9dc69db74868167b782c", "simplification/2026-08-28-remove-hero-input-glow.zh.md": "sha256:b5e52a51adddee66a7f738e0345203cc339f0aaf2dbdfd9ad7524592a300837c", @@ -1864,9 +2623,45 @@ "simplification/2026-09-01-ptc-omits-workflow-tool.i18n.yaml": "sha256:84318dde2d7955dae069138a3bb223002beeec859e536be9c42c51a9bbb333d3", "simplification/2026-09-01-ptc-omits-workflow-tool.md": "sha256:c5ba23ff9dfbba90d55e1585ad78e11151bb4370d3c5b1ad0414390f86d2bc7c", "simplification/2026-09-01-ptc-omits-workflow-tool.zh.md": "sha256:e06ed5bb82e5f5372546704e340dea6539b438c76363fa23a74d7c512f4acafc", + "simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml": "sha256:3dd8beb43c0e727df7f3d5da6bdbb0e0960b813e2833fbc3a8f31564c81c87a9", + "simplification/2026-09-03-minimal-profiles-persistent-shell-only.md": "sha256:a9d23a24f4c60c8576237cff24c0d99a28770fe5a66d464f88363fbdb4b9d9a1", + "simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md": "sha256:cc69c1145bdcade61833006303b0ded6cd97bffef257f316d47fac88a9be8f4a", + "simplification/2026-09-05-base-default-file-editor.i18n.yaml": "sha256:d0611e9fec6722724369578398f2bae075a85054b9bdb93de0a6bd4c64eb2a2c", + "simplification/2026-09-05-base-default-file-editor.md": "sha256:cc0bafa6054912e39d4e34db29b19522988e0f1684fcb2eef88f03831bb2db44", + "simplification/2026-09-05-base-default-file-editor.zh.md": "sha256:7ddbb1671916d9fad4ce83c27f544825c413892976538432e99d73ea3a3d9c9e", + "simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml": "sha256:4c9da0968fe2f511c17338671d134e868a8dd750285d0e769555d82738ad13c3", + "simplification/2026-09-06-agent-request-freeze-evidence.md": "sha256:f3cf59f0a56fa94326841523d5a91e8089c1a64cc064eaa870f086c87a6d22d6", + "simplification/2026-09-06-agent-request-freeze-evidence.zh.md": "sha256:8b74a60a7431a1b22fbd0b98e203ca846ee4f1d7bbb753399595f6234acd9f62", + "simplification/2026-09-07-file-content-scan.i18n.yaml": "sha256:01a445e9134f90145af77eeadd9c5e35736dacd22a5d2862b208e0e916dada18", + "simplification/2026-09-07-file-content-scan.md": "sha256:2a465ba85647f4486df3383747ac88187f02ad7e577caf162620d71a49d12375", + "simplification/2026-09-07-file-content-scan.zh.md": "sha256:8a355f184ea428a5361cfb55faea6ec038e65e38b8cc653a9c9a65c589af3e9a", + "simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml": "sha256:0f4fa03a2dde71d8548639cefad6695794efb52ba1f7990046334d70946cca04", + "simplification/2026-09-08-web-subagent-catalog-projections.md": "sha256:816f072e6a7e1e1a363620a3cc49c27016d31e842873395eedf59fa67bcf237e", + "simplification/2026-09-08-web-subagent-catalog-projections.zh.md": "sha256:88c4efa930c7bab4b98c23395d0935c02ae5427171426694c5b5db808c927570", + "simplification/2026-09-10-derived-workspace-recency.i18n.yaml": "sha256:8b2a1c5b42cd10198a5af5dc1a8474800b42e889d4d9de36c420e7dbed9ee650", + "simplification/2026-09-10-derived-workspace-recency.md": "sha256:fa3d4c2ae4fa7ece5ab1d13e02f4c41a4a133c2046304dfb68e153de626dff0a", + "simplification/2026-09-10-derived-workspace-recency.zh.md": "sha256:11e5944c00c8fee4d69e724129fb701997dd3e6df3c9fae38b284a48de1ef71b", + "simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml": "sha256:81b73d88887e00273c05ee6d029dd7462142d89451a95a2646c0928c6f694a2c", + "simplification/2026-09-12-ralph-off-in-shipped-defaults.md": "sha256:72d794787d401e3d25320265266a43d0dfa69d0a9026c3f53c2f94e5926bd5f3", + "simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md": "sha256:c19fb72cd4c47f9473e2097a19910a87c03ab907df576b57014f2079be7e8b19", + "simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml": "sha256:17c0efede08c45997f20184f1991bac715cfae0c741affd1124344601b1d66b1", + "simplification/2026-09-15-host-only-remote-input-validation.md": "sha256:47d36719562b43ae2ba34166828bbbe2c0730a7c3e465645134b6e71d5965c46", + "simplification/2026-09-15-host-only-remote-input-validation.zh.md": "sha256:52dcb1f44afb802705976b98534c1640626f451dfae401852337a6c908652ac3", + "simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml": "sha256:fffea408a7ed18983af7f21fab3aeccf2a3d98d8e86c9dd2ac541fb112f8f0c2", + "simplification/2026-09-15-model-agent-availability-and-team-targets.md": "sha256:b37ec30fc0c1bcc25c2f7d40982ef69673c485958bf6cd97569532c173eab8e8", + "simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md": "sha256:42cafa7987b5d0997e7c338128f9d881d8e83b9e06344bd304e2c261205f1a36", + "simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml": "sha256:ef33017bacb4011f17c2982c01f3cda4cd238fc7741a7a02bdbafb74cdabedd8", + "simplification/2026-09-16-subagent-catalog-membership-only.md": "sha256:281347ccd28f020f1d623074890b67bc42f02aea677997ae7de777db1cf68d4e", + "simplification/2026-09-16-subagent-catalog-membership-only.zh.md": "sha256:c52bd261a0cd2a483bf159501a362ca0b22a6cede93c16eae31dc8ff36efd638", + "simplification/2026-09-19-deepseek-messages-only.i18n.yaml": "sha256:7ffd708c5823c39cc9536178292e7e7fa57db5267bbc9acdda0a87e4953d5ba8", + "simplification/2026-09-19-deepseek-messages-only.md": "sha256:241861200ce73e02f77d182633d6ea10b1f29c696c81bff002680762db01d9b5", + "simplification/2026-09-19-deepseek-messages-only.zh.md": "sha256:311ef6f8f4cf60dae2a43ca94d772028b645631501fb3648aaeb406ba174991e", "testing/2026-06-11-property-based-testing.i18n.yaml": "sha256:9d378912101ad30203f1fcbcad23e0f5d6ec6f39e04a83f17f90887b5c5d761e", "testing/2026-06-11-property-based-testing.md": "sha256:cca3b6299784cff94965f21b6d59966109a0bb41fdc2f8cf56653fa6c417a20a", "testing/2026-06-11-property-based-testing.zh.md": "sha256:09f3d1e2defd2792f87fb0e9892fb7e1db006f3c75ea4581f97e9cde4ea98175", + "testing/2026-06-19-acp-snapshot-tests.i18n.yaml": "sha256:fb85e95356a1565bc5da52e07614f8dc0bc08d1c7f95429117ecd132351dad51", + "testing/2026-06-19-acp-snapshot-tests.md": "sha256:3bcd0bf4cba8151c4074349e70e35506369afeaa35c8ae9e83f2afc3840af028", + "testing/2026-06-19-acp-snapshot-tests.zh.md": "sha256:902b7b2cec815d66df395e20149ebceff430085bef303f3bbd52057dbc93bb9a", "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml": "sha256:4177012c0821a8c22499852ecdf096af56d7263cb91c5d9d1bcd552cc26a3e00", "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md": "sha256:45234e7cc04b6010c6141f8d5924c04547300098f96262d423c50108e7c7011a", "testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md": "sha256:15e5a4ad3dee0bb711480cabe45cd97ec37bbdba19c2c2b47d1e9c203b07a48b", @@ -1906,6 +2701,9 @@ "testing/2026-07-30-vitest-jsdom-webstorage-ownership.i18n.yaml": "sha256:3752fb76874000ae95da9c404e9f5ec47332c92bc46bce7b42ab92997b5084c1", "testing/2026-07-30-vitest-jsdom-webstorage-ownership.md": "sha256:c3f0da0213a3024cd15bc31952a7e13573d18eb4edf0905e3c63b1af5fd2f7ba", "testing/2026-07-30-vitest-jsdom-webstorage-ownership.zh.md": "sha256:08ea14581f7485f4af7e945afbd5c2a2a52dafff7be723849bcbd1c91cf9160d", + "testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml": "sha256:f1278e1c9934f036f6a31401d236cff77c4ca2844165e8b646709b50393259b4", + "testing/2026-07-30-web-browser-snapshot-ci-gate.md": "sha256:8df498a8c9b00c5e1d13b56b73e624159c6d7c6b71aaaad9555b1d00d84f5e63", + "testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md": "sha256:23bedca39c42437152531f0fbc6eb2a5f1506789fdb6a51132216ad72651741b", "testing/2026-08-12-required-python-runtime-pull-request-ci.i18n.yaml": "sha256:741e7e58e5e8a9c82d901c4a16a70cea9bd256eac0e94179b5a24a231bb9fe1f", "testing/2026-08-12-required-python-runtime-pull-request-ci.md": "sha256:1f1273d7a550667533e29c76efd148aebf57581a91729c877b44a5e43a52d9ad", "testing/2026-08-12-required-python-runtime-pull-request-ci.zh.md": "sha256:6b9bf126c6b83d9b21e135d38df677c0d5623168b4353c6ddb706f76762c2193", @@ -1923,6 +2721,57 @@ "testing/2026-08-28-ci-test-reliability-skill.zh.md": "sha256:f58bc370c9a1bf2fe2badd5bf10bd1a8c1012133bb506206113d5c12cb60f93a", "testing/2026-08-29-windows-lane-hook-and-lefthook-budget.i18n.yaml": "sha256:d6bd5a532f5ed27678291659c5a9424a854e97da6c8bdddba16e4ac4764b9762", "testing/2026-08-29-windows-lane-hook-and-lefthook-budget.md": "sha256:18a9c2d55ddf166ea3ae854e48cdbd69a9851ab6d68da71c650085ff9611753a", - "testing/2026-08-29-windows-lane-hook-and-lefthook-budget.zh.md": "sha256:571322ae3764969df7bfca845ab5fbec2b577762c53701333db7f6c10489cb9b" + "testing/2026-08-29-windows-lane-hook-and-lefthook-budget.zh.md": "sha256:571322ae3764969df7bfca845ab5fbec2b577762c53701333db7f6c10489cb9b", + "testing/2026-09-06-client-assembly-test-line.i18n.yaml": "sha256:fa2a9de4fd908f01b4c2aca4bdf3b148e3cfffcf386744613744968a1720cfea", + "testing/2026-09-06-client-assembly-test-line.md": "sha256:b3647621620618bd837bb069a764e3626c530b9584ebfb526534e916ef63283a", + "testing/2026-09-06-client-assembly-test-line.zh.md": "sha256:0c3cd219f647551aad62a79905ec7ab33c39c2f3edb943b76d1cb2c413c23a64", + "testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml": "sha256:6427c77639c2a88a4308bd1b23c468dc28f916fe9ac1672ee55b15b5e0ef9d24", + "testing/2026-09-06-pr-ci-runner-temporary-storage.md": "sha256:1c2cf0a50f8f5bb38be13720265de63758185faac0a8a2ea706c7821487c5468", + "testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md": "sha256:348e6924131340b33d9b6d4dbd0fe6b497dae378b3139cb65280521d29996e19", + "testing/2026-09-06-python-runtime-install-retry.i18n.yaml": "sha256:b1a73895989e776c9481ca5fa7c8bacd2bf86c3ea0ee97ca049918d2c6f816f5", + "testing/2026-09-06-python-runtime-install-retry.md": "sha256:45bb787803e3b3177f6c2a21db5d2ba8558af134239a0fc05ccda52f64401f5b", + "testing/2026-09-06-python-runtime-install-retry.zh.md": "sha256:87565daf17023e140665ee015ab28a0961f0ad996784123d3d60b69e9511644a", + "testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml": "sha256:432a39d73af43f4a4e4cf02223550b615076e357b74c16987cb7ed72512940c7", + "testing/2026-09-06-standard-hosted-benchmark-runner.md": "sha256:e4afa7982f84acde3caf014d823bd33805f69b9175c0a56fa8b4202141895dbd", + "testing/2026-09-06-standard-hosted-benchmark-runner.zh.md": "sha256:8ebbc614514a652ac0336a0be0136143f9a0aadde363b96375c09a7681a8aebd", + "testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml": "sha256:37a6291a1c4f0a98a2c9485ba79f8524abd7772ecbd05f7f015e7d44b8d5fc4a", + "testing/2026-09-07-publint-test-subprocess-lifetime.md": "sha256:bb22e153c41b88dcf2f521ef783e46a809b911ee062eccc27066243f802cd52b", + "testing/2026-09-07-publint-test-subprocess-lifetime.zh.md": "sha256:81bd4f4f59659f2cf2dab35dcfdf2fd64931856c5597b59a864f8f36ea6e39e6", + "testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml": "sha256:ee13efb1c71bdb08d05f1c71b475ec4c651cbedc09485dfc5bff4b00d3d27240", + "testing/2026-09-07-pwsh-ci-observable-completion.md": "sha256:31655b54c5c2e5cddd09261b9b7c688a20a7b730b468356663b8cefe3d08751d", + "testing/2026-09-07-pwsh-ci-observable-completion.zh.md": "sha256:9a6e7ebf26e5af6eb84e66b1cd1b6767ff0e69c395a876d55ee9b14815f22729", + "testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml": "sha256:ef61dc888e2570bcaf982f6cb486b7ec5524ef0bd01f0c82182ed880672ef975", + "testing/2026-09-07-subagent-teardown-test-budgets.md": "sha256:f9ebe270c6c1564af433347d9e53d428803ab3adeace6ef614d1fc5836cd0ac2", + "testing/2026-09-07-subagent-teardown-test-budgets.zh.md": "sha256:86bfeb957ee089f11f8649fae28176e33ecedcf9d6219e4470308e62de2b9b22", + "testing/2026-09-08-ci-completion-observations.i18n.yaml": "sha256:4d1dfb50ab7e56131438def163f81d34333f59dcc50dc66a95628095cd051dee", + "testing/2026-09-08-ci-completion-observations.md": "sha256:d149c2a4a2f42ba8bf386cbebae87091b376b6e89d9b1aa9efee596e5f771de6", + "testing/2026-09-08-ci-completion-observations.zh.md": "sha256:09bb88a97662d42a84c3242edb56588cd4625d918d438de8198ba672029da353", + "testing/2026-09-08-ci-readiness-and-completion.i18n.yaml": "sha256:785303ca27d14ff7f0b079e80b81b8b464c2fc2346435fa2e2b4d227b74cdb95", + "testing/2026-09-08-ci-readiness-and-completion.md": "sha256:e3c45bf06214ddebc74b3e0a1b4244de228759fc2a4a3266f0cef6e6ea412702", + "testing/2026-09-08-ci-readiness-and-completion.zh.md": "sha256:a589cfcdc6320b2a712c90735b0fe0b5644eb21d7f4de35ba070479d24792764", + "testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml": "sha256:6c064651423fe9bff6887b28af79aa6d48101fd939c74f4cfcf4e8c11c8db6ab", + "testing/2026-09-09-user-patch-hmr-test-delivery.md": "sha256:8ac7b8913c1c2ccaab749c06c73cd272cf3feacb5a9c1c6c10345bcfbe249b4e", + "testing/2026-09-09-user-patch-hmr-test-delivery.zh.md": "sha256:c71fbb603ef2c58eed1246f9cb094739099f212bc5945311a218be9b6eaf1c9a", + "testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml": "sha256:f9630a0dc879ecd00cd7921e6517582ca8c37092569d0f65913a4dfb1890d0f2", + "testing/2026-09-10-hosted-image-test-assumptions.md": "sha256:6d9b719070257f0d8e9bef285a08f85a3f65fe4d48af4ef63637542c81346457", + "testing/2026-09-10-hosted-image-test-assumptions.zh.md": "sha256:4565cdd322c6028d47c8ac2c824fb022190f50a17bb75d89a641c1802f8cc690", + "testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml": "sha256:1740bca0aafb2f91dad66b870917a78ec8d50243b20f21f872cc005229231b65", + "testing/2026-09-12-connection-and-compaction-fixture-preconditions.md": "sha256:959da865d7740bfde03dcc4a5a1bd67e80c7595807e035d6499af738a6efcde4", + "testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md": "sha256:c00055c634f579ee80e910cc89de742ce1db90e43b407356f9fdc12a7ccab66d", + "testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml": "sha256:1e57f76a30c07fd343ce22be7ea9454e5beb8cc3979b7709d2f55423b1c7d2c0", + "testing/2026-09-26-web-conversation-clock-fixture-day.md": "sha256:a918c35b1b4e69b766b02883218bd1280d161392c4ca2f359d45dbe1cbfcfce8", + "testing/2026-09-26-web-conversation-clock-fixture-day.zh.md": "sha256:910eff4bae8075dd576726ccb3bc7cfd1f8bb5a970c543e1b9c986494a09749f", + "testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml": "sha256:12091d19b21eed28f0a2e0730bc0bf3929aca320ad64d6b19cbeed2cfc0e8c91", + "testing/2026-09-27-oxlint-contract-spawn-case-budgets.md": "sha256:3a74c192392d1671b13c18092e3876952acc929cf60748873653ec62de255769", + "testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md": "sha256:c38b92967d9237ff5bfe23b312964f022295b1e5aefe2425be1a1c564abea124", + "testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml": "sha256:2679fc6e54e1949aa07c952fdb174fad9d2c4ff61982dcb645b7e05898e22de2", + "testing/2026-09-27-plugin-install-interceptor-lifetime.md": "sha256:133fef1b85a5495522bb6b13d104ec41d4993a187d012b75f8eb61eec8baf05e", + "testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md": "sha256:0e99eb1bcb50e2fb7eda6f155f3602c39130d1e4dc9d3b342fda1782d672cc4b", + "testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml": "sha256:d59bfb871a6f0e3c07f59efb72c6ace251c44e065cceca913a77badb9cebb15d", + "testing/2026-09-27-web-lane-assertions-name-their-input-state.md": "sha256:5574f069f6e07652415840b4dbf3194a8f5efbb6d3e7111b32c451b29099543f", + "testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md": "sha256:44e6485e939cc854b3468a3ce1ba999dc4b15951606acbacea91593405e3e6c0", + "testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml": "sha256:c9b4a6e5c47812f12ed210624beabffe0bfd54fd0b217bb071dccf8942f49be5", + "testing/2026-09-28-detail-close-waits-on-observed-state.md": "sha256:e2168131bd9c671600b8dcb31c0a96d934f6e5bdb0bd41af542fdcdfce3c182d", + "testing/2026-09-28-detail-close-waits-on-observed-state.zh.md": "sha256:8439b884f36c0b594abf2a861cfdcb140acf106f6d4fa462be3243da5fef1fe6" } } diff --git a/.agents/notes/archived/process/2026-06-11-quality-gates.i18n.yaml b/.agents/notes/archived/process/2026-06-11-quality-gates.i18n.yaml new file mode 100644 index 0000000000..0e5ebb4f63 --- /dev/null +++ b/.agents/notes/archived/process/2026-06-11-quality-gates.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/archived/process/2026-06-11-quality-gates.md +2026-06-11-quality-gates.md: f49d377614681724b10f0e3f4eda5d1a645332cf +2026-06-11-quality-gates.zh.md: f80406f938cc5461eb77e211f2fe9f0b1643d15e diff --git a/.agents/notes/implemented/process/2026-06-11-quality-gates.md b/.agents/notes/archived/process/2026-06-11-quality-gates.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-11-quality-gates.md rename to .agents/notes/archived/process/2026-06-11-quality-gates.md index b4bcbcbb6e..f49d377614 100644 --- a/.agents/notes/implemented/process/2026-06-11-quality-gates.md +++ b/.agents/notes/archived/process/2026-06-11-quality-gates.md @@ -1,6 +1,7 @@ # Agent Note: Mechanical quality gates over prose guidelines Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-11-quality-gates.zh.md) diff --git a/.agents/notes/implemented/process/2026-06-11-quality-gates.zh.md b/.agents/notes/archived/process/2026-06-11-quality-gates.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-11-quality-gates.zh.md rename to .agents/notes/archived/process/2026-06-11-quality-gates.zh.md index 64179fd7b1..f80406f938 100644 --- a/.agents/notes/implemented/process/2026-06-11-quality-gates.zh.md +++ b/.agents/notes/archived/process/2026-06-11-quality-gates.zh.md @@ -1,6 +1,7 @@ # Agent Note: 以机械质量门禁取代行文约定 Status: implemented +Archived: 2026-09-30 [English](2026-06-11-quality-gates.md) | 中文 diff --git a/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.i18n.yaml b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.i18n.yaml new file mode 100644 index 0000000000..b6cbadcfee --- /dev/null +++ b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.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/archived/process/2026-06-16-pnpm-over-yarn.md +2026-06-16-pnpm-over-yarn.md: f27856dd51d349fef7f7969c03cdd16c6353162e +2026-06-16-pnpm-over-yarn.zh.md: d2736e98946ce8916efd12ab930cf3f1c8777f2a diff --git a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md rename to .agents/notes/archived/process/2026-06-16-pnpm-over-yarn.md index 4a8560a021..f27856dd51 100644 --- a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md +++ b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.md @@ -1,6 +1,7 @@ # Agent Note: pnpm as the package manager instead of Yarn 4 Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-16-pnpm-over-yarn.zh.md) diff --git a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md rename to .agents/notes/archived/process/2026-06-16-pnpm-over-yarn.zh.md index 4955cf8883..d2736e9894 100644 --- a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md +++ b/.agents/notes/archived/process/2026-06-16-pnpm-over-yarn.zh.md @@ -1,6 +1,7 @@ # Agent Note: 使用 pnpm 替代 Yarn 4 作为包管理器 Status: implemented +Archived: 2026-09-30 [English](2026-06-16-pnpm-over-yarn.md) | 中文 diff --git a/.agents/notes/archived/process/2026-06-17-ts-build-config.i18n.yaml b/.agents/notes/archived/process/2026-06-17-ts-build-config.i18n.yaml new file mode 100644 index 0000000000..d1230e5755 --- /dev/null +++ b/.agents/notes/archived/process/2026-06-17-ts-build-config.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/archived/process/2026-06-17-ts-build-config.md +2026-06-17-ts-build-config.md: 9e823575f7c4591425bb629268ac4a1ae0e40c8b +2026-06-17-ts-build-config.zh.md: a735a795a5fcd8882af02cfa525594bed83d7feb diff --git a/.agents/notes/implemented/process/2026-06-17-ts-build-config.md b/.agents/notes/archived/process/2026-06-17-ts-build-config.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-17-ts-build-config.md rename to .agents/notes/archived/process/2026-06-17-ts-build-config.md index 5b40ba01a0..9e823575f7 100644 --- a/.agents/notes/implemented/process/2026-06-17-ts-build-config.md +++ b/.agents/notes/archived/process/2026-06-17-ts-build-config.md @@ -1,6 +1,7 @@ # Agent Note: TSC-first build and one compiler ownership Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-17-ts-build-config.zh.md) diff --git a/.agents/notes/implemented/process/2026-06-17-ts-build-config.zh.md b/.agents/notes/archived/process/2026-06-17-ts-build-config.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-06-17-ts-build-config.zh.md rename to .agents/notes/archived/process/2026-06-17-ts-build-config.zh.md index c250cdd831..a735a795a5 100644 --- a/.agents/notes/implemented/process/2026-06-17-ts-build-config.zh.md +++ b/.agents/notes/archived/process/2026-06-17-ts-build-config.zh.md @@ -1,6 +1,7 @@ # Agent Note: TSC 优先构建与编译器单一归属 Status: implemented +Archived: 2026-09-30 [English](2026-06-17-ts-build-config.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml new file mode 100644 index 0000000000..24e4eacfe6 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.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/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.md +2026-07-02-bilingual-docs-and-pairing-gate.md: b6a93dcdf7d440dfbfd6a18cd7122f237795fb7e +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: 7e0f5f6cf260152b3f12475b063d7bbcb5f99e6c diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md rename to .agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.md index 9448c9dcd7..b6a93dcdf7 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md +++ b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.md @@ -1,6 +1,7 @@ # Agent Note: Bilingual documentation via paired sibling files and a pairing gate Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md rename to .agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md index b843de91e2..7e0f5f6cf2 100644 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md +++ b/.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md @@ -1,6 +1,7 @@ # Agent Note: 通过配对兄弟文件与配对门禁实现双语文档 Status: implemented +Archived: 2026-09-30 [English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.i18n.yaml b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.i18n.yaml new file mode 100644 index 0000000000..a968f0ec96 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.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/archived/process/2026-07-04-doc-tiers-and-budgets.md +2026-07-04-doc-tiers-and-budgets.md: 9281990adffb751f627737513b540ae65839fcb1 +2026-07-04-doc-tiers-and-budgets.zh.md: 4cd13ce339c0a38262208eb194769e0ef95930fa diff --git a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md rename to .agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.md index 209504218d..9281990adf 100644 --- a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md +++ b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.md @@ -1,6 +1,7 @@ # Agent Note: Documentation structure, tiers, and budgets Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-04-doc-tiers-and-budgets.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.zh.md b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.zh.md rename to .agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.zh.md index 9c866424d8..4cd13ce339 100644 --- a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.zh.md +++ b/.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.zh.md @@ -1,6 +1,7 @@ # Agent Note: 文档结构、层级与预算 Status: implemented +Archived: 2026-09-30 [English](2026-07-04-doc-tiers-and-budgets.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.i18n.yaml b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.i18n.yaml new file mode 100644 index 0000000000..3ecf6cda42 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.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/archived/process/2026-07-05-uniform-agent-note-format.md +2026-07-05-uniform-agent-note-format.md: 3412209688c97f0e3d8dbe9064bd2e18285d7c2b +2026-07-05-uniform-agent-note-format.zh.md: 9a4d67b6538aa58ca5c1b91bfa5f33eb437ab6d3 diff --git a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md rename to .agents/notes/archived/process/2026-07-05-uniform-agent-note-format.md index c05d81c700..3412209688 100644 --- a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md +++ b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.md @@ -1,6 +1,7 @@ # Agent Note: One gated in-file format for Agent Notes Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-05-uniform-agent-note-format.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.zh.md b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.zh.md rename to .agents/notes/archived/process/2026-07-05-uniform-agent-note-format.zh.md index 13991632e7..9a4d67b653 100644 --- a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.zh.md +++ b/.agents/notes/archived/process/2026-07-05-uniform-agent-note-format.zh.md @@ -1,6 +1,7 @@ # Agent Note: Agent Note 的统一受门禁约束的文件内格式 Status: implemented +Archived: 2026-09-30 [English](2026-07-05-uniform-agent-note-format.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.i18n.yaml b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.i18n.yaml new file mode 100644 index 0000000000..c44e212df4 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.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/archived/process/2026-07-06-parallel-pre-push-gates.md +2026-07-06-parallel-pre-push-gates.md: ef60e7d15bbf370ce12dc0e0cbc94c305806081a +2026-07-06-parallel-pre-push-gates.zh.md: 67c0a3bab858588d1fe141ae37b0396fc2ad453b diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md rename to .agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.md index 9773a86428..ef60e7d15b 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md +++ b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.md @@ -1,6 +1,7 @@ # Agent Note: Parallel pre-push gates Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-06-parallel-pre-push-gates.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md rename to .agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.zh.md index 4d0e15a338..67c0a3bab8 100644 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.zh.md +++ b/.agents/notes/archived/process/2026-07-06-parallel-pre-push-gates.zh.md @@ -1,6 +1,7 @@ # Agent Note: 并行 pre-push 门禁 Status: implemented +Archived: 2026-09-30 [English](2026-07-06-parallel-pre-push-gates.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.i18n.yaml b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.i18n.yaml new file mode 100644 index 0000000000..8be4508216 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.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/archived/process/2026-07-12-package-model-experience-contract.md +2026-07-12-package-model-experience-contract.md: baa590cb4d36722dd6ade5d03b6345bd061a0062 +2026-07-12-package-model-experience-contract.zh.md: 79d000d4f9eb64033feb54bafa782edea94ca591 diff --git a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md rename to .agents/notes/archived/process/2026-07-12-package-model-experience-contract.md index 8462cc20e8..baa590cb4d 100644 --- a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.md @@ -1,6 +1,7 @@ # Agent Note: Package Model Experience contract Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-12-package-model-experience-contract.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.zh.md b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-12-package-model-experience-contract.zh.md rename to .agents/notes/archived/process/2026-07-12-package-model-experience-contract.zh.md index 040412f3af..79d000d4f9 100644 --- a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.zh.md +++ b/.agents/notes/archived/process/2026-07-12-package-model-experience-contract.zh.md @@ -1,6 +1,7 @@ # Agent Note: 包的模型体验约定 Status: implemented +Archived: 2026-09-30 [English](2026-07-12-package-model-experience-contract.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml new file mode 100644 index 0000000000..33abb9cbb8 --- /dev/null +++ b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.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/archived/process/2026-07-19-remove-generated-agent-note-index.md +2026-07-19-remove-generated-agent-note-index.md: c8c943582bc05cfa5de57023bf7a35db5951eed1 +2026-07-19-remove-generated-agent-note-index.zh.md: 8e474536d63d534195f7a87978a7eef220fa42c9 diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md rename to .agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.md index 60dba14064..c8c943582b 100644 --- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md +++ b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.md @@ -1,6 +1,7 @@ # Agent Note: Keep Agent Notes discoverable without a generated index Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-19-remove-generated-agent-note-index.zh.md) diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md rename to .agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.zh.md index 912aea1330..8e474536d6 100644 --- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.zh.md +++ b/.agents/notes/archived/process/2026-07-19-remove-generated-agent-note-index.zh.md @@ -1,6 +1,7 @@ # Agent Note: 无需生成索引即可发现 Agent Note Status: implemented +Archived: 2026-09-30 [English](2026-07-19-remove-generated-agent-note-index.md) | 中文 diff --git a/.agents/notes/archived/process/2026-07-19-web-styling-system.i18n.yaml b/.agents/notes/archived/process/2026-07-19-web-styling-system.i18n.yaml new file mode 100644 index 0000000000..2be48bcbbb --- /dev/null +++ b/.agents/notes/archived/process/2026-07-19-web-styling-system.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/archived/process/2026-07-19-web-styling-system.md +2026-07-19-web-styling-system.md: 348c18137734b45458fdf8cdfeb10f5bd7e3ea41 +2026-07-19-web-styling-system.zh.md: 86c5731ba49fce4b9c39787dc55ea888e783dd5e diff --git a/.agents/notes/implemented/process/2026-07-19-web-styling-system.md b/.agents/notes/archived/process/2026-07-19-web-styling-system.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-19-web-styling-system.md rename to .agents/notes/archived/process/2026-07-19-web-styling-system.md index b4d647924a..348c181377 100644 --- a/.agents/notes/implemented/process/2026-07-19-web-styling-system.md +++ b/.agents/notes/archived/process/2026-07-19-web-styling-system.md @@ -1,6 +1,7 @@ # Agent Note: Web styling system — the token framework and engineering constraints Status: implemented +Archived: 2026-09-30 > Token-system update (2026-07-22): the framework rulings here (CSS Modules + clsx, no component library, no tailwind, tokens-only colors) remain in force, but the two-layer `--bg-*`/`--text-*` token table and its `web-ui/src/style/global.css` home were replaced by the `--dsw-*` static+alias sheets in `packages/client/ui-theme/src/styles/` (dark = `body[data-ds-dark-theme]` override) — the sheets themselves are the token authority. diff --git a/.agents/notes/implemented/process/2026-07-19-web-styling-system.zh.md b/.agents/notes/archived/process/2026-07-19-web-styling-system.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-19-web-styling-system.zh.md rename to .agents/notes/archived/process/2026-07-19-web-styling-system.zh.md index e7384575cf..86c5731ba4 100644 --- a/.agents/notes/implemented/process/2026-07-19-web-styling-system.zh.md +++ b/.agents/notes/archived/process/2026-07-19-web-styling-system.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 样式体系——token 框架与工程约束 Status: implemented +Archived: 2026-09-30 > token 体系更新(2026-07-22):本文框架裁决(CSS Modules + clsx、无组件库、无 tailwind、颜色只用 token)仍然生效,但两层 `--bg-*`/`--text-*` token 表及其宿主 `web-ui/src/style/global.css` 已被 `packages/client/ui-theme/src/styles/` 的 `--dsw-*` static+alias 双层表取代(暗色=`body[data-ds-dark-theme]` 覆写)——样式表本身即 token 权威。 diff --git a/.agents/notes/archived/process/2026-07-20-gui-testing-system.i18n.yaml b/.agents/notes/archived/process/2026-07-20-gui-testing-system.i18n.yaml new file mode 100644 index 0000000000..c86d0b687d --- /dev/null +++ b/.agents/notes/archived/process/2026-07-20-gui-testing-system.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/archived/process/2026-07-20-gui-testing-system.md +2026-07-20-gui-testing-system.md: 07c4e948c6c93516420cfb70c5b4372360e4de08 +2026-07-20-gui-testing-system.zh.md: f41b92495d12bcd108379af204423d60f8e1f33d diff --git a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.md b/.agents/notes/archived/process/2026-07-20-gui-testing-system.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-20-gui-testing-system.md rename to .agents/notes/archived/process/2026-07-20-gui-testing-system.md index 3fdb9a596d..07c4e948c6 100644 --- a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.md +++ b/.agents/notes/archived/process/2026-07-20-gui-testing-system.md @@ -1,6 +1,7 @@ # Agent Note: GUI testing system — the three-tier structure Status: implemented +Archived: 2026-09-30 > Path update (2026-08-27, Remote migration): the three-tier philosophy and golden-path method here remain current; object-layer specs live across `packages/api/session-controller/tests/` and `packages/test-support/client-runtime/tests/`, while Remote and carrier specs live across `packages/api/gateway/tests/` and `packages/client/connection/tests/`. Component specs are per-plugin jsdom suites under each `packages/client/*/tests/`. Component-spec shape follows the [slot system standard](../architecture/2026-07-22-slot-type-chain-implementation.md): feed props directly — the store share comes from `createXXXStore().create()` (the real engine, the sanctioned zero-machinery path), framework hooks are plain stubs; no render machinery, no provider mounting. Slot ownership and registry semantics are tier-2 territory (`ui-renderer` + `ui-slots` suites), not component specs. diff --git a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.zh.md b/.agents/notes/archived/process/2026-07-20-gui-testing-system.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-07-20-gui-testing-system.zh.md rename to .agents/notes/archived/process/2026-07-20-gui-testing-system.zh.md index e49d5a2f84..f41b92495d 100644 --- a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.zh.md +++ b/.agents/notes/archived/process/2026-07-20-gui-testing-system.zh.md @@ -1,6 +1,7 @@ # Agent Note: GUI 测试体系——三层结构 Status: implemented +Archived: 2026-09-30 > 路径更新(2026-08-27,Remote 迁移):本文三层理念与黄金路径方法仍为现行;对象层 spec 分布于 `packages/api/session-controller/tests/` 和 `packages/test-support/client-runtime/tests/`,Remote 与承载 spec 分布于 `packages/api/gateway/tests/` 和 `packages/client/connection/tests/`。组件 spec 是各 `packages/client/*/tests/` 下的插件级 jsdom 套件。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.zh.md):props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的无额外机制路径),框架钩子用普通桩;无渲染机制、不挂载提供方。slot 归属和注册表语义归 2 层地界(`ui-renderer` + `ui-slots` 套件),不归组件 spec。 diff --git a/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml new file mode 100644 index 0000000000..aed46e2618 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.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/archived/process/2026-08-03-package-anchored-subsystem-pages.md +2026-08-03-package-anchored-subsystem-pages.md: eab569b7dcf7c671ea0ae7c50691ffd1edc0f0d6 +2026-08-03-package-anchored-subsystem-pages.zh.md: 61b648c45bb3cc54eebe69ddf4ae0b9f56c9c50d diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md rename to .agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.md index ea98bae0c5..eab569b7dc 100644 --- a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md +++ b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.md @@ -1,6 +1,7 @@ # Agent Note: Package-anchored subsystem pages and thin group READMEs Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-03-package-anchored-subsystem-pages.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md rename to .agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.zh.md index 4da4211f4e..61b648c45b 100644 --- a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md +++ b/.agents/notes/archived/process/2026-08-03-package-anchored-subsystem-pages.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按包锚定的子系统页面与精简的分组 README Status: implemented +Archived: 2026-09-30 [English](2026-08-03-package-anchored-subsystem-pages.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.i18n.yaml b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.i18n.yaml new file mode 100644 index 0000000000..9b64d6e70c --- /dev/null +++ b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.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/archived/process/2026-08-08-browser-gif-evidence-chain.md +2026-08-08-browser-gif-evidence-chain.md: e85c05ef214cbf03409be1512da4b127231ac4f3 +2026-08-08-browser-gif-evidence-chain.zh.md: bc2d11f007adc4645aa6402736007f1d16ecf56e diff --git a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.md b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.md rename to .agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.md index 8a7c28792b..e85c05ef21 100644 --- a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.md +++ b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.md @@ -1,6 +1,7 @@ # Agent Note: Browser GIFs preserve one evidence chain Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-08-browser-gif-evidence-chain.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.zh.md b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.zh.md rename to .agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.zh.md index ff9b6cc729..bc2d11f007 100644 --- a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.zh.md +++ b/.agents/notes/archived/process/2026-08-08-browser-gif-evidence-chain.zh.md @@ -1,6 +1,7 @@ # Agent Note: 浏览器 GIF 保留单一证据链 Status: implemented +Archived: 2026-09-30 [English](2026-08-08-browser-gif-evidence-chain.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.i18n.yaml b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.i18n.yaml new file mode 100644 index 0000000000..8b06f09d72 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.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/archived/process/2026-08-09-committed-artifact-citations.md +2026-08-09-committed-artifact-citations.md: cc7483ac97c69191cfb755e304e011e528727070 +2026-08-09-committed-artifact-citations.zh.md: 1d19250e3e4ccb3a23f80517f31b072938278698 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md rename to .agents/notes/archived/process/2026-08-09-committed-artifact-citations.md index 66dec38755..cc7483ac97 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md +++ b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.md @@ -1,6 +1,7 @@ # Agent Note: Cite committed artifacts, never design-session ordinals Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-09-committed-artifact-citations.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md rename to .agents/notes/archived/process/2026-08-09-committed-artifact-citations.zh.md index ebc4541e56..1d19250e3e 100644 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.zh.md +++ b/.agents/notes/archived/process/2026-08-09-committed-artifact-citations.zh.md @@ -1,6 +1,7 @@ # Agent Note: 引用已提交的产物,绝不引用设计会话序号 Status: implemented +Archived: 2026-09-30 [English](2026-08-09-committed-artifact-citations.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml new file mode 100644 index 0000000000..602ee5c781 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.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/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md +2026-08-09-concrete-prose-names-actors-and-recorded-facts.md: 26075efb0b202e2e401e8d33007239c4df10b1b6 +2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md: 1904a3575447928a32c64816f1fd10f4ce6f28bd diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md rename to .agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md index 91f7913ff0..26075efb0b 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md +++ b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md @@ -1,6 +1,7 @@ # Agent Note: Concrete prose names actors and recorded facts Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md rename to .agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md index 315850711a..1904a35754 100644 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md +++ b/.agents/notes/archived/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md @@ -1,6 +1,7 @@ # Agent Note: 具体表述说明执行者和记录的事实 Status: implemented +Archived: 2026-09-30 [English](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml new file mode 100644 index 0000000000..7d93d49b49 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.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/archived/process/2026-08-18-in-job-partitioned-coverage.md +2026-08-18-in-job-partitioned-coverage.md: f2e531a8242c4597f0c85fe30d37167df7c0e444 +2026-08-18-in-job-partitioned-coverage.zh.md: 39425a378afd924c231349e7be81b64dc1c1fc9b diff --git a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md rename to .agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.md index 3eafecc02f..f2e531a824 100644 --- a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md +++ b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.md @@ -1,6 +1,7 @@ # Agent Note: In-job partitioned coverage Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-18-in-job-partitioned-coverage.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md rename to .agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.zh.md index 457e352992..39425a378a 100644 --- a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md +++ b/.agents/notes/archived/process/2026-08-18-in-job-partitioned-coverage.zh.md @@ -1,6 +1,7 @@ # Agent Note: 单 job 分区覆盖率 Status: implemented +Archived: 2026-09-30 [English](2026-08-18-in-job-partitioned-coverage.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-19-remove-knip.i18n.yaml b/.agents/notes/archived/process/2026-08-19-remove-knip.i18n.yaml new file mode 100644 index 0000000000..d281b03bc5 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-19-remove-knip.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/archived/process/2026-08-19-remove-knip.md +2026-08-19-remove-knip.md: d385e729d3336400b55aec50a9464260c2b6bfa7 +2026-08-19-remove-knip.zh.md: 5decd988fbec425961d73b36cda93fd10746c2cd diff --git a/.agents/notes/implemented/process/2026-08-19-remove-knip.md b/.agents/notes/archived/process/2026-08-19-remove-knip.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-19-remove-knip.md rename to .agents/notes/archived/process/2026-08-19-remove-knip.md index 5e7e898e05..d385e729d3 100644 --- a/.agents/notes/implemented/process/2026-08-19-remove-knip.md +++ b/.agents/notes/archived/process/2026-08-19-remove-knip.md @@ -1,6 +1,7 @@ # Agent Note: Remove Knip from repository gates Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-19-remove-knip.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-19-remove-knip.zh.md b/.agents/notes/archived/process/2026-08-19-remove-knip.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-19-remove-knip.zh.md rename to .agents/notes/archived/process/2026-08-19-remove-knip.zh.md index f6bca86616..5decd988fb 100644 --- a/.agents/notes/implemented/process/2026-08-19-remove-knip.zh.md +++ b/.agents/notes/archived/process/2026-08-19-remove-knip.zh.md @@ -1,6 +1,7 @@ # Agent Note: 从仓库门禁中移除 Knip Status: implemented +Archived: 2026-09-30 [English](2026-08-19-remove-knip.md) | 中文 diff --git a/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml new file mode 100644 index 0000000000..519da97f20 --- /dev/null +++ b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.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/archived/process/2026-08-28-ci-node-compile-cache-data-disk.md +2026-08-28-ci-node-compile-cache-data-disk.md: 5b6b29ab42c6d041e846bd65f63957ee1906bdc2 +2026-08-28-ci-node-compile-cache-data-disk.zh.md: 040b3ab730c9baafa21481a84600fb3dddc89ea9 diff --git a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md rename to .agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.md index 3f5f7eb2f0..5b6b29ab42 100644 --- a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md +++ b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.md @@ -1,6 +1,7 @@ # Agent Note: Redirect the Node compile cache to the data-volume runner temp Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-28-ci-node-compile-cache-data-disk.zh.md) diff --git a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md rename to .agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md index c55a6c73cf..040b3ab730 100644 --- a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md +++ b/.agents/notes/archived/process/2026-08-28-ci-node-compile-cache-data-disk.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将 Node 编译缓存重定向到数据卷 runner 临时目录 Status: implemented +Archived: 2026-09-30 [English](2026-08-28-ci-node-compile-cache-data-disk.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.i18n.yaml b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.i18n.yaml new file mode 100644 index 0000000000..f913cc8c6e --- /dev/null +++ b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.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/archived/process/2026-09-02-project-local-issue-planning-fields.md +2026-09-02-project-local-issue-planning-fields.md: 9e7ec91eaa2cff7acd1690c3557c934982854a24 +2026-09-02-project-local-issue-planning-fields.zh.md: 1f138721585195dd81f0b286eadee664bc65beea diff --git a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md rename to .agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.md index e44fe0f002..9e7ec91eaa 100644 --- a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md +++ b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.md @@ -1,6 +1,7 @@ # Agent Note: Project-local Issue planning fields Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-02-project-local-issue-planning-fields.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.zh.md b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.zh.md rename to .agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.zh.md index 28308d4899..1f13872158 100644 --- a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.zh.md +++ b/.agents/notes/archived/process/2026-09-02-project-local-issue-planning-fields.zh.md @@ -1,6 +1,7 @@ # Agent Note: Project 局部 Issue 规划字段 Status: implemented +Archived: 2026-09-30 [English](2026-09-02-project-local-issue-planning-fields.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml new file mode 100644 index 0000000000..6a1d749c24 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.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/archived/process/2026-09-03-semantic-issue-templates-and-policy.md +2026-09-03-semantic-issue-templates-and-policy.md: 6d6e425b9b4fc5cf158ba6e701786c7cdb4c0743 +2026-09-03-semantic-issue-templates-and-policy.zh.md: b963bc0c5490dda606a877da8281331b3b2ea27e diff --git a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.md b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.md rename to .agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.md index 96b1981b21..6d6e425b9b 100644 --- a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.md +++ b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.md @@ -1,6 +1,7 @@ # Agent Note: Semantic Issue templates and presentation-neutral policy Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-03-semantic-issue-templates-and-policy.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.zh.md b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.zh.md rename to .agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.zh.md index 706cd59047..b963bc0c54 100644 --- a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.zh.md +++ b/.agents/notes/archived/process/2026-09-03-semantic-issue-templates-and-policy.zh.md @@ -1,6 +1,7 @@ # Agent Note: 语义化 Issue template 与不检查展示形式的 policy Status: implemented +Archived: 2026-09-30 [English](2026-09-03-semantic-issue-templates-and-policy.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.i18n.yaml b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.i18n.yaml new file mode 100644 index 0000000000..b981481511 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.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/archived/process/2026-09-06-evidence-driven-performance-skill.md +2026-09-06-evidence-driven-performance-skill.md: 33725da5c3fa6b48f14728305d15b0e994af54f8 +2026-09-06-evidence-driven-performance-skill.zh.md: 7df77c104133f9fddeb7478b728603a56479835c diff --git a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.md b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.md rename to .agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.md index 221287ef03..33725da5c3 100644 --- a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.md +++ b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.md @@ -1,6 +1,7 @@ # Agent Note: Evidence-driven performance optimization workflow Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-evidence-driven-performance-skill.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.zh.md b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.zh.md rename to .agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.zh.md index 90e2c1aa57..7df77c1041 100644 --- a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.zh.md +++ b/.agents/notes/archived/process/2026-09-06-evidence-driven-performance-skill.zh.md @@ -1,6 +1,7 @@ # Agent Note: 以证据驱动的性能优化工作流 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-evidence-driven-performance-skill.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.i18n.yaml b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.i18n.yaml new file mode 100644 index 0000000000..0bca3431cc --- /dev/null +++ b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.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/archived/process/2026-09-06-master-only-platform-ci.md +2026-09-06-master-only-platform-ci.md: cd2148ddf7bf65cec0257f7a2b0f4db7569889eb +2026-09-06-master-only-platform-ci.zh.md: 8806fbd9f6d8694235da82b8098289ec26071981 diff --git a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md rename to .agents/notes/archived/process/2026-09-06-master-only-platform-ci.md index fcdc413d64..cd2148ddf7 100644 --- a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md +++ b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.md @@ -1,6 +1,7 @@ # Agent Note: Master-only platform CI Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-master-only-platform-ci.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.zh.md b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.zh.md rename to .agents/notes/archived/process/2026-09-06-master-only-platform-ci.zh.md index 4fb55d5e95..8806fbd9f6 100644 --- a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.zh.md +++ b/.agents/notes/archived/process/2026-09-06-master-only-platform-ci.zh.md @@ -1,6 +1,7 @@ # Agent Note: 仅 master 执行的平台 CI Status: implemented +Archived: 2026-09-30 [English](2026-09-06-master-only-platform-ci.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.i18n.yaml b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.i18n.yaml new file mode 100644 index 0000000000..bb503ec9f7 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.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/archived/process/2026-09-06-node-compatibility-selfhosted.md +2026-09-06-node-compatibility-selfhosted.md: cd8c7c4e1e27ec32d2a64bc7ddf765c4b8427344 +2026-09-06-node-compatibility-selfhosted.zh.md: 3800e7ef170b605594be20ccca5f617b751a214a diff --git a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.md b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.md rename to .agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.md index 0ed4227114..cd8c7c4e1e 100644 --- a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.md +++ b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.md @@ -1,6 +1,7 @@ # Agent Note: Isolated Node compatibility jobs on self-hosted Linux Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-node-compatibility-selfhosted.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.zh.md b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.zh.md rename to .agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.zh.md index 2e58e3e798..3800e7ef17 100644 --- a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.zh.md +++ b/.agents/notes/archived/process/2026-09-06-node-compatibility-selfhosted.zh.md @@ -1,6 +1,7 @@ # Agent Note: 自托管 Linux 上隔离的 Node 兼容性作业 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-node-compatibility-selfhosted.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml new file mode 100644 index 0000000000..2c462f7f98 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.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/archived/process/2026-09-06-preview-hosted-runner-sizing.md +2026-09-06-preview-hosted-runner-sizing.md: 8ccaf66e059095be3a51330a063f83f1010c16e7 +2026-09-06-preview-hosted-runner-sizing.zh.md: 3a30032c20c00b7554ffd8cbcc9d22c7b0a89af8 diff --git a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.md b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.md rename to .agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.md index 414a17bf8a..8ccaf66e05 100644 --- a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.md +++ b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.md @@ -1,6 +1,7 @@ # Agent Note: Measured GitHub-hosted PR preview sizing Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-preview-hosted-runner-sizing.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.zh.md b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.zh.md rename to .agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.zh.md index 91a67b90b6..3a30032c20 100644 --- a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.zh.md +++ b/.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.zh.md @@ -1,6 +1,7 @@ # Agent Note: 基于测量的 GitHub 托管 PR 预览规格 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-preview-hosted-runner-sizing.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml new file mode 100644 index 0000000000..2d3ef2e4f7 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.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/archived/process/2026-09-06-release-rehearsal-selfhosted.md +2026-09-06-release-rehearsal-selfhosted.md: eea2a3804bb993ec98b45a9d3b7608fe08fa44c7 +2026-09-06-release-rehearsal-selfhosted.zh.md: 05f8d7192ce8bed99b90346fb7d02cd6c7f0fd71 diff --git a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.md b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.md rename to .agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.md index 415ae4716e..eea2a3804b 100644 --- a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.md +++ b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.md @@ -1,6 +1,7 @@ # Agent Note: trusted release rehearsals on persistent Linux runners Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-release-rehearsal-selfhosted.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.zh.md b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.zh.md rename to .agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.zh.md index a6fa441d01..05f8d7192c 100644 --- a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.zh.md +++ b/.agents/notes/archived/process/2026-09-06-release-rehearsal-selfhosted.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在持久化 Linux 运行器上执行受信任的发布演练 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-release-rehearsal-selfhosted.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml new file mode 100644 index 0000000000..918fa2c3e4 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.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/archived/process/2026-09-07-selective-issue-policy-evaluation.md +2026-09-07-selective-issue-policy-evaluation.md: 07c564f95b8ab3904d807f6e709f45fffe6fc394 +2026-09-07-selective-issue-policy-evaluation.zh.md: f0d5133ab6284c25f3a079394d32359ab5c248ff diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md rename to .agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.md index 5e05bef320..07c564f95b 100644 --- a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md +++ b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.md @@ -1,6 +1,7 @@ # Agent Note: Selective Issue policy evaluation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-selective-issue-policy-evaluation.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md rename to .agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.zh.md index 37c531e14e..f0d5133ab6 100644 --- a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md +++ b/.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.zh.md @@ -1,6 +1,7 @@ # Agent Note: Issue 策略的选择性求值 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-selective-issue-policy-evaluation.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.i18n.yaml b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.i18n.yaml new file mode 100644 index 0000000000..535d6efbcf --- /dev/null +++ b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.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/archived/process/2026-09-08-browser-third-party-build-inputs.md +2026-09-08-browser-third-party-build-inputs.md: c654ab50d1dfbf1e6782707bb9ed899a963905da +2026-09-08-browser-third-party-build-inputs.zh.md: 1ddd50e81abb91ee5a01d3fb6fdd074a2519b425 diff --git a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.md b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.md rename to .agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.md index 97e61f3b99..c654ab50d1 100644 --- a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.md +++ b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.md @@ -1,6 +1,7 @@ # Agent Note: Browser third-party libraries as build inputs Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-browser-third-party-build-inputs.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.zh.md b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.zh.md rename to .agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.zh.md index e23c4e9d46..1ddd50e81a 100644 --- a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.zh.md +++ b/.agents/notes/archived/process/2026-09-08-browser-third-party-build-inputs.zh.md @@ -1,6 +1,7 @@ # Agent Note: 浏览器第三方库作为构建输入 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-browser-third-party-build-inputs.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.i18n.yaml b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.i18n.yaml new file mode 100644 index 0000000000..8d99fd295f --- /dev/null +++ b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.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/archived/process/2026-09-08-package-index-link-discovery.md +2026-09-08-package-index-link-discovery.md: b23626811d6d3ab79e7a2aa5abfcc88c1e355be3 +2026-09-08-package-index-link-discovery.zh.md: 016fd987f9244f6410c0a3b950245dc1d2836a02 diff --git a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.md b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-08-package-index-link-discovery.md rename to .agents/notes/archived/process/2026-09-08-package-index-link-discovery.md index be9084f72e..b23626811d 100644 --- a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.md +++ b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.md @@ -1,6 +1,7 @@ # Agent Note: Include package indexes in Markdown link discovery Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-package-index-link-discovery.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.zh.md b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-08-package-index-link-discovery.zh.md rename to .agents/notes/archived/process/2026-09-08-package-index-link-discovery.zh.md index 93d6c21631..016fd987f9 100644 --- a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.zh.md +++ b/.agents/notes/archived/process/2026-09-08-package-index-link-discovery.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将包索引纳入 Markdown 链接发现范围 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-package-index-link-discovery.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-08-playwright-video-gif.i18n.yaml b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.i18n.yaml new file mode 100644 index 0000000000..bbff172425 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.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/archived/process/2026-09-08-playwright-video-gif.md +2026-09-08-playwright-video-gif.md: ef7a1f9cbfc487428df50fda10ce9b64ef292dbb +2026-09-08-playwright-video-gif.zh.md: 35c487419daa5bd8ac92484de53d1e1c69527b2c diff --git a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.md b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-08-playwright-video-gif.md rename to .agents/notes/archived/process/2026-09-08-playwright-video-gif.md index 44db048149..ef7a1f9cbf 100644 --- a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.md +++ b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.md @@ -1,6 +1,7 @@ # Agent Note: Playwright video captures continuous browser demos Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-playwright-video-gif.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.zh.md b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-08-playwright-video-gif.zh.md rename to .agents/notes/archived/process/2026-09-08-playwright-video-gif.zh.md index 4922d739f0..35c487419d 100644 --- a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.zh.md +++ b/.agents/notes/archived/process/2026-09-08-playwright-video-gif.zh.md @@ -1,6 +1,7 @@ # Agent Note: Playwright 视频录制连续浏览器演示 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-playwright-video-gif.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml new file mode 100644 index 0000000000..2da9f1e69e --- /dev/null +++ b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.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/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.md +2026-09-09-blocked-weighted-approvals-remain-pending.md: 1cd01bb4661f7b98df59385f1105774969f5b71d +2026-09-09-blocked-weighted-approvals-remain-pending.zh.md: ca9480c2fd38141be541aeb30c2ebe36a9600d2a diff --git a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.md b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.md rename to .agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.md index 50ab64dadc..1cd01bb466 100644 --- a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.md +++ b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.md @@ -1,6 +1,7 @@ # Agent Note: Blocked weighted approvals remain pending Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-blocked-weighted-approvals-remain-pending.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md rename to .agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md index 58ab10b3ac..ca9480c2fd 100644 --- a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md +++ b/.agents/notes/archived/process/2026-09-09-blocked-weighted-approvals-remain-pending.zh.md @@ -1,6 +1,7 @@ # Agent Note: 阻塞中的加权批准保持 pending Status: implemented +Archived: 2026-09-30 [English](2026-09-09-blocked-weighted-approvals-remain-pending.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.i18n.yaml b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.i18n.yaml new file mode 100644 index 0000000000..55b0948851 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.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/archived/process/2026-09-09-cancel-superseded-ci.md +2026-09-09-cancel-superseded-ci.md: cb6c906d7507c2c2345faa72072a293a3a663968 +2026-09-09-cancel-superseded-ci.zh.md: e3a6b50e70cb721d84418ff097040ea2bdea3fcd diff --git a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.md b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.md rename to .agents/notes/archived/process/2026-09-09-cancel-superseded-ci.md index 8de1f75787..cb6c906d75 100644 --- a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.md +++ b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.md @@ -1,6 +1,7 @@ # Agent Note: Cancel superseded CI validation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-cancel-superseded-ci.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.zh.md b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.zh.md rename to .agents/notes/archived/process/2026-09-09-cancel-superseded-ci.zh.md index 3ae6ec6ad5..e3a6b50e70 100644 --- a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.zh.md +++ b/.agents/notes/archived/process/2026-09-09-cancel-superseded-ci.zh.md @@ -1,6 +1,7 @@ # Agent Note: 取消被取代的 CI 验证 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-cancel-superseded-ci.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.i18n.yaml b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.i18n.yaml new file mode 100644 index 0000000000..d619b5ee89 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-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/archived/process/2026-09-10-approval-review-workflow-identity.md +2026-09-10-approval-review-workflow-identity.md: e89db3c0e869ab3770c071018ec7de88f18f9ad5 +2026-09-10-approval-review-workflow-identity.zh.md: ce4885ae1f30a69d21c629a3f592b0700d8acac2 diff --git a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.md b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.md rename to .agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.md index 77164134e8..e89db3c0e8 100644 --- a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.md +++ b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.md @@ -1,6 +1,7 @@ # Agent Note: Identify approval review workflows by file path Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-approval-review-workflow-identity.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.zh.md b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.zh.md rename to .agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.zh.md index 736851d6f3..ce4885ae1f 100644 --- a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.zh.md +++ b/.agents/notes/archived/process/2026-09-10-approval-review-workflow-identity.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按文件路径识别审批评审工作流 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-approval-review-workflow-identity.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.i18n.yaml b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.i18n.yaml new file mode 100644 index 0000000000..00ff708436 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.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/archived/process/2026-09-11-production-blame-approval-weight.md +2026-09-11-production-blame-approval-weight.md: a29e02cc0a900ed02a79d4ca0ee7eedc001b5d3a +2026-09-11-production-blame-approval-weight.zh.md: d25a791fa5c03dead3a98a8bae8d0ba9e558c48a diff --git a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md rename to .agents/notes/archived/process/2026-09-11-production-blame-approval-weight.md index d9a223c2f3..a29e02cc0a 100644 --- a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md +++ b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.md @@ -1,6 +1,7 @@ # Agent Note: Weight approvals by changed production-line ownership Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-11-production-blame-approval-weight.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.zh.md b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.zh.md rename to .agents/notes/archived/process/2026-09-11-production-blame-approval-weight.zh.md index f4dfc9d4fc..d25a791fa5 100644 --- a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.zh.md +++ b/.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按变更生产代码行的归属调整审批权重 Status: implemented +Archived: 2026-09-30 [English](2026-09-11-production-blame-approval-weight.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.i18n.yaml b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.i18n.yaml new file mode 100644 index 0000000000..c66c8013dc --- /dev/null +++ b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.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/archived/process/2026-09-12-experimental-publication-denylist.md +2026-09-12-experimental-publication-denylist.md: 8e45ceb8ef5b5746e6ebf6afca6caca846fad56b +2026-09-12-experimental-publication-denylist.zh.md: 53d4c07dea3856c826667afa492a94b6e6701d72 diff --git a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.md similarity index 83% rename from .agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md rename to .agents/notes/archived/process/2026-09-12-experimental-publication-denylist.md index f19faa1f39..8e45ceb8ef 100644 --- a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md +++ b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.md @@ -1,6 +1,7 @@ # Agent Note: Publish experimental packages with explicit private exceptions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-experimental-publication-denylist.zh.md) @@ -10,7 +11,7 @@ An allowlist for public experimental packages requires a policy edit whenever a ## Decision -The local npm baseline publisher and the public dsh release family discover experimental packages by default. [`PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES`](../../../../scripts/experimental-package-policy.ts) owns explicit private exclusions. The [complete experimental publication decision](2026-09-12-publish-all-experimental-packages.md) leaves this denylist empty; all current experimental packages publish. +The local npm baseline publisher and the public dsh release family discover experimental packages by default. [`PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES`](../../../../scripts/experimental-package-policy.ts) owns explicit private exclusions. The denylist is empty, so all current experimental packages publish. Every experimental directory outside the denylist is public by default. Workspace constraints require public packages to omit `private` and set `publishConfig.access: public`; all experimental packages retain the `@deepseek-ai/dsh-experimental-*` npm prefix. Adding a private prototype requires a denylist entry as well as its private manifest. @@ -20,7 +21,7 @@ This decision supersedes the private publication default in the [Agent Teams pac **Retain a public allowlist.** Every new public experimental package needs another policy entry, even though the default publication path can discover it. -**Publish every experimental package immediately.** This would expose the existing internal-only prototypes. An explicit private denylist changes the default for new packages while preserving their current publication status. The [complete publication decision](2026-09-12-publish-all-experimental-packages.md) records their separate publication. +**Publish every experimental package immediately.** This would expose the existing internal-only prototypes. An explicit private denylist changes the default for new packages while preserving their current publication status. The [archived publication decision](../../archived/process/2026-09-12-publish-all-experimental-packages.md) records their subsequent publication. ## Consequences diff --git a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.zh.md b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.zh.md similarity index 82% rename from .agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.zh.md rename to .agents/notes/archived/process/2026-09-12-experimental-publication-denylist.zh.md index 92a1c6d5cd..53d4c07dea 100644 --- a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.zh.md +++ b/.agents/notes/archived/process/2026-09-12-experimental-publication-denylist.zh.md @@ -1,6 +1,7 @@ # Agent Note: 默认发布实验性包并保留显式私有例外 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-experimental-publication-denylist.md) | 中文 @@ -10,7 +11,7 @@ Status: implemented ## 决策 -本地 npm baseline 发布器与公开 dsh 发布系列默认发现实验性包。[`PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES`](../../../../scripts/experimental-package-policy.ts) 负责显式私有排除项。[完整实验包发布决策](2026-09-12-publish-all-experimental-packages.zh.md)使该拒绝列表保持为空;所有当前实验性包都参与发布。 +本地 npm baseline 发布器与公开 dsh 发布系列默认发现实验性包。[`PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES`](../../../../scripts/experimental-package-policy.ts) 负责显式私有排除项。拒绝列表为空,因此所有当前实验性包都参与发布。 拒绝列表以外的每个实验性目录默认公开。workspace 约束要求公开包省略 `private` 并设置 `publishConfig.access: public`;所有实验性包保留 `@deepseek-ai/dsh-experimental-*` npm 前缀。添加私有原型需要拒绝列表条目及其私有 manifest(元数据清单)。 @@ -20,7 +21,7 @@ Status: implemented **保留公开允许列表。** 即使默认发布路径能够发现新公开实验性包,每个包仍需要额外的策略条目。 -**立即发布所有实验性包。** 这会暴露现有的内部专用原型。显式私有拒绝列表改变新包的默认策略,同时保留这些原型的当前发布状态。[完整发布决策](2026-09-12-publish-all-experimental-packages.zh.md)记录了对这些包的单独发布决定。 +**立即发布所有实验性包。** 这会暴露现有的内部专用原型。显式私有拒绝列表改变新包的默认策略,同时保留这些原型的当前发布状态。[已归档的发布决策](../../archived/process/2026-09-12-publish-all-experimental-packages.md)记录了这些包后来的发布。 ## 后果 diff --git a/.agents/notes/archived/process/2026-09-12-maintained-repository-references.i18n.yaml b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.i18n.yaml new file mode 100644 index 0000000000..c4d2fb5f82 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.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/archived/process/2026-09-12-maintained-repository-references.md +2026-09-12-maintained-repository-references.md: b63c4315dba420a49b709787b098cc5402c6d391 +2026-09-12-maintained-repository-references.zh.md: 9c167d16bdca823c369d4a176ed35dee0d20cf6e diff --git a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.md b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-12-maintained-repository-references.md rename to .agents/notes/archived/process/2026-09-12-maintained-repository-references.md index b2bc750100..b63c4315db 100644 --- a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.md +++ b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.md @@ -1,6 +1,7 @@ # Agent Note: Maintained repository references Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-maintained-repository-references.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.zh.md b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-12-maintained-repository-references.zh.md rename to .agents/notes/archived/process/2026-09-12-maintained-repository-references.zh.md index 99216e8105..9c167d16bd 100644 --- a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.zh.md +++ b/.agents/notes/archived/process/2026-09-12-maintained-repository-references.zh.md @@ -1,6 +1,7 @@ # Agent Note: 维护中的仓库引用 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-maintained-repository-references.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.i18n.yaml b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.i18n.yaml new file mode 100644 index 0000000000..24a2b06c8f --- /dev/null +++ b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.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/archived/process/2026-09-12-publish-all-experimental-packages.md +2026-09-12-publish-all-experimental-packages.md: f8d586fb7e58273d912efe56542fb04ff0637642 +2026-09-12-publish-all-experimental-packages.zh.md: 7a4659369c88e05722184551f90b9c44933dc635 diff --git a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.md b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.md rename to .agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.md index 8bef877297..f8d586fb7e 100644 --- a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.md +++ b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.md @@ -1,6 +1,7 @@ # Agent Note: Publish every current experimental package Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-publish-all-experimental-packages.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.zh.md b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.zh.md rename to .agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.zh.md index 6163e6b4ac..7a4659369c 100644 --- a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.zh.md +++ b/.agents/notes/archived/process/2026-09-12-publish-all-experimental-packages.zh.md @@ -1,6 +1,7 @@ # Agent Note: 发布所有当前实验性包 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-publish-all-experimental-packages.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.i18n.yaml b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.i18n.yaml new file mode 100644 index 0000000000..ea0e44bd61 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.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/archived/process/2026-09-15-pr-approval-delegation.md +2026-09-15-pr-approval-delegation.md: 8165f4f0eec28e44b91aa074004fab43a6702d3d +2026-09-15-pr-approval-delegation.zh.md: da4c0dcf8ee920d73082abb6bb234860c7502ddf diff --git a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.md b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-15-pr-approval-delegation.md rename to .agents/notes/archived/process/2026-09-15-pr-approval-delegation.md index 4dcf0e8a8d..8165f4f0ee 100644 --- a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.md +++ b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.md @@ -1,6 +1,7 @@ # Agent Note: PR-scoped approval delegation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-pr-approval-delegation.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.zh.md b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-15-pr-approval-delegation.zh.md rename to .agents/notes/archived/process/2026-09-15-pr-approval-delegation.zh.md index bf21724d67..da4c0dcf8e 100644 --- a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.zh.md +++ b/.agents/notes/archived/process/2026-09-15-pr-approval-delegation.zh.md @@ -1,6 +1,7 @@ # Agent Note: PR 范围内的批准委托 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-pr-approval-delegation.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-16-author-approval-credit.i18n.yaml b/.agents/notes/archived/process/2026-09-16-author-approval-credit.i18n.yaml new file mode 100644 index 0000000000..ee60ccc06b --- /dev/null +++ b/.agents/notes/archived/process/2026-09-16-author-approval-credit.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/archived/process/2026-09-16-author-approval-credit.md +2026-09-16-author-approval-credit.md: 85e8a16db2a2bfd31773274d0b6215cda293c95e +2026-09-16-author-approval-credit.zh.md: 42aa544aceb4b0e5cbc01c8081b14994f6a9b4f7 diff --git a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.md b/.agents/notes/archived/process/2026-09-16-author-approval-credit.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-16-author-approval-credit.md rename to .agents/notes/archived/process/2026-09-16-author-approval-credit.md index ab7aba8880..85e8a16db2 100644 --- a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.md +++ b/.agents/notes/archived/process/2026-09-16-author-approval-credit.md @@ -1,6 +1,7 @@ # Agent Note: Author approval credit Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-author-approval-credit.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.zh.md b/.agents/notes/archived/process/2026-09-16-author-approval-credit.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-16-author-approval-credit.zh.md rename to .agents/notes/archived/process/2026-09-16-author-approval-credit.zh.md index 316a09ea5d..42aa544ace 100644 --- a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.zh.md +++ b/.agents/notes/archived/process/2026-09-16-author-approval-credit.zh.md @@ -1,6 +1,7 @@ # Agent Note: 作者审批积分 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-author-approval-credit.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.i18n.yaml b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.i18n.yaml new file mode 100644 index 0000000000..bc557847d3 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.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/archived/process/2026-09-16-desktop-release-version-derivation.md +2026-09-16-desktop-release-version-derivation.md: e7c89e7bca935f92d9395fee6d17d13b1080952f +2026-09-16-desktop-release-version-derivation.zh.md: 17fd98c51ccf88eceef511d8e43a6bcaefc55c41 diff --git a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md rename to .agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.md index 54a7b9d52c..e7c89e7bca 100644 --- a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md +++ b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.md @@ -1,6 +1,7 @@ # Agent Note: Derive Desktop test versions from the complete dsh version Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-desktop-release-version-derivation.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md rename to .agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.zh.md index dddf4b7732..17fd98c51c 100644 --- a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md +++ b/.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 从完整 dsh 版本派生 Desktop 测试版本 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-desktop-release-version-derivation.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml new file mode 100644 index 0000000000..d9b3053f90 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.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/archived/process/2026-09-19-evidence-led-simplification-surveys.md +2026-09-19-evidence-led-simplification-surveys.md: 04942f69cd0d2bb53fce34a6b8a00b80f1b8f84e +2026-09-19-evidence-led-simplification-surveys.zh.md: f899208ee75897b949d04fcf76c63ef5a82579b3 diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md rename to .agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.md index c217f80239..04942f69cd 100644 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md +++ b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.md @@ -1,6 +1,7 @@ # Agent Note: Evidence-led simplification surveys Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-evidence-led-simplification-surveys.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md rename to .agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.zh.md index 1b1a117319..f899208ee7 100644 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.zh.md +++ b/.agents/notes/archived/process/2026-09-19-evidence-led-simplification-surveys.zh.md @@ -1,6 +1,7 @@ # Agent Note: 以证据指导简化调研 Status: implemented +Archived: 2026-09-30 [English](2026-09-19-evidence-led-simplification-surveys.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.i18n.yaml b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.i18n.yaml new file mode 100644 index 0000000000..0221680129 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.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/archived/process/2026-09-21-desktop-build-version-as-input.md +2026-09-21-desktop-build-version-as-input.md: 8175d41166666be5ee5070a75e6fe23eb00c73fd +2026-09-21-desktop-build-version-as-input.zh.md: 076492a02be2fcb1cf4aea1345cdd5479173392a diff --git a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.md b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.md rename to .agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.md index 8653c1ed58..8175d41166 100644 --- a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.md +++ b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.md @@ -1,6 +1,7 @@ # Agent Note: Pass the Desktop build version as an argument instead of rewriting manifests Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-21-desktop-build-version-as-input.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.zh.md b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.zh.md rename to .agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.zh.md index 0a995ab574..076492a02b 100644 --- a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.zh.md +++ b/.agents/notes/archived/process/2026-09-21-desktop-build-version-as-input.zh.md @@ -1,6 +1,7 @@ # Agent Note:Desktop 发布版本改为命令参数,不再改写清单 Status: implemented +Archived: 2026-09-30 [English](2026-09-21-desktop-build-version-as-input.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml new file mode 100644 index 0000000000..f22494740b --- /dev/null +++ b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.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/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md +2026-09-22-desktop-main-bundle-after-workspace-tsdown.md: 14cdec05f876d2656f2b232dae3dfc15374f4b99 +2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md: f0fcaf6529634150e7d69cdb1395ec62eb45b454 diff --git a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md rename to .agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md index 3c997f0707..14cdec05f8 100644 --- a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md +++ b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md @@ -1,6 +1,7 @@ # Agent Note: Bundle the Desktop main process after the workspace tsdown pass Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md rename to .agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md index 3f741125f6..f0fcaf6529 100644 --- a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md +++ b/.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md @@ -1,6 +1,7 @@ # Agent Note:Desktop 主进程在工作区 tsdown 阶段之后再打 bundle Status: implemented +Archived: 2026-09-30 [English](2026-09-22-desktop-main-bundle-after-workspace-tsdown.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.i18n.yaml b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.i18n.yaml new file mode 100644 index 0000000000..3fc26993b0 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.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/archived/process/2026-09-22-workspace-release-ranges.md +2026-09-22-workspace-release-ranges.md: dc042ccc1f36b4af382f16754d1251b7303171d1 +2026-09-22-workspace-release-ranges.zh.md: 0d691a7a822d558dcca272f390e9d59e5d320720 diff --git a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md rename to .agents/notes/archived/process/2026-09-22-workspace-release-ranges.md index 57c74df3ea..dc042ccc1f 100644 --- a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md +++ b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.md @@ -1,6 +1,7 @@ # Agent Note: Workspace dependency release ranges Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-22-workspace-release-ranges.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.zh.md b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.zh.md similarity index 98% rename from .agents/notes/implemented/process/2026-09-22-workspace-release-ranges.zh.md rename to .agents/notes/archived/process/2026-09-22-workspace-release-ranges.zh.md index f39ede4fb1..0d691a7a82 100644 --- a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.zh.md +++ b/.agents/notes/archived/process/2026-09-22-workspace-release-ranges.zh.md @@ -1,6 +1,7 @@ # Agent Note: Workspace 依赖发布范围 Status: implemented +Archived: 2026-09-30 [English](2026-09-22-workspace-release-ranges.md) | 中文 diff --git a/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml new file mode 100644 index 0000000000..3d4a6cfa04 --- /dev/null +++ b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.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/archived/process/2026-09-23-section-keyed-translation-pairing-records.md +2026-09-23-section-keyed-translation-pairing-records.md: b0ae04be7b79cfd0f77eb6fe634e832c8a650204 +2026-09-23-section-keyed-translation-pairing-records.zh.md: c018642890a62add69d99f7daac1e6cba534e186 diff --git a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.md b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.md rename to .agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.md index e01eac4bbc..b0ae04be7b 100644 --- a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.md +++ b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.md @@ -1,6 +1,7 @@ # Agent Note: Section-keyed translation pairing records Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-23-section-keyed-translation-pairing-records.zh.md) diff --git a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.zh.md b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.zh.md similarity index 99% rename from .agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.zh.md rename to .agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.zh.md index e4746a5092..c018642890 100644 --- a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.zh.md +++ b/.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.zh.md @@ -1,6 +1,7 @@ # Agent Note: 按分节记录翻译配对 Status: implemented +Archived: 2026-09-30 [English](2026-09-23-section-keyed-translation-pairing-records.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml new file mode 100644 index 0000000000..bd78c7b590 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.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/archived/simplification/2026-06-20-collapse-trace-only-session-events.md +2026-06-20-collapse-trace-only-session-events.md: 7600455c73711af3b82fe1be2f38f3b235152e8c +2026-06-20-collapse-trace-only-session-events.zh.md: 1a340f102853bbe2d81d830fbcada98c11a5c885 diff --git a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md rename to .agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.md index f83d93a899..7600455c73 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md +++ b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.md @@ -1,6 +1,7 @@ # Agent Note: Fold trace-only session facts into load-bearing events Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-20-collapse-trace-only-session-events.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.zh.md b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.zh.md rename to .agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.zh.md index e53544aed0..1a340f1028 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.zh.md +++ b/.agents/notes/archived/simplification/2026-06-20-collapse-trace-only-session-events.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将仅用于追踪的会话事实折叠进承载实际功能的事件 Status: implemented +Archived: 2026-09-30 [English](2026-06-20-collapse-trace-only-session-events.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml new file mode 100644 index 0000000000..ee666289cf --- /dev/null +++ b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.md +2026-06-26-fsspec-style-fs-seam.md: 88d5c51f6e0fbede8df8f8168077ccf43910eace +2026-06-26-fsspec-style-fs-seam.zh.md: 6e6e3b43257839cc2d209bab314d898323ea1fe2 diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md rename to .agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.md index 138d6d4d74..88d5c51f6e 100644 --- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md +++ b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.md @@ -1,6 +1,7 @@ # Agent Note: Split the filesystem seam — provider text mutations plus the `dsh-fs-observation-policy` plugin Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-26-fsspec-style-fs-seam.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md rename to .agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.zh.md index bc7a3756db..6e6e3b4325 100644 --- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.zh.md +++ b/.agents/notes/archived/simplification/2026-06-26-fsspec-style-fs-seam.zh.md @@ -1,6 +1,7 @@ # Agent Note: 拆分文件系统 seam——提供方文本变更操作与 `dsh-fs-observation-policy` 插件 Status: implemented +Archived: 2026-09-30 [English](2026-06-26-fsspec-style-fs-seam.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml new file mode 100644 index 0000000000..66477c1d9b --- /dev/null +++ b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.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/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.md +2026-07-20-unwrap-injected-content-envelopes.md: 51ef8b9715d70c79b28c6327e1163298b80a073f +2026-07-20-unwrap-injected-content-envelopes.zh.md: 1d688765c14ac2981b73ff654a594e21998adf9b diff --git a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md rename to .agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.md index 7d14ada7cc..51ef8b9715 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md +++ b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.md @@ -1,6 +1,7 @@ # Agent Note: Project injected content verbatim, dropping the XML envelopes Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-20-unwrap-injected-content-envelopes.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md rename to .agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md index 813adfb818..1d688765c1 100644 --- a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md +++ b/.agents/notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md @@ -1,6 +1,7 @@ # Agent Note: 注入内容逐字投影,去除 XML 封套 Status: implemented +Archived: 2026-09-30 [English](2026-07-20-unwrap-injected-content-envelopes.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml new file mode 100644 index 0000000000..ccf743c04e --- /dev/null +++ b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.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/archived/simplification/2026-07-22-plan-specific-collaboration-state.md +2026-07-22-plan-specific-collaboration-state.md: b92530a9acd780a410a2dafca01bc4a4140bd2cc +2026-07-22-plan-specific-collaboration-state.zh.md: 90ae80ba2be52bfaa748ef4bffa2ff9442a10715 diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md rename to .agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.md index 20648b8d6c..b92530a9ac 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md +++ b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.md @@ -1,6 +1,7 @@ # Agent Note: Plan-specific collaboration state Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-22-plan-specific-collaboration-state.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md rename to .agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.zh.md index 2142839d5a..90ae80ba2b 100644 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md +++ b/.agents/notes/archived/simplification/2026-07-22-plan-specific-collaboration-state.zh.md @@ -1,6 +1,7 @@ # Agent Note: plan 专用协作状态 Status: implemented +Archived: 2026-09-30 [English](2026-07-22-plan-specific-collaboration-state.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml new file mode 100644 index 0000000000..474c370ae2 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.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/archived/simplification/2026-07-24-agent-loop-observable-state-machine.md +2026-07-24-agent-loop-observable-state-machine.md: 4c280505dd48d0d98ca28dfc4f88a217ee6c7f27 +2026-07-24-agent-loop-observable-state-machine.zh.md: b56c8943c2ba62d66cb64cb94858b09f1b0efe2e diff --git a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.md b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.md rename to .agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.md index f250d645ca..4c280505dd 100644 --- a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.md +++ b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.md @@ -1,6 +1,7 @@ # Agent Note: Collapse agent-loop events around the observable state machine Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-24-agent-loop-observable-state-machine.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md rename to .agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md index c77e5ad8f6..b56c8943c2 100644 --- a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md +++ b/.agents/notes/archived/simplification/2026-07-24-agent-loop-observable-state-machine.zh.md @@ -1,6 +1,7 @@ # Agent Note: 围绕可观察状态机收拢 agent loop 事件 Status: implemented +Archived: 2026-09-30 [English](2026-07-24-agent-loop-observable-state-machine.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.i18n.yaml b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.i18n.yaml new file mode 100644 index 0000000000..1979aa1946 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.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/archived/simplification/2026-07-27-request-error-retry-action.md +2026-07-27-request-error-retry-action.md: 83fb72de6ffaf45d3fd08d8e320829bc82e8527c +2026-07-27-request-error-retry-action.zh.md: 1e21b90b04cd42fe0016618472aa18be322db3e2 diff --git a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.md b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.md rename to .agents/notes/archived/simplification/2026-07-27-request-error-retry-action.md index 9853d7d043..83fb72de6f 100644 --- a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.md +++ b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.md @@ -1,6 +1,7 @@ # Agent Note: Request-error retry action Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-27-request-error-retry-action.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.zh.md b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.zh.md rename to .agents/notes/archived/simplification/2026-07-27-request-error-retry-action.zh.md index e0c4749b7e..1e21b90b04 100644 --- a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.zh.md +++ b/.agents/notes/archived/simplification/2026-07-27-request-error-retry-action.zh.md @@ -1,6 +1,7 @@ # Agent Note: 请求错误重试动作 Status: implemented +Archived: 2026-09-30 [English](2026-07-27-request-error-retry-action.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml new file mode 100644 index 0000000000..221aedc107 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.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/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md +2026-08-28-omit-unneeded-invariant-companions.md: db6e74752add7f7f78384987a55ea77f8a47c4ac +2026-08-28-omit-unneeded-invariant-companions.zh.md: ecd3eec023ee18ee0ef3b638230b0d64ceed1d94 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md rename to .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md index 82d421677c..db6e74752a 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md @@ -1,6 +1,7 @@ # Agent Note: Omit invariant companions without independent observations Status: implemented +Archived: 2026-09-30 English | [中文](2026-08-28-omit-unneeded-invariant-companions.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md rename to .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md index d0fda91546..ecd3eec023 100644 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md +++ b/.agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md @@ -1,6 +1,7 @@ # Agent Note: 没有独立观察时省略不变量伴生入口 Status: implemented +Archived: 2026-09-30 [English](2026-08-28-omit-unneeded-invariant-companions.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml new file mode 100644 index 0000000000..ae8918c95d --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.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/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md +2026-09-03-minimal-profiles-persistent-shell-only.md: 740d2e233d1b00f6babe4abbca889a3ffa0a5791 +2026-09-03-minimal-profiles-persistent-shell-only.zh.md: 48d800683acc82e78706be8ee57a80965a3c9f83 diff --git a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md rename to .agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md index 2bc4ef7ada..740d2e233d 100644 --- a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md +++ b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md @@ -1,6 +1,7 @@ # Agent Note: Minimal profiles expose only a persistent shell Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-03-minimal-profiles-persistent-shell-only.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md rename to .agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md index d54e5734ac..48d800683a 100644 --- a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md +++ b/.agents/notes/archived/simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md @@ -1,6 +1,7 @@ # Agent Note: 极简 profile 只提供持久 shell Status: implemented +Archived: 2026-09-30 [English](2026-09-03-minimal-profiles-persistent-shell-only.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.i18n.yaml b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.i18n.yaml new file mode 100644 index 0000000000..c6670f1f3f --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.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/archived/simplification/2026-09-05-base-default-file-editor.md +2026-09-05-base-default-file-editor.md: e4e3740292fce922dcd7340e180feb81a999b23c +2026-09-05-base-default-file-editor.zh.md: 8623149547fc4b2293da72392087236394b29358 diff --git a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.md b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.md rename to .agents/notes/archived/simplification/2026-09-05-base-default-file-editor.md index e1e1571896..e4e3740292 100644 --- a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.md +++ b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.md @@ -1,6 +1,7 @@ # Agent Note: Shared base default file editor selection Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-05-base-default-file-editor.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.zh.md b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.zh.md rename to .agents/notes/archived/simplification/2026-09-05-base-default-file-editor.zh.md index d43df3313e..8623149547 100644 --- a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.zh.md +++ b/.agents/notes/archived/simplification/2026-09-05-base-default-file-editor.zh.md @@ -1,6 +1,7 @@ # Agent Note: 共享 base 默认文件编辑器选择 Status: implemented +Archived: 2026-09-30 [English](2026-09-05-base-default-file-editor.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml new file mode 100644 index 0000000000..2b21ae0bee --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.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/archived/simplification/2026-09-06-agent-request-freeze-evidence.md +2026-09-06-agent-request-freeze-evidence.md: dccd3ee770582dae10cbbaf0b3ec1e1ccca75634 +2026-09-06-agent-request-freeze-evidence.zh.md: 1767c837a2e427454561b1010ca0b4144e94da6e diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md rename to .agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.md index 6603a728c3..dccd3ee770 100644 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md +++ b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.md @@ -1,6 +1,7 @@ # Agent Note: Reuse only loop-proven message freezes Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-agent-request-freeze-evidence.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md rename to .agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.zh.md index a5ba15ac13..1767c837a2 100644 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.zh.md +++ b/.agents/notes/archived/simplification/2026-09-06-agent-request-freeze-evidence.zh.md @@ -1,6 +1,7 @@ # Agent Note: 仅复用循环已证明的消息冻结 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-agent-request-freeze-evidence.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-07-file-content-scan.i18n.yaml b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.i18n.yaml new file mode 100644 index 0000000000..d971e41439 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.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/archived/simplification/2026-09-07-file-content-scan.md +2026-09-07-file-content-scan.md: 4c72988ee7722b6b80ae594f936710714bb5eb23 +2026-09-07-file-content-scan.zh.md: 55561b43f84d9780e3ece2bdeb51464737bcdc8b diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-07-file-content-scan.md rename to .agents/notes/archived/simplification/2026-09-07-file-content-scan.md index 1ec46e7d68..4c72988ee7 100644 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.md +++ b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.md @@ -1,6 +1,7 @@ # Agent Note: Scan file content without per-array callbacks Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-file-content-scan.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md rename to .agents/notes/archived/simplification/2026-09-07-file-content-scan.zh.md index b4ebd835ca..55561b43f8 100644 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.zh.md +++ b/.agents/notes/archived/simplification/2026-09-07-file-content-scan.zh.md @@ -1,6 +1,7 @@ # Agent Note: 文件内容扫描不为每个数组创建回调 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-file-content-scan.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml new file mode 100644 index 0000000000..099fcbe14c --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.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/archived/simplification/2026-09-08-web-subagent-catalog-projections.md +2026-09-08-web-subagent-catalog-projections.md: a8660dc6d90f9e555429e7fe666c10e2cc6beaf6 +2026-09-08-web-subagent-catalog-projections.zh.md: aa4e9c2d1ae4057c3060d94c87b90d3c6b5e4a51 diff --git a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.md b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.md rename to .agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.md index 766d33e581..a8660dc6d9 100644 --- a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.md +++ b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.md @@ -1,6 +1,7 @@ # Agent Note: Web subagent catalogs consume shared projections Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-web-subagent-catalog-projections.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.zh.md b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.zh.md rename to .agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.zh.md index ef14db2530..aa4e9c2d1a 100644 --- a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.zh.md +++ b/.agents/notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.zh.md @@ -1,6 +1,7 @@ # Agent Note:Web 子代理目录消费共享 projection Status: implemented +Archived: 2026-09-30 [English](2026-09-08-web-subagent-catalog-projections.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.i18n.yaml b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.i18n.yaml new file mode 100644 index 0000000000..cfa1c33357 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.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/archived/simplification/2026-09-10-derived-workspace-recency.md +2026-09-10-derived-workspace-recency.md: 65d861fc9ea7e10845c4a071c43959cfc4480c7c +2026-09-10-derived-workspace-recency.zh.md: 5bf4a0020d447cbb3613a52a417d75f546a211d3 diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md rename to .agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.md index b967e332f2..65d861fc9e 100644 --- a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md +++ b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.md @@ -1,6 +1,7 @@ # Agent Note: Derive Workspace recency independently of manual order Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-derived-workspace-recency.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md rename to .agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.zh.md index 87c2bb9f26..5bf4a0020d 100644 --- a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.zh.md +++ b/.agents/notes/archived/simplification/2026-09-10-derived-workspace-recency.zh.md @@ -1,6 +1,7 @@ # Agent Note: 独立于手动顺序派生 Workspace 最近更新顺序 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-derived-workspace-recency.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml new file mode 100644 index 0000000000..ea54a41cd2 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.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/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.md +2026-09-12-ralph-off-in-shipped-defaults.md: 6b1dea345c6d22c05550f6a5db35ccbc2da0216c +2026-09-12-ralph-off-in-shipped-defaults.zh.md: 85ad7419301f7a18231622a5d0dba271fa02418e diff --git a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md rename to .agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.md index fbd7ea72e0..6b1dea345c 100644 --- a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md +++ b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.md @@ -1,6 +1,7 @@ # Agent Note: Ship ralph off in the default compositions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-ralph-off-in-shipped-defaults.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md rename to .agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md index 377f865623..85ad741930 100644 --- a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md +++ b/.agents/notes/archived/simplification/2026-09-12-ralph-off-in-shipped-defaults.zh.md @@ -1,6 +1,7 @@ # Agent Note: 在随附默认组合中关闭 ralph Status: implemented +Archived: 2026-09-30 [English](2026-09-12-ralph-off-in-shipped-defaults.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml new file mode 100644 index 0000000000..038cebb0fd --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.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/archived/simplification/2026-09-15-host-only-remote-input-validation.md +2026-09-15-host-only-remote-input-validation.md: 327f7ef025c7a5fac0447f07282fab6c5a2f62ca +2026-09-15-host-only-remote-input-validation.zh.md: 9e8a1d67c77b3221b5d364c6e850887325164769 diff --git a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.md b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.md rename to .agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.md index 5c256fb5f5..327f7ef025 100644 --- a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.md +++ b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.md @@ -1,6 +1,7 @@ # Agent Note: Validate Remote inputs only on the Host Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-host-only-remote-input-validation.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.zh.md b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.zh.md rename to .agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.zh.md index 92a3f2415c..9e8a1d67c7 100644 --- a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.zh.md +++ b/.agents/notes/archived/simplification/2026-09-15-host-only-remote-input-validation.zh.md @@ -1,6 +1,7 @@ # Agent Note: 仅在 Host 校验 Remote 输入 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-host-only-remote-input-validation.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml new file mode 100644 index 0000000000..3700bfcbae --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.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/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.md +2026-09-15-model-agent-availability-and-team-targets.md: 61fafb5ad3de039b0c624be56285fc8556dc6fbf +2026-09-15-model-agent-availability-and-team-targets.zh.md: 595b4ce4eba6bf02c5223cf3029e7924f8f2da87 diff --git a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.md b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.md rename to .agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.md index e1f7f95cfa..61fafb5ad3 100644 --- a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.md +++ b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.md @@ -1,6 +1,7 @@ # Agent Note: Model agent availability and Team targets Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-15-model-agent-availability-and-team-targets.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md rename to .agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md index 0447d7d405..595b4ce4eb 100644 --- a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md +++ b/.agents/notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md @@ -1,6 +1,7 @@ # Agent Note: 模型可见的 Agent 可用状态与 Team 目标 Status: implemented +Archived: 2026-09-30 [English](2026-09-15-model-agent-availability-and-team-targets.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml new file mode 100644 index 0000000000..b2ddc69ba1 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.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/archived/simplification/2026-09-16-subagent-catalog-membership-only.md +2026-09-16-subagent-catalog-membership-only.md: b2c8395b3c7dc19aa3a0733a951390fa006ac95e +2026-09-16-subagent-catalog-membership-only.zh.md: e41a115c875744cfcc5607d9afc4c693dbfce491 diff --git a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.md b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.md similarity index 99% rename from .agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.md rename to .agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.md index d1d0956380..b2c8395b3c 100644 --- a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.md +++ b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.md @@ -1,6 +1,7 @@ # Agent Note: Keep model configuration outside subagent catalogs Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-16-subagent-catalog-membership-only.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.zh.md b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.zh.md similarity index 98% rename from .agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.zh.md rename to .agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.zh.md index 4225a331a0..e41a115c87 100644 --- a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.zh.md +++ b/.agents/notes/archived/simplification/2026-09-16-subagent-catalog-membership-only.zh.md @@ -1,6 +1,7 @@ # Agent Note: 将模型配置保留在子代理目录之外 Status: implemented +Archived: 2026-09-30 [English](2026-09-16-subagent-catalog-membership-only.md) | 中文 diff --git a/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.i18n.yaml b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.i18n.yaml new file mode 100644 index 0000000000..20b70441c1 --- /dev/null +++ b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.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/archived/simplification/2026-09-19-deepseek-messages-only.md +2026-09-19-deepseek-messages-only.md: ef3ab51c733fa92d8d1c4e29cbfe9154b67481c1 +2026-09-19-deepseek-messages-only.zh.md: 1d5bdabbaf05ec7291bcb0ade354806ba7234b66 diff --git a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.md b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.md similarity index 98% rename from .agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.md rename to .agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.md index c10ecae950..ef3ab51c73 100644 --- a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.md +++ b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.md @@ -1,6 +1,7 @@ # Agent Note: One Messages transport for the official DeepSeek route Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-19-deepseek-messages-only.zh.md) diff --git a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.zh.md b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.zh.md similarity index 98% rename from .agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.zh.md rename to .agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.zh.md index 3cfadac6eb..1d5bdabbaf 100644 --- a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.zh.md +++ b/.agents/notes/archived/simplification/2026-09-19-deepseek-messages-only.zh.md @@ -1,6 +1,7 @@ # Agent Note: One Messages transport for the official DeepSeek route Status: implemented +Archived: 2026-09-30 [English](2026-09-19-deepseek-messages-only.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.i18n.yaml b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.i18n.yaml new file mode 100644 index 0000000000..1aeb562128 --- /dev/null +++ b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.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/archived/testing/2026-06-19-acp-snapshot-tests.md +2026-06-19-acp-snapshot-tests.md: 35ac1b52fbcff87afc548dab7792f643cbbf52ae +2026-06-19-acp-snapshot-tests.zh.md: 76dfe847b53b1b0e15f578eebe9de75166339668 diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.md similarity index 99% rename from .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md rename to .agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.md index 3a21d09609..35ac1b52fb 100644 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md +++ b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.md @@ -1,6 +1,7 @@ # Agent Note: ACP snapshot tests — record-once / replay-deterministic Status: implemented +Archived: 2026-09-30 English | [中文](2026-06-19-acp-snapshot-tests.zh.md) diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md rename to .agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.zh.md index e9f3682289..76dfe847b5 100644 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.zh.md +++ b/.agents/notes/archived/testing/2026-06-19-acp-snapshot-tests.zh.md @@ -1,6 +1,7 @@ # Agent Note: ACP 快照测试——一次录制 / 确定性回放 Status: implemented +Archived: 2026-09-30 [English](2026-06-19-acp-snapshot-tests.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml new file mode 100644 index 0000000000..eec38f1c07 --- /dev/null +++ b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.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/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.md +2026-07-30-web-browser-snapshot-ci-gate.md: a0034aed5858c89778d3516e114ec03386a2e905 +2026-07-30-web-browser-snapshot-ci-gate.zh.md: 3e84a335960f977eb6e6ed6fc82ec0185fa638fd diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.md similarity index 99% rename from .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md rename to .agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.md index 352072bca6..a0034aed58 100644 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md +++ b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.md @@ -1,6 +1,7 @@ # Agent Note: Required CI gate for web browser expected outputs Status: implemented +Archived: 2026-09-30 English | [中文](2026-07-30-web-browser-snapshot-ci-gate.zh.md) diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md rename to .agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md index cd04f33a5d..3e84a33596 100644 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md +++ b/.agents/notes/archived/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web 浏览器预期输出的必需 CI 门禁 Status: implemented +Archived: 2026-09-30 [English](2026-07-30-web-browser-snapshot-ci-gate.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.i18n.yaml b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.i18n.yaml new file mode 100644 index 0000000000..034a3a8acc --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.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/archived/testing/2026-09-06-client-assembly-test-line.md +2026-09-06-client-assembly-test-line.md: 163f3ce63aee65055d995c12d81966f2dff98990 +2026-09-06-client-assembly-test-line.zh.md: 1dc6d07632caa21be9c87f2e8656a8152a013390 diff --git a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.md b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.md rename to .agents/notes/archived/testing/2026-09-06-client-assembly-test-line.md index d2515c0689..163f3ce63a 100644 --- a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.md +++ b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.md @@ -1,6 +1,7 @@ # Agent Note: Whole-client test tier over an endpoint-named Remote mock Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-client-assembly-test-line.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.zh.md b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.zh.md rename to .agents/notes/archived/testing/2026-09-06-client-assembly-test-line.zh.md index 90b5768db4..1dc6d07632 100644 --- a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.zh.md +++ b/.agents/notes/archived/testing/2026-09-06-client-assembly-test-line.zh.md @@ -1,6 +1,7 @@ # Agent Note: 基于端点具名 Remote mock 的整机客户端测试档 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-client-assembly-test-line.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml new file mode 100644 index 0000000000..3ed8efef11 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.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/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.md +2026-09-06-pr-ci-runner-temporary-storage.md: 9faeaa088649d053c3d293ff5ec5282d4fd29d90 +2026-09-06-pr-ci-runner-temporary-storage.zh.md: 2a4355412286a8a76b404e367c217d5a1838a2a0 diff --git a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.md b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.md rename to .agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.md index 59d609c08e..9faeaa0886 100644 --- a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.md +++ b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.md @@ -1,6 +1,7 @@ # Agent Note: runner-owned temporary storage for PR CI Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-pr-ci-runner-temporary-storage.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md rename to .agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md index f6765212af..2a43554122 100644 --- a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md +++ b/.agents/notes/archived/testing/2026-09-06-pr-ci-runner-temporary-storage.zh.md @@ -1,6 +1,7 @@ # Agent Note: PR CI 使用 runner 管理的临时存储 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-pr-ci-runner-temporary-storage.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.i18n.yaml b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.i18n.yaml new file mode 100644 index 0000000000..d34d4d7f47 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.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/archived/testing/2026-09-06-python-runtime-install-retry.md +2026-09-06-python-runtime-install-retry.md: 2fbb17afbcbc7ed0095256a5753d27273720585f +2026-09-06-python-runtime-install-retry.zh.md: e1b20eab4ff066b7df90c8aeaee51cc99cd8d9ca diff --git a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md rename to .agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.md index 639fe2814a..2fbb17afbc 100644 --- a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md +++ b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.md @@ -1,6 +1,7 @@ # Agent Note: Bounded retry for Python runtime dependency install Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-python-runtime-install-retry.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.zh.md b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.zh.md similarity index 98% rename from .agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.zh.md rename to .agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.zh.md index 7a7943f43f..e1b20eab4f 100644 --- a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.zh.md +++ b/.agents/notes/archived/testing/2026-09-06-python-runtime-install-retry.zh.md @@ -1,6 +1,7 @@ # Agent Note: Python runtime 依赖安装的有界重试 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-python-runtime-install-retry.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml new file mode 100644 index 0000000000..4290cef39c --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.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/archived/testing/2026-09-06-standard-hosted-benchmark-runner.md +2026-09-06-standard-hosted-benchmark-runner.md: 9ebf9868ef1ccd7e73e6a5bafac6af5eaf65ffaa +2026-09-06-standard-hosted-benchmark-runner.zh.md: 7f3af61c66939bc14ecdfd70c23431c87493c4f7 diff --git a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.md b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.md rename to .agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.md index af95af6ee8..9ebf9868ef 100644 --- a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.md +++ b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.md @@ -1,6 +1,7 @@ # Agent Note: Standard hosted runner for required benchmarks Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-06-standard-hosted-benchmark-runner.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md rename to .agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md index 47095a5b2e..7f3af61c66 100644 --- a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md +++ b/.agents/notes/archived/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md @@ -1,6 +1,7 @@ # Agent Note: 必需 benchmark 使用标准托管运行器 Status: implemented +Archived: 2026-09-30 [English](2026-09-06-standard-hosted-benchmark-runner.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml new file mode 100644 index 0000000000..0ab4ee9bb8 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.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/archived/testing/2026-09-07-publint-test-subprocess-lifetime.md +2026-09-07-publint-test-subprocess-lifetime.md: 8e3430d1a357b6b903b980c248e469907b85974c +2026-09-07-publint-test-subprocess-lifetime.zh.md: 9f4f20a358c86f2d8d17df7da19a28c03694a231 diff --git a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.md b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.md rename to .agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.md index f8b0e0455f..8e3430d1a3 100644 --- a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.md +++ b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.md @@ -1,6 +1,7 @@ # Agent Note: Publint test subprocesses inherit the execution lane deadline Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-publint-test-subprocess-lifetime.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md rename to .agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md index dd0533752b..9f4f20a358 100644 --- a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md +++ b/.agents/notes/archived/testing/2026-09-07-publint-test-subprocess-lifetime.zh.md @@ -1,6 +1,7 @@ # Agent Note: Publint 测试子进程继承执行通道期限 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-publint-test-subprocess-lifetime.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml new file mode 100644 index 0000000000..921be49b69 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.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/archived/testing/2026-09-07-pwsh-ci-observable-completion.md +2026-09-07-pwsh-ci-observable-completion.md: bb77be62e8d77c10749adb386d5a127eec5e76ac +2026-09-07-pwsh-ci-observable-completion.zh.md: 6516febde66bb05373e29a2fac421835ebccd758 diff --git a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.md b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.md rename to .agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.md index 2cd42296c1..bb77be62e8 100644 --- a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.md +++ b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.md @@ -1,6 +1,7 @@ # Agent Note: PowerShell CI completion and profile expectations Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-pwsh-ci-observable-completion.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.zh.md b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.zh.md rename to .agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.zh.md index 9ea83963be..6516febde6 100644 --- a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.zh.md +++ b/.agents/notes/archived/testing/2026-09-07-pwsh-ci-observable-completion.zh.md @@ -1,6 +1,7 @@ # Agent Note: PowerShell CI 完成信号与 profile 预期 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-pwsh-ci-observable-completion.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml new file mode 100644 index 0000000000..817a15b32c --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.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/archived/testing/2026-09-07-subagent-teardown-test-budgets.md +2026-09-07-subagent-teardown-test-budgets.md: 2a1049bdecfaabc172bb607fab4b25c7845d2acd +2026-09-07-subagent-teardown-test-budgets.zh.md: 2650b3057fe35aab5cfe73b85e40851644e58e24 diff --git a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.md b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.md rename to .agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.md index 5cb6e4662a..2a1049bdec 100644 --- a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.md +++ b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.md @@ -1,6 +1,7 @@ # Agent Note: Subagent teardown tests inherit their execution lane budgets Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-07-subagent-teardown-test-budgets.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.zh.md b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.zh.md rename to .agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.zh.md index 66a1cc0924..2650b3057f 100644 --- a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.zh.md +++ b/.agents/notes/archived/testing/2026-09-07-subagent-teardown-test-budgets.zh.md @@ -1,6 +1,7 @@ # Agent Note: 子代理清理测试继承执行通道的时间预算 Status: implemented +Archived: 2026-09-30 [English](2026-09-07-subagent-teardown-test-budgets.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.i18n.yaml b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.i18n.yaml new file mode 100644 index 0000000000..fb6ec305ff --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.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/archived/testing/2026-09-08-ci-completion-observations.md +2026-09-08-ci-completion-observations.md: c2d8fa0f0fe9e691cfa6e0d87c9fda0a8b5abdfe +2026-09-08-ci-completion-observations.zh.md: 97d64030ffa6f532904c440226c46e3d757a0f92 diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.md b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-08-ci-completion-observations.md rename to .agents/notes/archived/testing/2026-09-08-ci-completion-observations.md index 30145f26fc..c2d8fa0f0f 100644 --- a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.md +++ b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.md @@ -1,6 +1,7 @@ # Agent Note: CI fixture completion and isolation Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-ci-completion-observations.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.zh.md b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-08-ci-completion-observations.zh.md rename to .agents/notes/archived/testing/2026-09-08-ci-completion-observations.zh.md index a819d6ad45..97d64030ff 100644 --- a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.zh.md +++ b/.agents/notes/archived/testing/2026-09-08-ci-completion-observations.zh.md @@ -1,6 +1,7 @@ # Agent Note: CI fixture 的完成与隔离 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-ci-completion-observations.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.i18n.yaml b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.i18n.yaml new file mode 100644 index 0000000000..07c045bd3d --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.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/archived/testing/2026-09-08-ci-readiness-and-completion.md +2026-09-08-ci-readiness-and-completion.md: 1abe3e2b2f655b0622ee0288800364a473f51c84 +2026-09-08-ci-readiness-and-completion.zh.md: 8bac86c815d3343129ae0294debe680a076514b6 diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.md b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.md rename to .agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.md index 1bf4f701f1..1abe3e2b2f 100644 --- a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.md +++ b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.md @@ -1,6 +1,7 @@ # Agent Note: CI assertions wait for owned completion Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-08-ci-readiness-and-completion.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.zh.md b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.zh.md rename to .agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.zh.md index ae6d4e0195..8bac86c815 100644 --- a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.zh.md +++ b/.agents/notes/archived/testing/2026-09-08-ci-readiness-and-completion.zh.md @@ -1,6 +1,7 @@ # Agent Note: CI 断言等待所属操作完成 Status: implemented +Archived: 2026-09-30 [English](2026-09-08-ci-readiness-and-completion.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml new file mode 100644 index 0000000000..616cb322f2 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.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/archived/testing/2026-09-09-user-patch-hmr-test-delivery.md +2026-09-09-user-patch-hmr-test-delivery.md: 0c395b86ecaf07fe0f75432428c1b2b4f19ca4c5 +2026-09-09-user-patch-hmr-test-delivery.zh.md: 1c479e29885c9e4c99c77891e597ec7e73362191 diff --git a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md rename to .agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.md index 28fd99621a..0c395b86ec 100644 --- a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md +++ b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.md @@ -1,6 +1,7 @@ # Agent Note: User-patch tests control filesystem event delivery Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-09-user-patch-hmr-test-delivery.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md rename to .agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md index 3a57b6cc88..1c479e2988 100644 --- a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md +++ b/.agents/notes/archived/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md @@ -1,6 +1,7 @@ # Agent Note: 用户 patch 测试控制文件系统事件投递 Status: implemented +Archived: 2026-09-30 [English](2026-09-09-user-patch-hmr-test-delivery.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml new file mode 100644 index 0000000000..9034f92d65 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.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/archived/testing/2026-09-10-hosted-image-test-assumptions.md +2026-09-10-hosted-image-test-assumptions.md: 784d31b19f0cec2519fc335e7f5d3df89b855285 +2026-09-10-hosted-image-test-assumptions.zh.md: 6b605fe7d96e27f36af98cfbcc5ddf6fd9a6a4e9 diff --git a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.md b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.md rename to .agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.md index e6fe4e3584..784d31b19f 100644 --- a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.md +++ b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.md @@ -1,6 +1,7 @@ # Agent Note: Hosted-image assumptions in the coverage suite Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-10-hosted-image-test-assumptions.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.zh.md b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.zh.md rename to .agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.zh.md index 4fe8bf2a37..6b605fe7d9 100644 --- a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.zh.md +++ b/.agents/notes/archived/testing/2026-09-10-hosted-image-test-assumptions.zh.md @@ -1,6 +1,7 @@ # Agent Note: coverage 套件里的托管镜像假设 Status: implemented +Archived: 2026-09-30 [English](2026-09-10-hosted-image-test-assumptions.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml new file mode 100644 index 0000000000..d1df8805a6 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.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/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md +2026-09-12-connection-and-compaction-fixture-preconditions.md: ab7e0989db489e7f457a56061567e49886feb58a +2026-09-12-connection-and-compaction-fixture-preconditions.zh.md: 8810fc279955dcd7d0fb035960259e0824aec288 diff --git a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md rename to .agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md index 9b97657c77..ab7e0989db 100644 --- a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md +++ b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md @@ -1,6 +1,7 @@ # Agent Note: Connection and compaction fixture preconditions Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-12-connection-and-compaction-fixture-preconditions.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md rename to .agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md index bc73a0e84a..8810fc2799 100644 --- a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md +++ b/.agents/notes/archived/testing/2026-09-12-connection-and-compaction-fixture-preconditions.zh.md @@ -1,6 +1,7 @@ # Agent Note: 连接与压缩测试夹具的前置条件 Status: implemented +Archived: 2026-09-30 [English](2026-09-12-connection-and-compaction-fixture-preconditions.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml new file mode 100644 index 0000000000..8c4e4c2cdb --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.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/archived/testing/2026-09-26-web-conversation-clock-fixture-day.md +2026-09-26-web-conversation-clock-fixture-day.md: 1984219fc0850ca59c64b4f2864f790a63164d84 +2026-09-26-web-conversation-clock-fixture-day.zh.md: 6ecdb4cfbefe49fbd15ebea1d5a59f2cc1595ef2 diff --git a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.md similarity index 96% rename from .agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md rename to .agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.md index 8b572d43f3..1984219fc0 100644 --- a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md +++ b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.md @@ -1,6 +1,7 @@ # Agent Note: Web conversation clocks on the fixture day Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-26-web-conversation-clock-fixture-day.zh.md) @@ -32,7 +33,7 @@ The steering mid capture waits for that settled question title before it capture **Pin the clocks for the whole lane.** Pinning inside `launchWebScaffold` and `newEnglishPage` would cover every scenario at once, including the ones that fail this way in future. It loses on blast radius: 83 goldens carry a clock and session-tree goldens assert literal relative ages, so a lane-wide now moves every scenario away from its own session and fixture times simultaneously, and each would still need its own anchor to read the buckets it was recorded with. -**Keep scenarios clear of local midnight.** Scheduling or refusing runs near the boundary leaves the dependency in place and fails the run instead of the code under test. The [CI readiness and completion decision](2026-09-08-ci-readiness-and-completion.md) already rejects budgets and retries as substitutes for a controlled observation. +**Keep scenarios clear of local midnight.** Scheduling or refusing runs near the boundary leaves the dependency in place and fails the run instead of the code under test. The [CI test reliability workflow](../../../skills/dsh-ci-test-reliability/SKILL.md#reject-flake-masking-fixes) rejects budgets and retries as substitutes for a controlled observation. ## Verification diff --git a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md similarity index 97% rename from .agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md rename to .agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md index 4631d36b8a..6ecdb4cfbe 100644 --- a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md +++ b/.agents/notes/archived/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web conversation clocks on the fixture day Status: implemented +Archived: 2026-09-30 [English](2026-09-26-web-conversation-clock-fixture-day.md) | 中文 @@ -32,7 +33,7 @@ steering 的 mid 捕获在截图前等待该稳定标题出现;[整队场景]( **为整条通道固定时钟。** 在 `launchWebScaffold` 与 `newEnglishPage` 内固定时钟可以一次覆盖所有场景,包括将来以同样方式失败的场景。它因影响面而落选:83 个 golden 带有时钟,且会话树 golden 断言字面相对时间,因此通道级的 now 会同时把所有场景移离各自的会话与 fixture 时间,而每个场景仍需自己的锚点才能读出录制时的分桶。 -**让场景避开本地午夜。** 在边界附近调度或拒绝运行会把依赖留在原处,并让运行而非被测代码失败。[CI 就绪与完成决策](2026-09-08-ci-readiness-and-completion.zh.md) 已经拒绝用预算或重试替代受控观察。 +**让场景避开本地午夜。** 在边界附近调度或拒绝运行会把依赖留在原处,并让运行而非被测代码失败。[CI 测试可靠性工作流](../../../skills/dsh-ci-test-reliability/SKILL.md#reject-flake-masking-fixes)拒绝用预算或重试替代受控观察。 ## Verification diff --git a/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml new file mode 100644 index 0000000000..ab7c789c31 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.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/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md +2026-09-27-oxlint-contract-spawn-case-budgets.md: ccfaa054a5c201350e0c92ef75c9ae62ebe3013f +2026-09-27-oxlint-contract-spawn-case-budgets.zh.md: 7170e37ef876b595d57b27e28bf53849116102ef diff --git a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md similarity index 89% rename from .agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md rename to .agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md index fc4e47fdf8..ccfaa054a5 100644 --- a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md +++ b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md @@ -1,6 +1,7 @@ # Agent Note: Oxlint contract cases that spawn a process carry a 90 s ceiling Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-27-oxlint-contract-spawn-case-budgets.zh.md) @@ -16,7 +17,7 @@ Every case in the file that spawns oxlint or the repository lint entrypoint carr **A `describe`-level `{ timeout: 90_000 }`.** Rejected: case-level values already bound seven cases and take precedence over a `describe` value, so a suite-level number would govern only the remaining seven, including the two configuration-only cases that spawn nothing, and the file would carry two budget styles. Per-case values keep one style, one place to read each case's ceiling, and the default on the cases that spawn nothing. Either form overrides the lane's `--testTimeout` rather than yielding to it (#2677). The archived [translation-pairing-merge](../../archived/testing/2026-08-27-translation-pairing-merge-budget.md) and [Lefthook budget](../../archived/testing/2026-08-29-windows-lane-hook-and-lefthook-budget.md) notes chose the `describe` form for files whose cases carried no constants of their own, because a case added later without an allowance inherits the default; this file already carried per-case constants, so that risk is accepted here and named under Consequences. -**Passing `--testTimeout 90000` to the darwin parity job and letting the cases inherit the lane budget**, the form the [subagent teardown budgets](2026-09-07-subagent-teardown-test-budgets.md) note prefers for suites bound by process creation. Rejected: the cost is the file's own tsx boot plus two oxlint runs, not a darwin property, so a developer's plain `pnpm run test` would keep the default; the parity job exists to run the same command developers run; and seven cases already carry constants, so a lane flag would split the file's budget between two sources. +**Passing `--testTimeout 90000` to the darwin parity job and letting the cases inherit the lane budget**, the form the [CI test reliability workflow](../../../skills/dsh-ci-test-reliability/SKILL.md#budget-timeouts-against-the-lane) prefers for suites bound by process creation. Rejected: the cost is the file's own tsx boot plus two oxlint runs, not a darwin property, so a developer's plain `pnpm run test` would keep the default; the parity job exists to run the same command developers run; and seven cases already carry constants, so a lane flag would split the file's budget between two sources. **Raising Vitest's global `testTimeout`.** Rejected: the whole unit inventory runs on the default; widening it to absorb one file's process spawns removes the hang detection the default provides everywhere else. diff --git a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md similarity index 88% rename from .agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md rename to .agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md index 2b04cfe058..7170e37ef8 100644 --- a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md +++ b/.agents/notes/archived/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md @@ -1,6 +1,7 @@ # Agent Note: 会 spawn 进程的 Oxlint 契约用例携带 90 s 上限 Status: implemented +Archived: 2026-09-30 [English](2026-09-27-oxlint-contract-spawn-case-budgets.md) | 中文 @@ -16,7 +17,7 @@ Status: implemented **`describe` 级 `{ timeout: 90_000 }`。** 拒绝:用例级取值已经约束 7 个用例并且优先于 `describe` 级取值,因此套件级数字只会作用于其余 7 个用例,包括两个不 spawn 的纯配置用例,而且文件会同时带两种预算风格。用例级取值保持一种风格,每个用例的上限只在一处可读,不 spawn 的用例保持默认值。两种形式都覆盖通道的 `--testTimeout` 而不是让位于它(#2677)。已归档的 [translation-pairing-merge](../../archived/testing/2026-08-27-translation-pairing-merge-budget.md) 与 [Lefthook 预算](../../archived/testing/2026-08-29-windows-lane-hook-and-lefthook-budget.md)两篇 Note 为用例本身不带常量的文件选择了 `describe` 形式,理由是之后新增而未带余量的用例会继承默认值;本文件已经带有用例级常量,因此这里接受该风险并在「后果」中点名。 -**给 darwin parity job 传入 `--testTimeout 90000`,让用例继承通道预算**,这是[子代理清理预算](2026-09-07-subagent-teardown-test-budgets.zh.md)一篇对进程创建受限套件偏好的形式。拒绝:成本来自本文件自身的 tsx 引导加两次 oxlint 运行,不是 darwin 的属性,因此开发者直接运行的 `pnpm run test` 仍会停留在默认值;parity job 的存在意义就是运行与开发者相同的命令;而且 7 个用例已带常量,通道旗标会把本文件的预算拆到两个来源。 +**给 darwin parity job 传入 `--testTimeout 90000`,让用例继承通道预算**,这是 [CI 测试可靠性工作流](../../../skills/dsh-ci-test-reliability/SKILL.md#budget-timeouts-against-the-lane)对进程创建受限套件偏好的形式。拒绝:成本来自本文件自身的 tsx 引导加两次 oxlint 运行,不是 darwin 的属性,因此开发者直接运行的 `pnpm run test` 仍会停留在默认值;parity job 的存在意义就是运行与开发者相同的命令;而且 7 个用例已带常量,通道旗标会把本文件的预算拆到两个来源。 **抬高 Vitest 全局 `testTimeout`。** 拒绝:整个单元测试清单都跑在默认值上;为吸收一个文件的进程 spawn 而放宽它,会移除默认值在其他所有地方提供的挂起检测。 diff --git a/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml new file mode 100644 index 0000000000..a5e0fb35d4 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.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/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.md +2026-09-27-plugin-install-interceptor-lifetime.md: b1f1a5641d78483bc1954513ecfcf841951efe01 +2026-09-27-plugin-install-interceptor-lifetime.zh.md: 1e9ff9090086d08226f2bfd0b216610b27097b69 diff --git a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.md similarity index 92% rename from .agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md rename to .agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.md index 1db162d210..b1f1a5641d 100644 --- a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md +++ b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.md @@ -1,6 +1,7 @@ # Agent Note: Keep plugin-install interception for the page lifetime Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-27-plugin-install-interceptor-lifetime.zh.md) @@ -12,7 +13,7 @@ The [plugin-install scenario](../../../../apps/web/tests/plugin-install-cancel.e The scenario registers its `installBundle` and `cancelInstall` interceptors before navigation and retains them until the browser closes. Each interceptor calls a typed handler that the scenario replaces when it changes phases. The delivery and cancellation barriers retain their ordering, and `activeReplySettled` joins the lost-response handler before the next installation starts. Ordinary requests still reach the real Host. -The [input-state decision](2026-09-27-web-lane-assertions-name-their-input-state.md) owns the separate wait for the highlighted card's status to become `running`. Keeping interception active lets the directory read complete; polling its result still establishes the state the golden records. +The [scenario](../../../../apps/web/tests/plugin-install-cancel.e2e.ts) separately waits for the highlighted card's status to become `running`. Keeping interception active lets the directory read complete; polling its result still establishes the state the golden records. ## Alternatives considered diff --git a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md similarity index 93% rename from .agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md rename to .agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md index 942a201fc8..1e9ff90900 100644 --- a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md +++ b/.agents/notes/archived/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md @@ -1,6 +1,7 @@ # Agent Note: 让插件安装拦截器持续到页面关闭 Status: implemented +Archived: 2026-09-30 [English](2026-09-27-plugin-install-interceptor-lifetime.md) | 中文 @@ -12,7 +13,7 @@ Status: implemented 场景在导航前注册 `installBundle` 和 `cancelInstall` 拦截器,并保留到浏览器关闭。每个拦截器调用一个带类型的处理函数,场景切换阶段时替换该函数。请求交付和取消屏障保持原有顺序,`activeReplySettled` 在下一次安装开始前等待丢失响应的处理函数结束。普通请求仍然到达真实 Host。 -[输入状态决策](2026-09-27-web-lane-assertions-name-their-input-state.zh.md) 负责另一项等待:高亮卡片的状态变为 `running`。持续启用拦截使目录读取能够完成;轮询其结果仍负责确立预期输出记录的状态。 +[该场景](../../../../apps/web/tests/plugin-install-cancel.e2e.ts)还会单独等待高亮卡片的状态变为 `running`。持续启用拦截使目录读取能够完成;轮询其结果仍负责确立预期输出记录的状态。 ## Alternatives considered diff --git a/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml new file mode 100644 index 0000000000..befb1f5dcd --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.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/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.md +2026-09-27-web-lane-assertions-name-their-input-state.md: 29c15d688ca18c7b0b05f934fd70b9984213d854 +2026-09-27-web-lane-assertions-name-their-input-state.zh.md: 9b95e99c8e5e023e5caeda7395c087a38f4a8a41 diff --git a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.md b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.md rename to .agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.md index 69d2c08091..29c15d688c 100644 --- a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.md +++ b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.md @@ -1,6 +1,7 @@ # Agent Note: Web lane assertions name their input state Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-27-web-lane-assertions-name-their-input-state.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md rename to .agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md index cbe39c1180..9b95e99c8e 100644 --- a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md +++ b/.agents/notes/archived/testing/2026-09-27-web-lane-assertions-name-their-input-state.zh.md @@ -1,6 +1,7 @@ # Agent Note: Web lane assertions name their input state Status: implemented +Archived: 2026-09-30 [English](2026-09-27-web-lane-assertions-name-their-input-state.md) | 中文 diff --git a/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml new file mode 100644 index 0000000000..01aeb49b51 --- /dev/null +++ b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.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/archived/testing/2026-09-28-detail-close-waits-on-observed-state.md +2026-09-28-detail-close-waits-on-observed-state.md: d77fb560ab205e0663df3a8b20417368ec6c8988 +2026-09-28-detail-close-waits-on-observed-state.zh.md: 9c7d1524360b3d12d8fb37397c7da1581ade06a4 diff --git a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.md b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.md rename to .agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.md index 662782fd4b..d77fb560ab 100644 --- a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.md +++ b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.md @@ -1,6 +1,7 @@ # Agent Note: The task-detail close waits on the observed state Status: implemented +Archived: 2026-09-30 English | [中文](2026-09-28-detail-close-waits-on-observed-state.zh.md) diff --git a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md similarity index 99% rename from .agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md rename to .agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md index 92bffd53ba..9c7d152436 100644 --- a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md +++ b/.agents/notes/archived/testing/2026-09-28-detail-close-waits-on-observed-state.zh.md @@ -1,6 +1,7 @@ # Agent Note: 任务详情关闭断言等待它所观测的状态 Status: implemented +Archived: 2026-09-30 [English](2026-09-28-detail-close-waits-on-observed-state.md) | 中文 diff --git a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml deleted file mode 100644 index 5e1614518c..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-11-content-block-vocabulary.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md -/agent-note-provider-neutral-content-block-vocabulary-owned-by-dsh-llm: - en: 64935f5c8e315004 - zh: 6a9cbb4578eaa437 -/agent-note-provider-neutral-content-block-vocabulary-owned-by-dsh-llm/problem: - en: 4e81df7bca1c1638 - zh: f7483d180a613237 -/agent-note-provider-neutral-content-block-vocabulary-owned-by-dsh-llm/decision: - en: 9526dfcf4da1d000 - zh: ea2bf79a02b26b39 -/agent-note-provider-neutral-content-block-vocabulary-owned-by-dsh-llm/alternatives-considered: - en: 1de2e19e711105e6 - zh: a2cd5a64cc4e9e73 -/agent-note-provider-neutral-content-block-vocabulary-owned-by-dsh-llm/consequences: - en: 999d10e69e447e44 - zh: 38370da5b900ac30 diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml index d01dd83fdb..cc4e1e124c 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.i18n.yaml @@ -2,36 +2,33 @@ # section, a hash of its English and Chinese blocks outside code blocks and generated regions. # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md -/agent-note-source-owned-session-immutability-and-dev-mode-invariants: - en: 052aa271e87074c0 - zh: 3ccffc50fc1a28d4 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/problem: - en: 6ea85a4531d5e59f - zh: b4b568eb347f016a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision: - en: 6e701ec64c3bbfae - zh: b8ed0f1cb7a0477a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/session-owns-immutable-history: - en: ff47e58c377a8dc0 - zh: 5edbe4f3642a3fe6 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/derived-requests-remain-detached: +/agent-note-source-owned-session-immutability: + en: cf33ffdd07df7257 + zh: 3d6895c9c80b0b1f +/agent-note-source-owned-session-immutability/problem: + en: e5d87f6c5aef2dad + zh: 9f7f1459d99c523c +/agent-note-source-owned-session-immutability/decision: + en: 821a886d365a2271 + zh: 4ba5d74a7772150d +/agent-note-source-owned-session-immutability/decision/session-owns-immutable-history: + en: 76fc0d8548d192c1 + zh: 34c610fff5beb455 +/agent-note-source-owned-session-immutability/decision/derived-requests-remain-detached: en: 975df574ee6e6df8 zh: 96d765058482835a -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/package-owned-invariant-companions-check-relationships: - en: a88e3a896eedca91 - zh: 03e96c7870cba932 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered: +/agent-note-source-owned-session-immutability/alternatives-considered: en: 6119e2aff3b6be5a zh: 76136ef5249c23eb -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/pervasive-deep-readonly-types: +/agent-note-source-owned-session-immutability/alternatives-considered/pervasive-deep-readonly-types: en: ec54af2931616616 zh: 5fd9bc469a6fd3a5 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/development-only-freezing: - en: 85591197bb62eabf - zh: ba69b9ccf596a6d7 -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/clone-only-when-deriving-messages: +/agent-note-source-owned-session-immutability/alternatives-considered/development-only-freezing: + en: 1857fb245ec3f086 + zh: 6d3324c872627bf6 +/agent-note-source-owned-session-immutability/alternatives-considered/clone-only-when-deriving-messages: en: a9848dec50664b92 zh: 7c558550b054627b -/agent-note-source-owned-session-immutability-and-dev-mode-invariants/consequences: - en: bcccf7d464a070db - zh: eccd442a21e2c99e +/agent-note-source-owned-session-immutability/consequences: + en: d26609b76b9eea01 + zh: cf046d5c7173b738 diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md index 0784960397..8265c7980a 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md @@ -1,4 +1,4 @@ -# Agent Note: Source-owned session immutability and dev-mode invariants +# Agent Note: Source-owned session immutability Status: implemented @@ -6,17 +6,17 @@ English | [中文](2026-06-11-dev-invariants-over-deep-readonly.zh.md) ## Problem -The session log needs two different protections: immutable ownership of each stored fact, and checks for relationships among facts across time and service contracts. Conflating them in an optional development plugin would leave production history vulnerable; trying to express both through TypeScript readonly types would not create a runtime boundary or describe relational rules. +The session log needs immutable ownership of each stored fact. Making that protection an optional development plugin would leave production history vulnerable; expressing it through TypeScript readonly types would not create a runtime boundary. The session log is the durable source of truth for replay, request reconstruction, persistence, and user-visible history. Code outside the session package must be able to inspect that history without retaining a reference that can rewrite it later, and inputs accepted from callers must not remain connected to caller-owned mutable objects. -Immutability of individual values is only half of the contract. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services and cannot be established by freezing one object. +Immutability of individual values does not establish relationships among records. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services, cannot be established by freezing one object, and are outside this decision. TypeScript readonly types are not a sufficient runtime boundary. They disappear when the program runs, a cast can bypass them, and a recursive `DeepReadonly` would spread through every log and message consumer even though some downstream request-processing APIs intentionally work with mutable values. ## Decision -Responsibility is split between an always-on storage boundary and optional development assertions. +`Session` owns an always-on storage boundary. ### Session owns immutable history @@ -24,18 +24,12 @@ Responsibility is split between an always-on storage boundary and optional devel The accepted event and all of its descendants are deep-frozen before publication. `append()` returns that owned frozen event, and `session/event` observers and `eventAt(seq)` receive the same record. `snapshotEvents(fromSeq?, toSeqExclusive?)` returns a frozen array snapshot; a previously returned array does not grow after a later append. `seq` reads the current length without materializing an array. Synchronous historical readers are deprecated under the [event-read policy](2026-09-09-deprecate-synchronous-session-event-reads.md). Seed records pass through the same validation, snapshot, and freeze boundary before construction succeeds. -This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics whether or not development support plugins are registered. +This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics regardless of which plugins are registered. ### Derived requests remain detached `deriveMessages()` projects logged surface events into detached, deep-frozen `Message` objects and returns a fresh array snapshot. Request assembly can therefore combine derived history with other inputs without exposing a path back into the log. The cache reuses safe immutable projections rather than recloning the complete history for each model call. -### Package-owned invariant companions check relationships - -`dsh-invariants` registers the configurable `ctx.invariants` service and contains no product checks. A package publishes a `./invariant` ownership companion only for an independently observable runtime relationship; packages without one omit the companion and record the reason in their README. `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop` provide the initial rules that require trace state or observation of another seam: monotonic sequence numbers, turn and step nesting, tool-call/result pairing, legal agent-status transitions, subject-correct scoped dispatch, and equality between a loop-built request and the request reconstructed from its session-log prefix. Global enablement and package-name regex filters belong to the service ([package-owned invariant service](../../archived/architecture/2026-07-19-package-owned-invariant-service.md); [omission decision](../simplification/2026-08-28-omit-unneeded-invariant-companions.md)). - -When the session companion attaches to an existing or seeded session, it replays the immutable log to rebuild trace state. The service gives each contribution a disposable child fiber, so hot reload is safe in the middle of a turn without giving diagnostics ownership of session storage. - ## Alternatives considered ### Pervasive deep-readonly types @@ -44,7 +38,7 @@ A rejected companion proposal would apply a recursive `DeepReadonly` type acr ### Development-only freezing -Freezing history only when an invariants plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on, while the more expensive relational checks remain opt-in development support. +Freezing history only when a development plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on. ### Clone only when deriving messages @@ -55,6 +49,4 @@ Detaching `deriveMessages()` would protect the most common request path but leav - Every accepted live or seeded session event is detached from caller-owned inputs and deeply immutable before any observer can receive it. - Existing `snapshotEvents()` and `eventAt()` callers retain immutable read results while their migration is deferred; `seq` reads the log length without copying the array. - Request-side mutation cannot reach stored history through derived messages. -- Development builds can enable relational assertions without changing storage behavior, and disposing or filtering a companion does not weaken log immutability. -- `dsh-invariants` configures global enablement plus package allow/block regex lists; each check remains owned and tested by its product package. - The runtime boundary carries a recursive snapshot-and-freeze cost once per accepted event; later readers and cached projections reuse the owned immutable records. diff --git a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md index 7183813130..10077cc24b 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md @@ -1,4 +1,4 @@ -# Agent Note: 源端拥有的会话不可变性与开发模式不变式 +# Agent Note: 源端拥有的会话不可变性 Status: implemented @@ -6,17 +6,17 @@ Status: implemented ## 问题 -会话日志需要两种不同的保护:对每条已存储事实的不可变所有权,以及对跨时间和服务约定的事实之间关系的检查。如果将二者混为一个可选的开发插件,生产环境的历史记录将失去保护;如果试图通过 TypeScript readonly 类型同时表达两者,既无法建立运行时边界,也无法描述关系规则。 +会话日志需要对每条已存储事实的不可变所有权。如果将这项保护做成可选的开发插件,生产环境的历史记录将失去保护;如果通过 TypeScript readonly 类型表达它,则无法建立运行时边界。 会话日志是回放、请求重建、持久化与用户可见历史的持久真源。会话包外部的代码必须能检视历史,但不能保留一个可在之后改写历史的引用;从调用方接受的输入也不能继续连接到调用方拥有的可变对象。 -单个值的不可变性只是约定的一半。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立。 +单个值的不可变性不能建立记录之间的关系。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立,也不属于本决策。 TypeScript readonly 类型不是充分的运行时边界。它们在程序运行时消失,类型转换可以绕过它们,而递归的 `DeepReadonly` 会扩散到每个日志和消息消费方,尽管某些下游请求处理 API 有意使用可变值。 ## 决策 -职责划分给始终启用的存储边界和可选的开发断言。 +`Session` 拥有始终启用的存储边界。 ### Session 拥有不可变历史 @@ -24,18 +24,12 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 被接受的事件及其所有后代在发布前被深度冻结。`append()` 返回由 Session 拥有的冻结事件,`session/event` 观察者和 `eventAt(seq)` 接收同一记录。`snapshotEvents(fromSeq?, toSeqExclusive?)` 返回冻结的数组快照;先前返回的数组不会因后续 append 而增长。`seq` 无需物化数组即可读取当前长度。同步历史读取方法按[事件读取策略](2026-09-09-deprecate-synchronous-session-event-reads.zh.md)弃用。种子记录在构造成功前经过相同的验证、快照与冻结边界。 -此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论是否注册了开发支持插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。 +此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论注册了哪些插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。 ### 派生请求保持分离 `deriveMessages()` 将已记录的表面事件投影为分离的、深度冻结的 `Message` 对象,并返回一份新的数组快照。因此请求组装可以将派生历史与其他输入组合,而不会暴露一条回到日志的路径。缓存复用安全的不可变投影,而非为每次模型调用重新克隆完整历史。 -### 包拥有的不变式配套插件检查关系 - -`dsh-invariants` 注册可配置的 `ctx.invariants` 服务,本身不包含产品检查。只有拥有可独立观察的运行时关系时,包才发布 `./invariant` 所有权配套插件;没有该关系的包会省略 companion 并在 README 中记录原因。`dsh-session`、`dsh-agent`、`dsh-scope` 和 `dsh-agent-loop` 提供首批需要跟踪状态或观察另一个 seam 的规则:单调递增的序列号、轮次与步骤嵌套、工具调用/结果配对、合法的 agent(智能体)状态转换、主体正确的作用域分发,以及循环构建的请求与从其会话日志前缀重建的请求之间的相等性。全局启用和包名 regex 过滤器归该服务所有(见[包拥有的不变式服务](../../archived/architecture/2026-07-19-package-owned-invariant-service.md)与[省略决策](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md))。 - -当会话配套插件附加到已有会话或以种子记录初始化的会话时,它回放不可变日志以重建跟踪状态。服务为每项贡献提供一个可 dispose(资源释放)的子 fiber,因此轮次中途热重载是安全的,同时不赋予诊断逻辑对会话存储的所有权。 - ## 曾考虑的替代方案 ### 全面的 deep-readonly 类型 @@ -44,7 +38,7 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 ### 仅在开发模式冻结 -仅当不变式插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用,而开销更大的关系检查则保持为可选的开发支持。 +仅当开发插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用。 ### 仅在派生消息时克隆 @@ -55,6 +49,4 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行 - 每个被接受的实时或种子会话事件在任何观察者接收之前,都已从调用方拥有的输入中分离并深度不可变。 - 现有 `snapshotEvents()` 和 `eventAt()` 调用方在暂缓迁移期间仍获得不可变的读取结果;`seq` 无需复制数组即可读取日志长度。 - 请求侧的修改无法通过派生消息触及已存储的历史。 -- 开发构建可以启用关系断言而不改变存储行为;dispose 或过滤一个配套插件不会削弱日志不可变性。 -- `dsh-invariants` 配置全局启用状态以及包名允许/阻止 regex 列表;每项检查仍由其产品包拥有并测试。 - 运行时边界对每个被接受的事件产生一次递归快照与冻结的开销;后续读取者和缓存投影复用已拥有的不可变记录。 diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml index 60451f71cc..d26c1de298 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml @@ -15,5 +15,5 @@ en: 73541e0833eb9559 zh: ddca382d54e498e5 /agent-note-event-sourced-sessions-with-derived-message-history/consequences: - en: d2ea77a5c8e30949 - zh: 19dc61cb0d29315f + en: 0b534fa20cdf9711 + zh: 0a89b988816b9266 diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md index b6d17d2db1..8d6a937147 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md @@ -24,5 +24,5 @@ Ordering contract: the loop claims inbox messages before `agent/pre-step`, opens - Replay, trace, and telemetry are structurally guaranteed, not bolted on. - Persistence stays a plugin concern; the in-memory store ships in dsh-session. -- The event vocabulary is merge-extensible (plugins add e.g. compaction events); [session persistence](2026-06-14-session-persistence.md) froze its shape once the log became durable. +- The event vocabulary is merge-extensible (plugins add e.g. compaction events); [session persistence](../../../../packages/session/session-persistence/README.md) froze its shape once the log became durable. - Derivation cost grows with log length — compaction (dsh-compaction) is the intended mitigation, not log mutation. diff --git a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md index 11e5740dc2..028332a7cf 100644 --- a/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.zh.md @@ -24,5 +24,5 @@ MVP 要求严格的基于事件的追踪,以及完全可回放的会话(严 - 回放、追踪与遥测在结构上得到保证,而非事后附加。 - 持久化仍是插件关注点;内存存储随 dsh-session 一起提供。 -- 事件词汇可通过合并扩展(插件可添加如压缩(compaction)事件);[会话持久化](2026-06-14-session-persistence.zh.md)在日志具备持久性后固定了其结构。 +- 事件词汇可通过合并扩展(插件可添加如压缩(compaction)事件);[会话持久化](../../../../packages/session/session-persistence/README.zh.md)在日志具备持久性后固定了其结构。 - 派生成本随日志长度增长,压缩(dsh-compaction)是预期的缓解手段,而不是改写日志。 diff --git a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml deleted file mode 100644 index f23e22ad36..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-11-microkernel-event-taxonomy.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md -/agent-note-microkernel-extension-via-cordis-event-taxonomy-one-concrete-loop: - en: e5775d7ec6f1cdc3 - zh: a364697f860a545a -/agent-note-microkernel-extension-via-cordis-event-taxonomy-one-concrete-loop/problem: - en: 3bc74749db819441 - zh: 19b5497ed3a9378b -/agent-note-microkernel-extension-via-cordis-event-taxonomy-one-concrete-loop/decision: - en: 2153b0cb5773cb08 - zh: 82d7ad1b250cd52d -/agent-note-microkernel-extension-via-cordis-event-taxonomy-one-concrete-loop/alternatives-considered: - en: e528c5a20e0c55a3 - zh: 004d3bf06241cc26 -/agent-note-microkernel-extension-via-cordis-event-taxonomy-one-concrete-loop/consequences: - en: 6acb80cdaa7c010f - zh: acca672d8094d985 diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml index 18a5b7dcf2..c9c3750401 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.i18n.yaml @@ -9,8 +9,8 @@ en: 3215928333de66f7 zh: 2c72fcacccf25741 /agent-note-capability-seams-service-definition-service-provider-consumer-roles/decision: - en: 774c54c0d3d9acad - zh: 7dd4bfaf0ba2181b + en: 8cfb86dded79b161 + zh: abe489d686563a14 /agent-note-capability-seams-service-definition-service-provider-consumer-roles/terminology-seam-names-the-trio-not-the-interface: en: 8dca727800bea2f4 zh: 4c4e028520967ca0 diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md index 798739b7ef..a3d599be3d 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md @@ -15,7 +15,7 @@ This is distinct from "who provides vs. needs a capability at runtime", which Co A swappable capability has **three roles**: 1. **Service Definition** — the Cordis `Service` and vocabulary types owning `ctx.` and depending only on the vocabulary the contract needs (e.g. `dsh-shell`: `ShellExecutor`, `ShellRunResult`, `ShellProcess`). A definition may be an abstract class or a concrete registry service; it is never a TypeScript `interface`. -2. **Service Provider** — a plugin that supplies or registers an implementation (e.g. `dsh-bash-local`: subprocesses, provider-managed range termination, spill-file truncation). The [native-containment decision](2026-08-28-subprocess-native-containment.md) owns the local provider's OS-specific range mechanics. Sandboxed and remote providers are sibling packages implementing or registering against the same Service Definition. +2. **Service Provider** — a plugin that supplies or registers an implementation (e.g. `dsh-bash-local`: subprocesses, provider-managed range termination, spill-file truncation). The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns the local provider's OS-specific range mechanics. Sandboxed and remote providers are sibling packages implementing or registering against the same Service Definition. 3. **Consumer** — what the model and plugins program against (e.g. `dsh-tool-bash`: the `bash` schema, with background handles registered into the generic job runtime). Consumers inject the service key and never import provider-specific types. The role names use title case: **Service Definition**, **Service Provider**, and **Consumer**. Generic uses of `provider` and `consumer` remain lowercase. diff --git a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md index c8f568aa56..11bee6b2db 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md @@ -15,7 +15,7 @@ harness 具有可替换的能力,包括 shell 执行和模型提供方。一 一项可替换的能力包含**三个角色**: 1. **Service Definition**——拥有 `ctx.` 的 Cordis `Service` 和词汇类型,仅依赖约定所需的词汇(例如 `dsh-shell`:`ShellExecutor`、`ShellRunResult`、`ShellProcess`)。Service Definition 可以是抽象类,也可以是具体的注册表服务;绝不是 TypeScript `interface`。 -2. **Service Provider**——提供或注册实现的插件(例如 `dsh-bash-local`:子进程、由提供方管理的范围终止、spill 文件截断)。[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责本地提供方的 OS 特有范围机制。沙箱化和远程 Service Provider 是依据同一 Service Definition 实现或注册的兄弟包。 +2. **Service Provider**——提供或注册实现的插件(例如 `dsh-bash-local`:子进程、由提供方管理的范围终止、spill 文件截断)。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责本地提供方的 OS 特有范围机制。沙箱化和远程 Service Provider 是依据同一 Service Definition 实现或注册的兄弟包。 3. **Consumer**——模型和插件编程所面向的内容(例如 `dsh-tool-bash`:`bash` schema,后台句柄注册到通用任务运行时)。Consumer 注入服务键,从不导入 Service Provider 特有的类型。 角色名使用标题式大小写:**Service Definition**、**Service Provider** 和 **Consumer**。泛指的 `provider` 和 `consumer` 仍使用小写。 diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml index 30a57122ad..6d1378ce4a 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.i18n.yaml @@ -6,8 +6,8 @@ en: dcb28a02ba26cb31 zh: 096d0ab60a3ad638 /agent-note-two-llm-adapters-as-a-design-verification-twin/problem: - en: feb8f5213be03a5c - zh: 5ca85048792db7d8 + en: d8c455ccae0fc166 + zh: 801cc040eb67ab5b /agent-note-two-llm-adapters-as-a-design-verification-twin/decision: en: 0dd6ebb29aebc164 zh: 20065ae72f4d131d diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md index fe8b0b55e0..10e7c42e54 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md @@ -6,7 +6,7 @@ English | [中文](2026-06-13-twin-llm-adapters.zh.md) ## Problem -`dsh-llm` owns a provider-neutral streaming vocabulary — the `StreamChunk` protocol (`block-start`, `text-delta`, `reasoning-delta`, `tool-call-delta`, `block-end`, `usage`, `finish`) and the content-block types ([the content-block vocabulary](2026-06-11-content-block-vocabulary.md)). A vocabulary defined against a single adapter risks baking that adapter's quirks into the "neutral" contract: anything the one implementation happens to do becomes the de-facto spec, and the abstraction is unverified until a second provider arrives — by which point the leak is expensive to fix. +`dsh-llm` owns a provider-neutral streaming vocabulary — the `StreamChunk` protocol (`block-start`, `text-delta`, `reasoning-delta`, `tool-call-delta`, `block-end`, `usage`, `finish`) and the content-block types ([the content-block vocabulary](../../../../packages/llm/llm/README.md)). A vocabulary defined against a single adapter risks baking that adapter's quirks into the "neutral" contract: anything the one implementation happens to do becomes the de-facto spec, and the abstraction is unverified until a second provider arrives — by which point the leak is expensive to fix. ## Decision diff --git a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md index 4248b5afeb..b55bd6cbcb 100644 --- a/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -`dsh-llm` 拥有一套提供方无关的流式词汇:`StreamChunk` 协议(`block-start`、`text-delta`、`reasoning-delta`、`tool-call-delta`、`block-end`、`usage`、`finish`)以及内容块类型([内容块词汇](2026-06-11-content-block-vocabulary.zh.md))。如果词汇仅针对单个适配器定义,就有可能将该适配器的特异行为固化到「中立」约定中:唯一实现碰巧做了什么,什么就成为事实上的规范;在第二个提供方到来之前,抽象层未经验证——而届时修复这种泄漏的代价已经很高。 +`dsh-llm` 拥有一套提供方无关的流式词汇:`StreamChunk` 协议(`block-start`、`text-delta`、`reasoning-delta`、`tool-call-delta`、`block-end`、`usage`、`finish`)以及内容块类型([内容块词汇](../../../../packages/llm/llm/README.zh.md))。如果词汇仅针对单个适配器定义,就有可能将该适配器的特异行为固化到「中立」约定中:唯一实现碰巧做了什么,什么就成为事实上的规范;在第二个提供方到来之前,抽象层未经验证——而届时修复这种泄漏的代价已经很高。 ## 决策 diff --git a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml deleted file mode 100644 index 995fba81fe..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-14-session-persistence.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-14-session-persistence.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-14-session-persistence.md -/agent-note-session-persistence-as-an-abstract-service-over-the-existing-sessionevent: - en: d7cf18702f4ed830 - zh: 7e0cc6db7c90a7bd -/agent-note-session-persistence-as-an-abstract-service-over-the-existing-sessionevent/problem: - en: fa1ffb258aee90f6 - zh: c25cead52e72f97d -/agent-note-session-persistence-as-an-abstract-service-over-the-existing-sessionevent/decision: - en: a7076073971de601 - zh: 5ea9a87705340d91 -/agent-note-session-persistence-as-an-abstract-service-over-the-existing-sessionevent/alternatives-considered: - en: e5c9929ca92cc12e - zh: 3e0668f6a822577c -/agent-note-session-persistence-as-an-abstract-service-over-the-existing-sessionevent/consequences: - en: 4f00ed0b90442440 - zh: 994a5b03c862178b diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index db7edf6873..c4bc3be352 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml @@ -27,8 +27,8 @@ en: ee71f9c0f12b6a0e zh: 403b1723f808391d /agent-note-session-surface-an-ordered-projection-over-the-event-log/decision/invariants: - en: 24b1342fb3b5be29 - zh: a62692b0250c072a + en: 98f39e77083d67b7 + zh: 634a9227fc2a7ec9 /agent-note-session-surface-an-ordered-projection-over-the-event-log/alternatives-considered: en: 193157129e65e487 zh: a0a73b8a9cbd78e6 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index 26780d54a1..3ac1707390 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -45,7 +45,7 @@ The `repair.ts` module synthesizes `tool/result` closers for orphaned tool calls ### Invariants -`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules, not optional invariant-service contributions. +`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules. Every surface-eligible event must carry `surfaceOp` or it would disappear from derived history. Typed `append` overloads enforce this for literal event types; runtime checks in `append` and the seed constructor cover widened unions and current loaded logs. Released validation and conversion belong to their versioned migration edges rather than generic Session code; see the [V2-to-V3 placement rules](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes). diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index 328a9ee9f1..d556142fde 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -45,7 +45,7 @@ surface 元数据仅属于四种 surface 事件类型(`system/message`、`user ### 不变式 -`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则,不是由可选 invariant service 提供的规则。 +`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则。 每个可进入 surface 的事件都必须携带 `surfaceOp`,否则它将从派生历史中消失。类型化的 `append` 重载对字面事件类型强制执行此规则;`append` 和种子构造函数中的运行时检查覆盖宽化联合类型和当前已加载日志。已发布格式的校验与转换属于各自版本化迁移边,而不属于通用 Session 代码;参见 [V2 到 V3 位置规则](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)。 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml deleted file mode 100644 index ff239e85c9..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ /dev/null @@ -1,58 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-20-generic-long-running-tool-runtime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools: - en: 56e269b2a2839796 - zh: 8fc971036985ac0d -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/problem: - en: 70e26df62568c337 - zh: a9e6f5f8a5482d0a -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/decision: - en: 277869395ad6f792 - zh: 93f13a97d52f10a5 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/runtime-contract: - en: eeb7ed8eec13668a - zh: 3f236f63c72fbba7 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/authorization-and-owner-lifecycle: - en: 3819a73ca70ecdfd - zh: c1a4f461a4e45978 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/service-api: - en: a1a884816aac3d7f - zh: b21129e5cd93b9a1 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/model-facing-control-api: - en: dd3f4d85d0ec136f - zh: ca4209cb856271dd -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/producer-opt-in: - en: a39343bf6f655051 - zh: d9894d39e7f4d6e1 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/producer-integrations: - en: ae4a6d6e0d83d3bd - zh: 9072890ba9374426 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered: - en: 6119e2aff3b6be5a - zh: e01a6c669a9ec6aa -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/per-capability-control-tools: - en: 9781c302dc72fd8e - zh: a78e1dd8da4d8ca8 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/an-immediate-abstract-task-runtime-backend: - en: 404a5c7c45a71a55 - zh: 213621e4a9618a9c -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/consumer-owned-authorization-or-cleanup-events: - en: d99de803718d9ca6 - zh: e03a97314851ed0f -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/blocking-output-or-a-separate-wait-tool: - en: e3beca50b6a57ce0 - zh: a77663b817d33247 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/runtime-owned-output-sinks: - en: 42eeeba9d84ae681 - zh: 1d540f9740675bee -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/alternatives-considered/random-ids-promotion-or-lifecycle-session-events: - en: e706b1b57241d3d2 - zh: 1779b7fa05f48283 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/testing: - en: e891db1723828da8 - zh: 657a62ed595bd081 -/agent-note-the-background-job-runtime-ctx-jobs-and-generic-task-control-tools/consequences: - en: b95ecd6052465ad3 - zh: 1527c38d1b4631d6 diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml index 2b8a5f380d..8dfd8e352f 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml @@ -6,8 +6,8 @@ en: fb8e336ecd4bf571 zh: 23e40e4cb224163f /agent-note-bounded-recovery-for-transient-llm-request-failures/problem: - en: 83a361538a092dae - zh: e8f33cb95d0d3e09 + en: ac1641f64d0e0a25 + zh: 2ddb6a3fd290ea31 /agent-note-bounded-recovery-for-transient-llm-request-failures/decision: en: a15c7c13f167e5ac zh: 63bcc2ef60c2f344 @@ -21,8 +21,8 @@ en: 57dbdd385f48bb3e zh: 5e15463fae4be582 /agent-note-bounded-recovery-for-transient-llm-request-failures/decision/bound-stalled-streams-where-they-can-be-stopped: - en: f7369086e87192b9 - zh: e2839ed8cc5c23b8 + en: 6a55107caa329eea + zh: 13a76e482739141e /agent-note-bounded-recovery-for-transient-llm-request-failures/decision/keep-attempts-separate-in-the-existing-log: en: 8f57db4bfd6e1c22 zh: 67ac62217bb68570 @@ -33,11 +33,11 @@ en: a6d0306740d470ee zh: 700bd4b0aa58f3e8 /agent-note-bounded-recovery-for-transient-llm-request-failures/verification: - en: db343f335a9d7cad - zh: 49b8300de55dcfeb + en: 7412edfd162c4a28 + zh: 64108a11e6194e35 /agent-note-bounded-recovery-for-transient-llm-request-failures/consequences: en: 08659e8606455ada zh: 862080f8df5f28ec /agent-note-bounded-recovery-for-transient-llm-request-failures/related: - en: e85a6ad7387c881b - zh: bb8fb4173ffe613d + en: f2c8539b6836eba1 + zh: 7c292611d09665a7 diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md index b0a4ebb11b..8687f9d524 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md @@ -8,7 +8,7 @@ The [per-provider request retry policy](../../archived/feature/2026-07-24-provid ## Problem -Provider adapters can fail by throwing during dispatch or iteration or by ending with `finish { kind: 'error' | 'aborted' }`. The final adapter boundary normalizes thrown values to that terminal finish protocol before `dsh-agent-loop` receives them; middleware and result-processing defects remain thrown. The loop offers a terminal model-request failure to `agent/request-error`. An unhandled failure is terminal; a handling listener repairs policy-owned state, returns `{ kind: 'retry' }`, and stops waterfall delegation. The [retry-action decision](../simplification/2026-07-27-request-error-retry-action.md) owns this return contract. +Provider adapters can fail by throwing during dispatch or iteration or by ending with `finish { kind: 'error' | 'aborted' }`. The final adapter boundary normalizes thrown values to that terminal finish protocol before `dsh-agent-loop` receives them; middleware and result-processing defects remain thrown. The loop offers a terminal model-request failure to `agent/request-error`. An unhandled failure is terminal; a handling listener repairs policy-owned state, returns `{ kind: 'retry' }`, and stops waterfall delegation. The [retry-action reference](../../../../packages/core/agent/README.md) owns this return contract. That boundary is already safe for another request attempt. Each failed stream commits one log-only `assistant/attempt` with its exact compact stream, message derivation ignores it, tool calls are dispatched only after a successful terminal finish and assembled `assistant/message`, and a retry reconstructs its next attempt from the durable surface. The harness therefore does not need a second response lifecycle or tentative-output protocol to keep two attempts separate. @@ -78,7 +78,7 @@ Each adapter exposes a validated `streamIdleTimeoutMs` configuration field with `@deepseek-ai/dsh-timeout` exposes a rearmable idle-watchdog primitive. One stable local `AbortController` is fused with the caller signal and passed to the transport for the whole adapter call; each outstanding `next()` arms the watchdog, resolution disarms it, and the next demand rearms it. Out-of-band transport activity calls `pulse()` to rearm an outstanding demand without yielding a value. Timeout aborts that stable controller with a capability-owned `TimeoutReason`, and `finally` clears the timer. The adapter classifies its watchdog as `TIMEOUT` and an earlier upstream abort as `ABORTED`. The existing one-shot `deadline()` is not presented as a sliding timer. -Boundary tests prove termination at both actual transports. The hand-written adapter aborts its fetch/reader, and the pi-ai adapter maps the stable signal through the SDK and proves the SDK closes the response. A timer that merely rejects a consumer promise while leaving the request running does not satisfy the contract. +Boundary tests prove termination at both actual transports. The hand-written adapter aborts its fetch/reader, and the pi-ai adapter maps the stable signal through the SDK and proves the SDK closes the response. A timer that merely rejects a consumer promise while leaving the request running does not satisfy the contract, so the watchdog does both: the signal aborts the request, and the deadline settles the outstanding demand even when the transport never observes that abort. ### Keep attempts separate in the existing log @@ -117,7 +117,7 @@ If recovery is exhausted, the final failure is stored once on `turn/end.reason` - Real agent-loop tests cover failure before chunks, partial chunks then failure, thrown and in-band failures, retry to success inside the same turn, exhaustion to structured `turn/end.reason`, and composition with `dsh-compaction-basic` context-overflow recovery. - The partial-chunk integration test proves failed chunks remain attributed to the failed step, no assistant message or tool side effect is committed for that step, and the successful retry records its own chunk seqs and provider/model route. - The plugin-owned `llm/retry` event is non-surface, survives a JSONL round trip, is ignored by message derivation, and drives TUI and Web retraction plus scheduled-retry rendering. Client tests cover complete wire validation, clock-independent countdown, cancellation versus completed retry labels, and trajectory attribution; keyless UI snapshots cover Web scheduling and success, real Web composition tests cover partial transport failure through recovery and exhausted recovery's terminal error row beside the settled retry chain, and ACP automation snapshots confirm that a discarded attempt stays off the wire while the recovered reply is emitted. -- Idle-watchdog tests prove the stable signal is rearmed only while `next()` is outstanding, disarmed during consumer think time and in `finally`, and classified separately from a total-call deadline and an earlier caller abort; adapter tests prove the signal stops the underlying request rather than merely detaching it. +- Idle-watchdog tests prove the stable signal is rearmed only while `next()` is outstanding, disarmed during consumer think time and in `finally`, and classified separately from a total-call deadline and an earlier caller abort; adapter tests prove the signal stops the underlying request rather than merely detaching it, and that the idle deadline settles a demand the transport abandoned. - Direct `ctx.llm.stream()` callers remain single-attempt and receive the same structured failure facts. ## Consequences @@ -134,6 +134,6 @@ If recovery is exhausted, the final failure is stored once on `turn/end.reason` - [Structured error taxonomy](../../archived/architecture/2026-06-11-structured-error-taxonomy.md) owns stable machine-routable codes and cause chaining. - [Reconstructable requests](../../implemented/architecture/2026-07-05-reconstructable-requests.md) makes provider/model and complete request inputs durable before dispatch. - [Timeout deadline library](../../implemented/architecture/2026-07-06-timeout-deadline-library.md) separates shared deadline classification from capability-owned termination. -- [After-call compaction pressure and context-overflow recovery](../../implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md) owns the current closed-step request-recovery extension point and bounded overflow retry. +- The [compaction reference](../../../../packages/compaction/compaction-basic/README.md) owns pressure measurement, progress checks, and bounded overflow retry. - [Provider-routed LLM adapters](../../implemented/architecture/2026-07-14-provider-routed-llm-adapters.md) owns explicit provider/model routing and the one-adapter-per-provider invariant. - [Terminal turn errors survive same-turn retry history](../../archived/bug-fix/2026-08-20-turn-error-survives-same-turn-retry-history.md) owns the removal of the Web retry-history suppression that hid exhausted recovery's terminal error row. diff --git a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md index e8c25f9f64..f39125afeb 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md @@ -8,7 +8,7 @@ Status: implemented ## 问题 -提供方适配器可能在分发或迭代时抛出异常,也可能以 `finish { kind: 'error' | 'aborted' }` 结束。最终适配器边界会在 `dsh-agent-loop` 接收前把抛出值规范化为该终止 finish 协议;middleware 与结果处理缺陷仍会抛出。loop 会将终止模型请求失败交给 `agent/request-error`。未被处理的失败是终态;处理失败的监听器修复策略自有状态,返回 `{ kind: 'retry' }`,并停止 waterfall(瀑布式事件)委托。[重试动作决策](../simplification/2026-07-27-request-error-retry-action.zh.md)规定这一返回约定。 +提供方适配器可能在分发或迭代时抛出异常,也可能以 `finish { kind: 'error' | 'aborted' }` 结束。最终适配器边界会在 `dsh-agent-loop` 接收前把抛出值规范化为该终止 finish 协议;middleware 与结果处理缺陷仍会抛出。loop 会将终止模型请求失败交给 `agent/request-error`。未被处理的失败是终态;处理失败的监听器修复策略自有状态,返回 `{ kind: 'retry' }`,并停止 waterfall(瀑布式事件)委托。[重试动作说明](../../../../packages/core/agent/README.zh.md)规定这一返回约定。 该边界已能安全地再次发起请求。每个失败 stream 会提交一个包含精确紧凑 stream 的仅日志 `assistant/attempt`,message derivation 会忽略它;系统只会在 terminal finish 成功并组装 `assistant/message` 后分派工具调用,重试则从持久 surface 重建下一次 attempt。因此,harness 无需引入第二套响应生命周期或暂定输出协议,即可分隔两次 attempt。 @@ -78,7 +78,7 @@ agent loop(智能体循环)会将终止 finish 的 `LlmFailure` 传给 `agen `@deepseek-ai/dsh-timeout` 公开一个可重新布防的空闲看门狗原语。一个稳定的局部 `AbortController` 会与调用方信号融合,并在整个适配器调用期间传给传输层;每个尚未完成的 `next()` 都会布防看门狗,该调用完成时解除布防,下一次请求数据时再重新布防。带外传输活动会调用 `pulse()`,在不产生值的情况下为尚未完成的需求重新布防。超时会使用能力自身拥有的 `TimeoutReason` 中止这个稳定控制器,`finally` 则会清除定时器。适配器将自身看门狗归类为 `TIMEOUT`,将更早发生的上游中止归类为 `ABORTED`。现有的一次性 `deadline()` 不会被描述为滑动计时器。 -边界测试证明两个实际传输层都能终止。手写适配器会中止其 fetch/reader,pi-ai 适配器会把稳定信号映射到 SDK,并证明 SDK 会关闭响应。如果定时器只拒绝消费方 promise,却让请求继续运行,就不满足此约定。 +边界测试证明两个实际传输层都能终止。手写适配器会中止其 fetch/reader,pi-ai 适配器会把稳定信号映射到 SDK,并证明 SDK 会关闭响应。如果定时器只拒绝消费方 promise,却让请求继续运行,就不满足此约定;因此 watchdog 两者都做:信号中止请求,截止时间则在传输层始终不观察该中止时结算尚未完成的 demand。 ### 在现有日志中分隔尝试 @@ -117,7 +117,7 @@ agent loop(智能体循环)会将终止 finish 的 `LlmFailure` 传给 `agen - 真实 agent-loop 测试覆盖分片前失败、部分分片后失败、抛出及带内失败、在同一轮次内重试至成功、耗尽后写入结构化 `turn/end.reason`,以及与 `dsh-compaction-basic` 上下文溢出恢复的组合。 - 部分分片集成测试证明:失败分片仍归属于失败步骤,该步骤不会提交 assistant 消息或工具副作用,成功的重试会记录自己的分片 seq 和提供方/模型路由。 - 插件拥有的不进入表层的 `llm/retry` 事件可在 JSONL 往返后保留,被消息派生忽略,并驱动 TUI 和 Web 撤回及计划重试渲染。客户端测试覆盖完整的 wire 验证、独立于时钟的倒计时、已取消与已完成重试标签的区别以及轨迹归属;无密钥 UI 快照覆盖 Web 的调度与成功,真实 Web 组合测试覆盖部分传输失败直至恢复,以及耗尽后终态错误行与定格重试链并列的画面,ACP 自动化快照确认,被丢弃的尝试不会通过协议发出,而恢复后的回复会正常发出。 -- 空闲看门狗测试证明:只有 `next()` 尚未完成时才会重新布防稳定信号;在消费方思考期间及 `finally` 中会解除布防;它与总调用 deadline 以及更早发生的调用方中止分开分类。适配器测试证明该信号会终止底层请求,而不只是与其脱离。 +- 空闲看门狗测试证明:只有 `next()` 尚未完成时才会重新布防稳定信号;在消费方思考期间及 `finally` 中会解除布防;它与总调用 deadline 以及更早发生的调用方中止分开分类。适配器测试证明该信号会终止底层请求,而不只是与其脱离,并证明空闲截止时间会结算传输层放弃的 demand。 - `ctx.llm.stream()` 的直接调用方仍只尝试一次,并收到相同的结构化失败事实。 ## 后果 @@ -134,6 +134,6 @@ agent loop(智能体循环)会将终止 finish 的 `LlmFailure` 传给 `agen - [结构化错误分类体系](../../archived/architecture/2026-06-11-structured-error-taxonomy.md)负责稳定、可供机器路由的 code 与 cause chaining。 - [可重建请求](../../implemented/architecture/2026-07-05-reconstructable-requests.zh.md)使提供方/模型和完整请求输入在分发前持久化。 - [超时 deadline 库](../../implemented/architecture/2026-07-06-timeout-deadline-library.zh.md)将共享的 deadline 分类与能力自身拥有的终止操作分开。 -- [调用后压缩压力与上下文溢出恢复](../../implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md)负责当前已关闭步骤的请求恢复扩展点与有界溢出重试。 +- [压缩参考](../../../../packages/compaction/compaction-basic/README.zh.md)负责压力测量、进展检查与有界溢出重试。 - [提供方路由的 LLM 适配器](../../implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md)负责显式提供方/模型路由与每个提供方仅有一个适配器的不变量。 - [Terminal turn errors survive same-turn retry history](../../archived/bug-fix/2026-08-20-turn-error-survives-same-turn-retry-history.md)负责移除曾藏掉耗尽恢复终态错误行的 Web 重试历史抑制。 diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml deleted file mode 100644 index 0856d5ac56..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-21-mandatory-app-attribution-headers.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md -/agent-note-mandatory-user-agent-attribution-for-provider-requests: - en: 3f804e0236d13aba - zh: ef8b1b7b9c3d8376 -/agent-note-mandatory-user-agent-attribution-for-provider-requests/problem: - en: f65797760084b93a - zh: d1fb36ef61a93003 -/agent-note-mandatory-user-agent-attribution-for-provider-requests/investigation: - en: 5ea11f54f507ba64 - zh: 22a83a0714a2fda3 -/agent-note-mandatory-user-agent-attribution-for-provider-requests/decision: - en: b3e29e609e3f0966 - zh: e3df0cb13df1cd5d -/agent-note-mandatory-user-agent-attribution-for-provider-requests/verification: - en: 567737dbccdb3a2a - zh: be92a5303247b889 -/agent-note-mandatory-user-agent-attribution-for-provider-requests/alternatives-considered: - en: 016678c52ef64e51 - zh: a113b57b9e9ddeb5 -/agent-note-mandatory-user-agent-attribution-for-provider-requests/consequences: - en: a9010e68445a0008 - zh: 1ec6104ab2865919 diff --git a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.i18n.yaml deleted file mode 100644 index 3addc01168..0000000000 --- a/.agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.i18n.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-26-file-context-as-event-gate.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface: - en: 950d6bb4d83b39d8 - zh: b40458972583b12b -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/problem: - en: 765a679e88ecda60 - zh: fccabc8ee516ea00 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/decision: - en: eb8355d4b7a6c66b - zh: 712c6f932f26a0d3 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/the-policy-is-enforced-by-provider-cas-not-by-dsh-fs-observation-policy-stat: - en: 2edc42b8210c995b - zh: 48f4354c9ec625ee -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/provider-contract-change-the-version-guard-is-optional: - en: a282600f92568e07 - zh: 3e6b2bec22c170c6 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/event-vocabulary-owned-by-dsh-fs: - en: f57494c21daf90ea - zh: 1a1f8623dc0cbef3 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/tool-contract-dsh-tool-fs: - en: c7f4457a83739520 - zh: f2d55d018c7dab4f -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/policy-plugin-contract-dsh-fs-observation-policy: - en: a239deb5dde631df - zh: 68cc6c954b247596 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/bare-provider-behavior-no-dsh-fs-observation-policy: - en: 5aed537454b78157 - zh: 9e8e7108be6a9537 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/supersedes: - en: 5cc21706fd0f00ad - zh: 2b51da75a95f1777 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/verification: - en: 1e3de2836160b4c2 - zh: a8af173d161f0949 -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/alternatives-considered: - en: 768c09662714cf25 - zh: cb641e8a77a2e32c -/agent-note-make-dsh-fs-observation-policy-an-event-gate-plugin-not-a-method-interface/consequences: - en: a1c00220fa5c6ce1 - zh: 4691374274aa8df9 diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml index 9afc68c549..acf21447d3 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml @@ -6,8 +6,8 @@ en: e7cdaa7a6029c259 zh: ed13f8158ee0eaeb /agent-note-event-domain-semantics-session-is-the-fact-log-agent-is-the-live-event-channel/problem: - en: 1222c2b55882b1e5 - zh: f81f481f5eb9a684 + en: 2994389e60dd7a27 + zh: 65a40580c8a7f0d2 /agent-note-event-domain-semantics-session-is-the-fact-log-agent-is-the-live-event-channel/decision: en: ccc0fed9b7388112 zh: 3cc527b11affd941 diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md index 7e0aee220a..91d9cc2dff 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md +++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md @@ -6,7 +6,7 @@ English | [中文](2026-06-30-event-domain-semantics.zh.md) ## Problem -The harness extends the agent loop through a Cordis event taxonomy (see [the microkernel event-taxonomy Agent Note](2026-06-11-microkernel-event-taxonomy.md)). As that taxonomy grew, the line between the three event domains blurred: +The harness extends the agent loop through a Cordis event taxonomy (see [the microkernel event-taxonomy reference](../../../../docs/architecture.md)). As that taxonomy grew, the line between the three event domains blurred: - `session/*` carries the durable, event-sourced log (`SessionEventMap`). - `agent/*` carries live runtime signals that hand a plugin the `Agent` handle. diff --git a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md index 14568c7a46..0fbdb0ac6c 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -harness 通过 Cordis 事件分类体系扩展 agent loop(智能体循环)(见[微内核事件分类体系 Agent Note](2026-06-11-microkernel-event-taxonomy.zh.md))。随着该分类体系的增长,三个事件域之间的界限变得模糊: +harness 通过 Cordis 事件分类体系扩展 agent loop(智能体循环)(见[微内核事件分类体系 reference](../../../../docs/architecture.zh.md))。随着该分类体系的增长,三个事件域之间的界限变得模糊: - `session/*` 承载持久的、事件溯源的日志(`SessionEventMap`)。 - `agent/*` 承载运行时实时信号,向插件传递 `Agent` 句柄。 diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml index 6e01984aee..8796f7fcf8 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.i18n.yaml @@ -33,5 +33,5 @@ en: e9d60967a9b3f9bd zh: 6dfa9ac30d847879 /agent-note-tagged-render-intent-union-for-tool-call-presentation/related: - en: fe27f777b3728fa4 - zh: 4b648dbf731ace00 + en: 00bdd1bb918bc844 + zh: b6d40587041e3245 diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md index 52626d3d92..2b682db206 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md @@ -78,5 +78,5 @@ A new render intent is a compile-breaking change at the bridge switch — delibe ## Related - Supersedes the deferral in the earlier rejected collapse-tool-owned-presentation proposal (rejected — "wait for two real tools and two real consumers, then a tagged render-intent union"). That bar is now met; this is that union. -- Extended by [Result-time applied-hunk diffs](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md) (archived), which added a persisted `meta` channel — the value/presentation split and the persisted `presentationMeta` channel are now owned by [the canonical tool output contract](2026-07-20-canonical-tool-output-contract.md) so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card. +- Extended by [Result-time applied-hunk diffs](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md) (archived), which added a persisted `meta` channel — the value/presentation split and the persisted `presentationMeta` channel are now owned by [the canonical tool output contract](../../../../packages/core/tools/README.md) so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card. - Folds `ToolTerminal` into the tagged `terminal` views used by current UI transports. diff --git a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md index bc91b89c53..ca9bf6b635 100644 --- a/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.zh.md @@ -78,5 +78,5 @@ terminal 意图只用于展示。harness 仍通过自身的 bash 服务执行命 ## 相关 - 取代早先被否决的折叠工具自有呈现提案(已否决——「等两个真实工具和两个真实消费方,然后做带标签 render-intent 联合类型」)中的推迟决定。该条件现已满足;本 Agent Note 即为那个联合类型。 -- 被[结果时已应用 hunk 差异](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md)(已归档)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff)——值/呈现拆分与持久化的 `presentationMeta` 通道现由[规范工具输出约定](2026-07-20-canonical-tool-output-contract.zh.md)拥有。 +- 被[结果时已应用 hunk 差异](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md)(已归档)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff)——值/呈现拆分与持久化的 `presentationMeta` 通道现由[规范工具输出约定](../../../../packages/core/tools/README.zh.md)拥有。 - 将 `ToolTerminal` 折入当前 UI 传输层使用的带标签 `terminal` 视图。 diff --git a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.i18n.yaml index 670619cca9..a02253e5ac 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.i18n.yaml @@ -9,14 +9,14 @@ en: 8ccef8fbd9260dfe zh: 106b2bafe969c676 /agent-note-prompt-variables-and-tool-guidance-ownership/decision: - en: c551d888282d9c73 - zh: 889b5ca3cdf25aff + en: 8d3c456a97365869 + zh: 983421e09cc90d5e /agent-note-prompt-variables-and-tool-guidance-ownership/decision/assemble-context: en: 46e67248692e333a zh: 51c5f2c6152c94d7 /agent-note-prompt-variables-and-tool-guidance-ownership/decision/prompt-variables: - en: 04b623ed2879fdf1 - zh: c111be62834fda93 + en: dc41d226f111d574 + zh: c6242bf30b2ed61c /agent-note-prompt-variables-and-tool-guidance-ownership/decision/persona-as-the-order-0-section: en: c2d32c0ac5d28759 zh: 59cd252ba6da6e59 @@ -30,11 +30,11 @@ en: 9a114359d2e8988b zh: ce39453408b2af7f /agent-note-prompt-variables-and-tool-guidance-ownership/out-of-scope: - en: bbfecc70d0b457fe - zh: 7f207a37a86484f4 + en: 06169acd6e002300 + zh: 605cf02f81df48bc /agent-note-prompt-variables-and-tool-guidance-ownership/shipped-invariants: en: 31ba63101fca5790 zh: af4ceefab2033142 /agent-note-prompt-variables-and-tool-guidance-ownership/consequences: - en: 993f17bf35f6a2e0 - zh: acb429176f0eebcc + en: 94c38eb61a2dcd4a + zh: 8a047857b6d3a4a3 diff --git a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md index 8f5542cb09..9bfcb0d86e 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md +++ b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md @@ -18,7 +18,7 @@ The assembled system prompt had four defects, all of one family: facts the harne ## Decision -**One principle: every fact in the prompt has exactly one owner.** The model name and workspace are config/session facts → the harness exposes them as variables and the persona references them. Per-tool semantics and when-to-use → the tool's `description`. Cross-call habits a description cannot carry → the tool package's prompt section. The product name and SDK identity line → the static `harness:identity` section. Deployment role and behavior → the deployment's persona. +**One principle: every prompt fact has exactly one owner.** The loop exposes route facts as variables referenced by the persona. The working-directory service supplies the current directory as user context. Per-tool semantics and when-to-use belong to the tool description; cross-call habits belong to its package prompt section. The product name and SDK identity line belong to the static `harness:identity` section. Deployment role and behavior belong to the deployment persona. ### Assemble context @@ -28,7 +28,7 @@ The assembled system prompt had four defects, all of one family: facts the harne Plugins register `{{name}}` values through `ctx.systemPrompt.variable(name, provider)`. Assembly resolves them into the waterfall-visible variable map. Rendering rejects unknown own-property references, registered providers that return `undefined`, malformed complete references, and unbalanced references that still contain a closing `}}`; a lone unmatched `{{` remains prose, and substituted values are not rescanned. Registration rejects invalid or duplicate variable names, and section names are unique. Sections may set `interpolate: false` to preserve generated documents literally; `tools:sdk` does so because tool descriptions and schemas may document their own `{{…}}` syntax. -`dsh-agent-loop` registers the two built-ins, both pure projections of the context agent: `model` (= `options.model`) and `cwd` (= `session.header.cwd`). The example personas write `powered by the {{model}} model` — the model name is stated once, in the `model:` config key. `{{cwd}}` is demonstrated in the ACP example only: every ACP session carries the client's cwd, while config-pre-created stdio agents have none (a persona claiming `{{cwd}}` there fails the turn — by design). The variables stay on the loop plugin (unlike the sections below): they are runtime facts of the agents THIS loop drives, and a replacement loop supplies its own. +`dsh-agent-loop` registers `provider` (= `options.provider`) and `model` (= `options.model`) as pure projections of the context agent. Example personas reference `{{model}}` so the route owns the model name. These variables remain on the loop plugin because they describe the agents it drives; a replacement loop supplies its own. The [Session working-directory decision](2026-09-13-session-working-directory.md) owns directory state and its literal user-context placement; the loop does not register `cwd`. ### Persona as the order-0 section @@ -54,7 +54,6 @@ Per-tool semantics and selection guidance live in tool descriptions. Prompt sect ## Out of scope - Further variables (`date`, platform, git state) — the registry makes each a one-line contribution by whichever plugin owns the fact; none is claimed here. -- A config `cwd` for pre-created stdio agents (would let the stdio persona use `{{cwd}}` and partition persistence by real path) — deferred until the session-cwd story is revisited. ## Shipped invariants @@ -68,5 +67,5 @@ Per-tool semantics and selection guidance live in tool descriptions. Prompt sect - Every fact in the assembled prompt now has exactly one owner, and the hand-maintained tool prose in leaf YAML is gone: loading or dropping a tool plugin no longer means editing any deployment's persona. - `{{model}}` reflects `AgentOptions.model` at assembly time. A plugin that switches models in the `agent/request` waterfall makes the prompt's claim stale for that step, and one that SUPPLIES the model there (options.model unset — the loop's documented fallback) leaves the variable valueless at render, failing a `{{model}}` persona before the waterfall runs. Both have the same remedy, and it is the ownership rule itself: the plugin that owns the late-bound model fact states it early on the `system-prompt/assemble` waterfall (`assembly.variables['model'] = …`) — one owner, both statements; a loop test pins the supply path end-to-end. Accepted. - While a bound provider is absent (not yet activated, unloaded, mid-HMR-reload), the subagent tool does not exist and a model request in that window simply lacks it. That is the honest state — the alternative was a registered tool whose description or execution could not be trusted. -- Strictness means a persona can fail a turn at render (e.g. `{{cwd}}` on a cwd-less session). The failure is contained — the turn ends `error`, the loop survives — and it is an authoring error we WANT loud. +- Strict interpolation rejects missing or valueless variables before a model request. The turn ends with `error` while the loop remains available for later turns; a persona referencing `{{model}}` requires its composing runtime to supply that value. - Inline escapes remain unsupported in interpolated text; literal sections need no escaping. PTC unit tests cover both modes and runtime languages, and the recorded `ptc-turn` scenario preserves tool-template examples in the model-visible prompt. diff --git a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.zh.md b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.zh.md index 348ae36cf6..cf0f3f1c88 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.zh.md @@ -18,7 +18,7 @@ Status: implemented ## 决策 -**一条原则:提示词中的每个事实恰好有一个归属方。** 模型名称和工作区是配置/会话事实 → harness 将它们暴露为变量,persona 引用它们。每个工具的语义和何时使用 → 工具的 `description`。description 无法承载的跨调用习惯 → 包的提示词 section。产品名称和 SDK 身份说明 → 静态的 `harness:identity` section。部署角色与行为 → 部署的 persona。 +**一条原则:提示词中的每个事实恰好有一个归属方。** 循环将路由事实暴露为 persona 引用的变量。工作目录服务通过用户上下文提供当前目录。每个工具的语义和选择指导属于工具描述;跨调用习惯属于其包的提示词 section。产品名称和 SDK 身份说明属于静态的 `harness:identity` section。部署角色与行为属于部署的 persona。 ### 组装上下文 @@ -28,7 +28,7 @@ Status: implemented 插件通过 `ctx.systemPrompt.variable(name, provider)` 注册 `{{name}}` 值。组装过程将它们解析到 waterfall 可见的变量映射中。渲染阶段拒绝以下情况:引用未知的自有属性、已注册的提供方返回 `undefined`、格式错误的完整引用、以及仍包含闭合 `}}` 的不平衡引用;孤立的未匹配 `{{` 保留为行文,替换后的值不会被重新扫描。注册阶段拒绝无效或重复的变量名,section 名称也必须唯一。段可设置 `interpolate: false` 来原样保留生成的文档;`tools:sdk` 使用此设置,因为工具描述和 schema 可能会介绍自身的 `{{…}}` 语法。 -`dsh-agent-loop` 注册两个内置变量,均为上下文 agent 的纯投影:`model`(= `options.model`)和 `cwd`(= `session.header.cwd`)。示例 persona 写 `powered by the {{model}} model`——模型名称只在 `model:` 配置键中声明一次。`{{cwd}}` 仅在 ACP 示例中演示:每个 ACP 会话携带客户端的 cwd,而配置预创建的 stdio agent 没有 cwd(在那里声称 `{{cwd}}` 的 persona 会导致该轮次失败——这是有意为之)。变量留在 loop 插件上(不同于下面的 section):它们是本循环驱动的 agent 的运行时事实,替换循环自行提供自己的变量。 +`dsh-agent-loop` 注册 `provider`(= `options.provider`)和 `model`(= `options.model`),它们都是上下文 agent 的纯投影。示例 persona 引用 `{{model}}`,由路由拥有模型名称。这些变量保留在 loop 插件上,因为它们描述该循环驱动的 agent;替换循环自行提供自己的变量。[Session 工作目录决策](2026-09-13-session-working-directory.zh.md) 拥有目录状态及其字面用户上下文位置;循环不注册 `cwd`。 ### Persona 作为 order-0 section @@ -54,7 +54,6 @@ Status: implemented ## 不在范围内 - 更多变量(`date`、platform、git 状态):注册表使每个变量成为拥有该事实的插件的一行贡献;本 Agent Note 不认领任何一个。 -- 为预创建的 stdio agent 提供配置 `cwd`(可让 stdio persona 使用 `{{cwd}}` 并按真实路径分区持久化):推迟到会话 cwd 方案重新讨论时。 ## 交付的不变式 @@ -68,5 +67,5 @@ Status: implemented - 组装后的提示词中每个事实现在恰好有一个归属方,leaf YAML 中手工维护的工具行文已消除:加载或卸载一个工具插件不再需要编辑任何部署的 persona。 - `{{model}}` 在组装时反映 `AgentOptions.model`。如果一个插件在 `agent/request` waterfall 中切换模型,提示词对该步骤的声明就会过时;如果一个插件在那里提供模型(options.model 未设置——循环文档中记载的回退路径),变量在渲染时无值,包含 `{{model}}` 的 persona 会在 waterfall 运行前失败。两者的补救方式相同,就是归属规则本身:拥有延迟绑定模型事实的插件在 `system-prompt/assemble` waterfall 上提前声明它(`assembly.variables['model'] = …`)——一个归属方,两处声明;一个循环测试端到端固定了 supply 路径。已接受。 - 当一个已绑定的提供方不存在时(尚未激活、已卸载、HMR(热模块替换)重载中),subagent 工具不存在,该窗口内的模型请求中不会包含它。这是诚实的状态——替代方案是注册一个 description 或执行都不可信的工具。 -- 严格性意味着 persona 可能在渲染时导致轮次失败(例如在无 cwd 的会话上使用 `{{cwd}}`)。失败是受控的——该轮次以 `error` 结束,循环存活——且这是一个我们希望明确暴露的撰写错误。 +- 严格插值在模型请求之前拒绝缺失或无值的变量。该轮次以 `error` 结束,循环仍可服务后续轮次;引用 `{{model}}` 的 persona 要求组合它的运行时提供该值。 - 插值文本仍不支持行内转义;字面文本段无需转义。PTC 单元测试覆盖两种模式和运行时语言,录制的 `ptc-turn` 场景在模型可见的提示词中保留工具模板示例。 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml index 987abc5f0e..4236d3263a 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml @@ -15,8 +15,8 @@ en: 6da15cf986d59a91 zh: 40282908b54295f9 /agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-mechanism: - en: 7d86e5058dbab2fb - zh: 9a97481325c8eded + en: 1c0ca7de364e4f8b + zh: 906be723ab39e3ae /agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-minicode-shape-adopted-with-the-event-log-as-the-source: en: e5f2ffaa3d7de0cc zh: 5ba6b36787a0e189 diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md index f47d0938b9..9c7444d050 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md @@ -24,11 +24,11 @@ Prefix-cache stability is corollary #1, not the headline: an append-only log pro `EpochHeader` records the request's non-history state: call config and tool schemas. Writers omit `tools: []` and `adapterDefaults: {}`; current acceptance rejects those fields and any `header.system`, rather than repairing them. Whitespace-only system-message content, `config.stop: []`, and nested extensions remain intact. The [V3 canonical-envelope decision](2026-09-06-v3-canonical-session-envelopes.md) owns historical conversion. The rendered system prompt is derived history — the `system/message` event at surface node 0, per the [surface-node Agent Note](2026-09-02-system-prompt-as-surface-node.md) — so a prompt change is a surface replacement rather than a header change. Adapter-supplied effort and token defaults retain their `adapterDefaults` ownership; a Web model selection restored from the log omits an adapter-owned effort so the next resolution cannot reclassify the same effective config as an explicit selection and a false change. `request/header` always writes a full snapshot: the first loop instance uses reason `initial`, later instances use `resume`, an in-instance change uses `change`, and an unchanged envelope beginning an explicitly declared message series or following a surface replacement uses `series`. A `change` snapshot carries `startsSeries: true` when the changed request also starts a series, preserving the two independent facts without a duplicate header. Ordinary append-only later Turns, further same-series Steps, and retries inherit the latest snapshot. `foldRequestHeader` selects the latest snapshot. Legacy `request/header-delta` events and the removed `fallback` reason are rejected when appended or loaded. -Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze evidence decision](../simplification/2026-09-06-agent-request-freeze-evidence.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header. +Each proposed step first claims its inbox batch, assembles the system prompt and tools, projects the rendered prompt against the surviving `system/message` node, and runs `agent/pre-step`. Rejection opens no step; enter opens `step/start`, commits a changed prompt as the `system/message` append or node-0 replacement, records the final message batch as `user/message` events, and may use `startsRequestSeries: true` to declare a distinct series. `agent/request` may replace only the frozen call-config seed. The loop records the owed initial, resume, change, or series full snapshot, builds `GenerateOptions` from derived messages (system message first) and that header with no `system` field, and freezes it while leaving `AbortSignal` live. The [request-freeze evidence reference](../../../../packages/core/agent-loop/README.md) owns reuse of completed message freezes and per-request local header freezing. The first call config starts from explicit `AgentOptions`, preserving fork overrides and resume reconfiguration; later calls start from the folded header. **The open step is the reconstruction boundary.** Its entered `user/message` batch and any newly written `request/header` precede request dispatch. Injection after the atomic claim joins a later request, while a listener that must affect this request returns messages through `agent/pre-step`. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written. -**Enforcement.** The `dsh-agent-loop/invariant` companion registers with `ctx.invariants` and, when selected, independently rebuilds each loop request through a fresh `Session`, so the live cache cannot vouch for itself, then compares messages and folded header fields at `llm/stream`. The loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets the companion and other request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. Correctness depends on sequence-bounded reconstruction rather than listener order. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step. +**Enforcement.** The loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step. ### The MiniCode shape: adopted, with the event log as the source diff --git a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md index d2499a6264..21e8e28fd2 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md @@ -24,11 +24,11 @@ Status: implemented `EpochHeader` 记录请求的非历史状态:调用配置和工具 schema。写入方省略 `tools: []` 与 `adapterDefaults: {}`;当前接纳拒绝这些字段以及任何 `header.system`,而不修复它们。仅含空白的系统消息内容、`config.stop: []` 与嵌套扩展保持原样。[V3 规范信封决策](2026-09-06-v3-canonical-session-envelopes.zh.md)负责历史转换。渲染后的系统提示词是派生历史——surface 第 0 号节点上的 `system/message` 事件,见[surface 节点 Agent Note](2026-09-02-system-prompt-as-surface-node.zh.md)——因此提示词变更是 surface 替换而不是 header 变更。适配器提供的推理强度与 token 默认值会保留其 `adapterDefaults` 来源信息;Web 从日志恢复模型选择时会省略适配器持有的推理强度,因此下一次解析不会把相同的有效配置重新归类为显式选择并产生虚假变更。`request/header` 始终写入完整快照:首个循环实例使用 reason `initial`,后续实例使用 `resume`,实例内变更使用 `change`,内容未变的封装显式开启消息序列或跟随表层替换时使用 `series`。如果发生变化的请求同时开启序列,`change` 快照会携带 `startsSeries: true`,无需重复 header 即可保留这两个独立事实。普通的仅追加后续 Turn、同一序列内后续的 Step 与重试沿用最新快照。`foldRequestHeader` 选择最新快照。旧的 `request/header-delta` 事件和已移除的 `fallback` reason 在追加或加载时都会被拒绝。 -每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结证据决策](../simplification/2026-09-06-agent-request-freeze-evidence.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。 +每个拟议步骤先领取其 inbox 批次,组装系统提示词与工具,把渲染后的提示词与存活的 `system/message` 节点比对投影,再运行 `agent/pre-step`。reject 不打开步骤;enter 打开 `step/start`,把变化的提示词作为 `system/message` 追加或第 0 号节点替换提交,把最终消息批次记录为 `user/message` 事件,并可使用 `startsRequestSeries: true` 声明独立序列。`agent/request` 只能替换冻结的调用配置种子。循环记录所需的 initial、resume、change 或 series 完整快照,从派生消息(系统消息在先)与该不含 `system` 字段的 header 构建 `GenerateOptions`,冻结请求但保持 `AbortSignal` 活跃。[请求冻结证据说明](../../../../packages/core/agent-loop/README.zh.md)拥有消息完整冻结的复用规则和每次请求的本地 header 冻结规则。首次调用配置从显式的 `AgentOptions` 出发,保留 fork 覆盖和恢复重配置;后续调用从折叠后的 header 出发。 **已打开步骤是重建边界。** 进入步骤的 `user/message` 批次与任何新写入的 `request/header` 都位于请求分派之前。原子领取后发生的注入加入后续请求;必须影响本次请求的监听器则通过 `agent/pre-step` 返回消息。header 重建选择该步骤的 `request/header`,或在无新 header 写入时沿用前一个快照。 -**强制执行。** `dsh-agent-loop/invariant` 配套插件向 `ctx.invariants` 注册,并在被选用时通过一个全新的 `Session` 独立重建每个循环请求,使活跃缓存无法为自身背书,然后在 `llm/stream` 处比较消息和折叠后的 header 字段。循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让配套插件和其他请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。正确性依赖于序列有界的重建,而非监听器顺序。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。 +**强制执行。** 循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。 ### MiniCode 形态:采纳,以事件日志为真源 diff --git a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml deleted file mode 100644 index 2848f7cd4b..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-05-windows-jsonl-durable-publish.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md -/agent-note-windows-native-durable-jsonl-publication: - en: db7fe80708911f2f - zh: 42c6639defea2bde -/agent-note-windows-native-durable-jsonl-publication/problem: - en: dcc3c6238131d302 - zh: 4d4e8513a4069af3 -/agent-note-windows-native-durable-jsonl-publication/decision: - en: 385565b209f1e739 - zh: 0eab0997c49a3c33 -/agent-note-windows-native-durable-jsonl-publication/alternatives-considered: - en: 83d5bf5781f89e29 - zh: 068835993789e09d -/agent-note-windows-native-durable-jsonl-publication/consequences: - en: 4be0ad03a334c257 - zh: 95d473ed5b94e270 diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml index 9db3e2596d..f92e73d1e4 100644 --- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml @@ -6,8 +6,8 @@ en: 4a9921fef5e65cc8 zh: 672cd27d68310cb1 /agent-note-a-shared-timeout-deadline-primitive-with-hard-kill-left-to-each-capability/problem: - en: 110ff68b35bd43cb - zh: f1cea15fb29a9ae9 + en: f3fb995aaf40ac76 + zh: 95bbc4d7d79e558c /agent-note-a-shared-timeout-deadline-primitive-with-hard-kill-left-to-each-capability/decision: en: 18645d82d9e40107 zh: 73e510f1ed1106e6 @@ -21,8 +21,8 @@ en: 333eab4967d753ec zh: dd7537bb82cc717f /agent-note-a-shared-timeout-deadline-primitive-with-hard-kill-left-to-each-capability/consequences: - en: 832f4dbd93a47f94 - zh: 65ada43f9f23a39b + en: 0f723f16456c343d + zh: 52b7aab9a6c4f7be /agent-note-a-shared-timeout-deadline-primitive-with-hard-kill-left-to-each-capability/alternatives-considered: - en: 7b3445622becbd36 - zh: bb358acf2ff28e26 + en: 0f3e3ecfa399c81a + zh: 951c2ee71047b11a diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md index 6f72693873..07ce41fa2e 100644 --- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md +++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md @@ -12,7 +12,7 @@ Timeout handling was drifting apart across the tool-bearing capabilities, and th - **web_fetch** ([packages/web/web-fetch-http/src/provider.ts](../../../../packages/web/web-fetch-http/src/provider.ts)) had a correct but *hand-rolled* timeout: it constructed an `AbortController`, wired `setTimeout(() => controller.abort(new WebError(…, 'WEB_FETCH_TIMEOUT')))`, manually added and removed the upstream-signal listener, cleared the timer in a `finally`, and recovered the timeout reason from `signal.reason` in a `translateAbortOrNetwork` helper because the reader surfaces a bare `AbortError`. - **web_search** ([packages/web/tool-web/src/search.ts](../../../../packages/web/tool-web/src/search.ts)) had **no timeout at all**: `WebSearchRequest` ([packages/web/web/src/types.ts](../../../../packages/web/web/src/types.ts)) carries no `timeoutMs` field, and each provider's `search()` only forwards `exec.signal`. (web_search stays untimed here — see Consequences.) -Each new external-process or network tool re-derived the same four things — clamp the requested value, start a timer, fuse the timeout with upstream cancellation, and distinguish "timed out" from "cancelled" on the way out — and the fusion and reason-recovery are exactly the parts that are easy to get subtly wrong (web_fetch's `signal.reason` dance is evidence). At the same time, the *termination* each performs is irreducibly different: bash asks its subprocess provider to terminate an OS-owned range, while web aborts an in-process `fetch` and lets undici tear down the socket. The [native-containment decision](2026-08-28-subprocess-native-containment.md) owns the local scope, Job, and fallback mechanisms; there is no single mechanism that can stop every capability's work. +Each new external-process or network tool re-derived the same four things — clamp the requested value, start a timer, fuse the timeout with upstream cancellation, and distinguish "timed out" from "cancelled" on the way out — and the fusion and reason-recovery are exactly the parts that are easy to get subtly wrong (web_fetch's `signal.reason` dance is evidence). At the same time, the *termination* each performs is irreducibly different: bash asks its subprocess provider to terminate an OS-owned range, while web aborts an in-process `fetch` and lets undici tear down the socket. The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns the local scope, Job, and fallback mechanisms; there is no single mechanism that can stop every capability's work. ## Decision @@ -101,7 +101,7 @@ The signal only *notifies*; termination is always the listener's job, and the li - `SpawnSpec.timeoutMs` and `SpawnOutcome.timedOut`/`aborted` were removed rather than kept as always-zero/always-false vestiges: with `runBash` owning no timer and the executor owning classification, they were read nowhere. An always-0 field read by nothing is dead weight under the per-file coverage gate. - web_fetch shed its bespoke controller/timer/listener/reason-recovery; the classifier now keys off the deadline signal (`timeoutOf` + `aborted`) rather than the thrown error's shape, which is robust across both the request-phase reject-with-reason and the read-phase bare-`AbortError`. - `AbortSignal.any` and `using`/`Symbol.dispose` enter the repo for the first time here (Node ≥ 24 baseline, already met). -- Model streams now share one rearmable timer contract without turning a sliding idle interval into a total-call deadline or charging consumer think time. Adapters that can observe out-of-band transport activity may pulse an outstanding demand; suppressed activity remains invisible to the watchdog. The primitive still only notifies; adapter tests prove their transports observe its stable signal and terminate. +- Model streams now share one rearmable timer contract without turning a sliding idle interval into a total-call deadline or charging consumer think time. Adapters that can observe out-of-band transport activity may pulse an outstanding demand; suppressed activity remains invisible to the watchdog. The one-shot `deadline()` still only notifies; adapter tests prove their transports observe the rearmable watchdog's stable signal and terminate. The watchdog additionally settles the one demand it drives when its idle interval elapses, because a transport that drops the error of an aborted in-flight read would otherwise hold the caller past that interval. Out of scope, named to mark the boundary: `web_search` can gain an optional model-facing `timeout_ms` once its tool-schema/snapshot coverage is planned; the ripgrep-backed fs discovery tools ([packaged ripgrep search](../../archived/architecture/2026-08-01-packaged-ripgrep-search.md)) consume the same provider-owned deadline shape through `dsh-tool-call-timeout-policy` and `exec.signal`; a `tools/execute` waterfall middleware could arm a default deadline for every tool call by driving `exec.signal` — that would be a plugin that *consumes* this library and still only notifies, the hard kill remaining each capability's job. @@ -111,6 +111,6 @@ Out of scope, named to mark the boundary: `web_search` can gain an optional mode **Per-tool ad-hoc timeout, no shared code (the prior status quo, and Claude Code's choice).** Rejected because it was already producing divergence and duplicated correctness burden: web_fetch hand-rolled the exact controller/reason logic that future network/process-backed tools would each have to re-derive, and the fusion + `signal.reason` recovery are the error-prone parts. Claude Code tolerates full duplication; this repo has a single shared abort channel (`exec.signal` on every `execute`) that makes a small shared primitive strictly cleaner, so the cost/benefit differs. -**A `withTimeout(promise, ms)` wrapper instead of a signal factory.** Rejected because racing a promise against a timer resolves the *tool-call* promise on deadline without stopping the underlying work — the child process or fetch socket leaks on. Handing out a signal and requiring the capability to listen is what forces a real termination path to exist. This mirrors the "dispose must reach quiescence, not just request it" defensive rule. +**A `withTimeout(promise, ms)` wrapper instead of a signal factory.** Rejected because racing a promise against a timer resolves the *tool-call* promise on deadline without stopping the underlying work — the child process or fetch socket leaks on. Handing out a signal and requiring the capability to listen is what forces a real termination path to exist. This mirrors the "dispose must reach quiescence, not just request it" defensive rule. `idleWatchdog` is the deliberate exception, and only for the single iterator demand it drives: the signal it hands the transport still owns releasing that read (an abort destroys the socket), while the deadline ends the demand when the transport never settles it. **Keep separate bash timeout and cancellation triggers.** Rejected because one deadline signal removes the bespoke timer and standardizes classification. Racing causes report whichever abort arrived first, while the provider-owned termination path is independent of which cause won. diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md index 48bd7b4b91..04569c51e5 100644 --- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md @@ -12,7 +12,7 @@ Status: implemented - **web_fetch**([packages/web/web-fetch-http/src/provider.ts](../../../../packages/web/web-fetch-http/src/provider.ts))有一套正确但*手写*的超时:构造一个 `AbortController`,连接 `setTimeout(() => controller.abort(new WebError(…, 'WEB_FETCH_TIMEOUT')))`,手动添加和移除上游信号监听器,在 `finally` 中清除定时器,并在 `translateAbortOrNetwork` 辅助函数中从 `signal.reason` 恢复超时原因(因为 reader 只抛出裸 `AbortError`)。 - **web_search**([packages/web/tool-web/src/search.ts](../../../../packages/web/tool-web/src/search.ts))**完全没有超时**:`WebSearchRequest`([packages/web/web/src/types.ts](../../../../packages/web/web/src/types.ts))不携带 `timeoutMs` 字段,各提供方的 `search()` 只转发 `exec.signal`。(web_search 在本次设计中保持无超时——见「后果」。) -每个新的外部进程或网络工具都要重新推导同样四件事:钳位请求值、启动定时器、将超时与上游取消融合、在出口处区分「超时」与「已取消」。而融合与原因恢复恰恰是最容易出微妙错误的部分(web_fetch 的 `signal.reason` 处理就是证据)。与此同时,各能力执行的*终止*操作不可归约地不同:bash 请求其子进程提供方终止由 OS 拥有的范围,而 web 中止一个进程内的 `fetch`,由 undici 拆除 socket。[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责本地 scope、Job 与 fallback 机制;不存在一个能停止所有能力工作的单一机制。 +每个新的外部进程或网络工具都要重新推导同样四件事:钳位请求值、启动定时器、将超时与上游取消融合、在出口处区分「超时」与「已取消」。而融合与原因恢复恰恰是最容易出微妙错误的部分(web_fetch 的 `signal.reason` 处理就是证据)。与此同时,各能力执行的*终止*操作不可归约地不同:bash 请求其子进程提供方终止由 OS 拥有的范围,而 web 中止一个进程内的 `fetch`,由 undici 拆除 socket。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责本地 scope、Job 与 fallback 机制;不存在一个能停止所有能力工作的单一机制。 ## 决策 @@ -101,7 +101,7 @@ export function timeoutOf(x: AbortSignal | { reason?: unknown }, code?: string): - `SpawnSpec.timeoutMs` 和 `SpawnOutcome.timedOut`/`aborted` 被移除,而非作为始终为零/始终为 false 的残余保留:由于 `runBash` 不再拥有定时器且执行器负责分类,这些字段无处被读取。一个始终为 0 且无处读取的字段在逐文件覆盖率门禁下属于死代码。 - web_fetch 去除了其定制的 controller/timer/listener/reason-recovery;分类器现在基于 deadline 信号(`timeoutOf` + `aborted`)而非抛出错误的形状来判断,这在请求阶段的 reject-with-reason 和读取阶段的裸 `AbortError` 两种情况下都是健壮的。 - `AbortSignal.any` 和 `using`/`Symbol.dispose` 在此首次进入本仓库(Node ≥ 24 基线,已满足)。 -- 模型流现在共享一个可重启的定时器约定,不会把滑动的空闲间隔变成总调用截止时间,也不会计入消费方思考时间。能够观察到带外传输活动的适配器可以对尚未结算的 demand 调用 `pulse()`;被屏蔽的活动对 watchdog 仍不可见。该原语仍然只做通知;适配器测试证明其传输观察到稳定信号并终止。 +- 模型流现在共享一个可重启的定时器约定,不会把滑动的空闲间隔变成总调用截止时间,也不会计入消费方思考时间。能够观察到带外传输活动的适配器可以对尚未结算的 demand 调用 `pulse()`;被屏蔽的活动对 watchdog 仍不可见。一次性的 `deadline()` 仍然只做通知;适配器测试证明其传输观察到可重启 watchdog 的稳定信号并终止。此外,watchdog 会在空闲间隔走完时结算它所驱动的那个 demand:否则丢弃已中止读取错误的传输层会让调用方一直等到间隔之外。 以下内容不在本次范围内,列出以标明边界:`web_search` 可以在其工具 schema 和快照覆盖规划完成后获得可选的面向模型的 `timeout_ms`;基于 ripgrep 的文件系统发现工具([打包的 ripgrep 搜索](../../archived/architecture/2026-08-01-packaged-ripgrep-search.md))通过 `dsh-tool-call-timeout-policy` 和 `exec.signal` 消费同样的提供方自有 deadline 形状;`tools/execute` waterfall(瀑布式事件)中间件可以通过驱动 `exec.signal` 为每次工具调用设置默认 deadline——那将是一个*消费*本库的插件,仍然只做通知,硬终止仍是各能力自己的事。 @@ -111,6 +111,6 @@ export function timeoutOf(x: AbortSignal | { reason?: unknown }, code?: string): **每个工具各自实现超时,不共享代码(先前的现状,也是 Claude Code 的选择)。** 否决,因为它已经在产生分化和重复的正确性负担:web_fetch 手写了与未来网络/进程类工具各自需要重新推导的完全相同的 controller/reason 逻辑,而融合 + `signal.reason` 恢复正是容易出错的部分。Claude Code 容忍完全重复;本仓库有一个统一的共享 abort 通道(每次 `execute` 上的 `exec.signal`),使得采用一个小型共享原语明显更简洁,因此成本/收益不同。 -**用 `withTimeout(promise, ms)` 包装器代替信号工厂。** 否决,因为让 promise 与定时器竞争只是在截止时间到达时 resolve *工具调用*的 promise,而不会停止底层工作——子进程或 fetch socket 会泄漏。分发信号并要求能力监听,才能强制一条真实的终止路径存在。这与「dispose 必须达到完全停稳,而非仅仅请求它」的防御性规则一致。 +**用 `withTimeout(promise, ms)` 包装器代替信号工厂。** 否决,因为让 promise 与定时器竞争只是在截止时间到达时 resolve *工具调用*的 promise,而不会停止底层工作——子进程或 fetch socket 会泄漏。分发信号并要求能力监听,才能强制一条真实的终止路径存在。这与「dispose 必须达到完全停稳,而非仅仅请求它」的防御性规则一致。`idleWatchdog` 是有意保留的例外,且仅针对它所驱动的单个迭代器 demand:交给传输层的信号仍负责释放该读取(中止会销毁 socket),而当传输层永不结算时由截止时间结束该 demand。 **保留 bash 独立的超时和取消触发器。** 否决,因为一个 deadline 信号移除了定制定时器并标准化了分类。发生竞争时,报告先到达的那个 abort 作为原因,由提供方管理的终止路径不受哪个原因先胜出的影响。 diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml deleted file mode 100644 index cfb0644c92..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-08-agent-scope-contexts.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md -/agent-note-the-agent-is-a-registration-scope: - en: f767894b8308f1a0 - zh: 4ff0aead6ee4e27b -/agent-note-the-agent-is-a-registration-scope/problem: - en: 87634b46a5642328 - zh: 2adce8b041626437 -/agent-note-the-agent-is-a-registration-scope/decision: - en: ac58e62f780b0879 - zh: 6b077226de04a8fa -/agent-note-the-agent-is-a-registration-scope/decision/registration-origin-chooses-visibility-and-cleanup: - en: 1ec8e54543272e1b - zh: b5b1695a805574a2 -/agent-note-the-agent-is-a-registration-scope/decision/the-operation-chooses-the-view: - en: 2e236d399efdc0b2 - zh: 00a7ba20f90862c6 -/agent-note-the-agent-is-a-registration-scope/decision/scoped-events-keep-routing-separate-from-event-data: - en: df5f5e4c1de44474 - zh: d851c02959d02380 -/agent-note-the-agent-is-a-registration-scope/decision/creation-publishes-last-and-disposal-revokes-last: - en: f46920b4945e0e6d - zh: 870a157205883144 -/agent-note-the-agent-is-a-registration-scope/security-and-authority-are-non-goals: - en: 5ffc5a8f65ce7dfe - zh: 3362f236356a8033 -/agent-note-the-agent-is-a-registration-scope/alternatives-considered: - en: bf43310538280f62 - zh: e4940ff47c5d47b5 -/agent-note-the-agent-is-a-registration-scope/alternatives-considered/pass-an-agent-option-to-every-registration: - en: 37e7487b1f1eb87a - zh: eeddbaf21eee7ebc -/agent-note-the-agent-is-a-registration-scope/alternatives-considered/filter-events-while-keeping-registries-global: - en: 7eb2628d51ea9ed0 - zh: e58f05faf0e95712 -/agent-note-the-agent-is-a-registration-scope/alternatives-considered/create-one-service-graph-per-agent: - en: 2fd1c41c3d1bc6ca - zh: cffad44a2c69330e -/agent-note-the-agent-is-a-registration-scope/alternatives-considered/inherit-parent-registration-scopes: - en: 01f989446aab1cf1 - zh: 8f5c9018b7b84c94 -/agent-note-the-agent-is-a-registration-scope/consequences: - en: 65c8f47d1607b276 - zh: 950b86b4682f7a0e diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml index 1325797191..06b3c22fd7 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.i18n.yaml @@ -6,17 +6,17 @@ en: 06b99197145a4080 zh: 698a61dcd2385985 /agent-note-tool-output-spill-policy/problem: - en: 858cbb874e213697 - zh: 1693b618886ac42d + en: fe94bf904e838bad + zh: d936ebf9f2ec0ce1 /agent-note-tool-output-spill-policy/decision: - en: 89536b0d2d7b4c92 - zh: 341240f445885549 + en: 5f467657361e732d + zh: 24c4c47034c8a6eb /agent-note-tool-output-spill-policy/decision/spill-seam: en: 1e92bc9a0548def9 zh: c99cf6878981ba71 /agent-note-tool-output-spill-policy/decision/spill-policy: - en: 3c49c0246188c513 - zh: 4b6749c30726903d + en: 702ec228efeca0b8 + zh: 0b817f9db08a7aeb /agent-note-tool-output-spill-policy/showcase-web-fetch: en: d5c334b50f458925 zh: 7631307b5d9fbc55 diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md index e32aec72f4..0b0b375090 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md @@ -10,7 +10,7 @@ Tool outputs need bounded model-facing previews, but some oversized results are Before this change the behavior was uneven. `dsh-bash-local` already writes complete stdout/stderr streams to private temp spill files when its in-memory tail overflows, but ordinary text tool results were returned inline unless the tool hand-rolled its own cap. The [tool result retention library](../../archived/architecture/2026-07-06-tool-result-retention-library.md) owns preview mechanics, but it does not own storage or an execution-pipeline policy that applies those mechanics to final tool results. -The shape matches the timeout policy design: a tool author declares a canonical value plus Native renderer, and a policy plugin enforces the deployment's default context budget on rendered content. Tool-specific early spill remains possible for provider acquisition bounds; tool-owned presentation spill may retain a complete acquired canonical value while replacing only presentation. The [canonical tool-output contract](2026-07-20-canonical-tool-output-contract.md) owns that split. +The shape matches the timeout policy design: a tool author declares a canonical value plus Native renderer, and a policy plugin enforces the deployment's default context budget on rendered content. Tool-specific early spill remains possible for provider acquisition bounds; tool-owned presentation spill may retain a complete acquired canonical value while replacing only presentation. The [canonical tool-output contract](../../../../packages/core/tools/README.md) owns that split. ## Decision @@ -22,7 +22,7 @@ A thin spill storage seam plus a default spill policy plugin, in a new `packages | `@deepseek-ai/dsh-spill-local` | Local backend: private, session-scoped file storage on the host filesystem. | | `@deepseek-ai/dsh-spill-policy` | Tool-result policy plugin: wraps final text results after dispatch and replaces oversized results with a retained preview plus a spill locator. | -The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../bug-fix/2026-09-05-session-reference-spill-reuse.md) adds a direct storage consumer with separate preview, source-description, and failure semantics; it does not change the tool-result policy. +The tool-result Consumer is `dsh-spill-policy`, which consumes final tool results through the `tools/post-execute` waterfall. The model follows the backend-supplied retrieval hint for the returned locator. [Session-reference spill reuse](../../../../packages/context/session-reference/README.md) adds a direct storage consumer with separate preview, source-description, and failure semantics; it does not change the tool-result policy. ### Spill seam @@ -66,7 +66,7 @@ interface SpillRef { ### Spill policy -`dsh-spill-policy` retains ordered text/image ends under `maxInlineTokens` after post-execute policy accepts the result. `saveText()` stores the complete result with readable image paths; image bytes remain in attachment storage. The [multimodal retention decision](../../implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md) owns image projection order, atomic image omission, and route pricing. An omitted budget installs no listeners, recovery failures keep the original result, and model-facing `read` results skip retention to avoid a read/spill loop. +`dsh-spill-policy` retains ordered text/image ends under `maxInlineTokens` after post-execute policy accepts the result. `saveText()` stores the complete result with readable image paths; image bytes remain in attachment storage. The [multimodal retention reference](../../../../packages/spill/spill-policy/README.md) owns image projection order, atomic image omission, and route pricing. An omitted budget installs no listeners, recovery failures keep the original result, and model-facing `read` results skip retention to avoid a read/spill loop. ## Showcase: web_fetch diff --git a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md index 2941fc5167..07300d01e6 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md @@ -10,7 +10,7 @@ Status: implemented 这项改动之前的行为并不一致。`dsh-bash-local` 已经会在内存尾部溢出时,把完整 stdout/stderr 流写入私有的临时 spill 文件;普通文本工具结果则仍以内联形式返回,除非工具自行实现上限。[工具结果保留库](../../archived/architecture/2026-07-06-tool-result-retention-library.md)负责预览机制,但不负责存储,也不负责把这些机制应用于最终工具结果的执行流水线策略。 -其形态与超时策略设计一致:工具作者声明规范值与 Native renderer(原生渲染器),由策略插件在渲染后的内容上执行部署默认的上下文预算。工具仍可在提供方采集上限处提前 spill;由工具负责的展示 spill 可以保留已完整采集的规范值,而只替换展示内容。[规范工具输出约定](2026-07-20-canonical-tool-output-contract.zh.md)规定了这项区分。 +其形态与超时策略设计一致:工具作者声明规范值与 Native renderer(原生渲染器),由策略插件在渲染后的内容上执行部署默认的上下文预算。工具仍可在提供方采集上限处提前 spill;由工具负责的展示 spill 可以保留已完整采集的规范值,而只替换展示内容。[规范工具输出约定](../../../../packages/core/tools/README.zh.md)规定了这项区分。 ## 决策 @@ -22,7 +22,7 @@ Status: implemented | `@deepseek-ai/dsh-spill-local` | 本地后端:在宿主文件系统中提供私有、会话作用域的文件存储。 | | `@deepseek-ai/dsh-spill-policy` | 工具结果策略插件:包装分发后的最终文本结果,并以保留预览和 spill 定位符替换超大结果。 | -工具结果消费方是 `dsh-spill-policy`,它通过 `tools/post-execute` waterfall(瀑布式事件)使用最终工具结果。模型按照后端随定位符返回的检索提示读取内容。[会话引用 spill 复用](../bug-fix/2026-09-05-session-reference-spill-reuse.zh.md)增加一个直接存储消费方,采用独立的预览、来源信息与失败语义;它不改变工具结果策略。 +工具结果消费方是 `dsh-spill-policy`,它通过 `tools/post-execute` waterfall(瀑布式事件)使用最终工具结果。模型按照后端随定位符返回的检索提示读取内容。[会话引用 spill 复用](../../../../packages/context/session-reference/README.zh.md)增加一个直接存储消费方,采用独立的预览、来源信息与失败语义;它不改变工具结果策略。 ### spill seam @@ -66,7 +66,7 @@ interface SpillRef { ### spill 策略 -`dsh-spill-policy` 在执行后策略接受结果之后,按 `maxInlineTokens` 对文字和图片共同计量并保留首尾。完整结果仍通过 `saveText()` 保存,图片本体留在附件存储,结果文件记录可读取路径。图片投影顺序、整图省略和模型计量由[图文结果保留决策](../../implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.zh.md)负责。省略配置时不安装监听器,存储失败时保留原结果;`read` 的模型可见结果跳过自动省略,避免重复读取产生循环。 +`dsh-spill-policy` 在执行后策略接受结果之后,按 `maxInlineTokens` 对文字和图片共同计量并保留首尾。完整结果仍通过 `saveText()` 保存,图片本体留在附件存储,结果文件记录可读取路径。图片投影顺序、整图省略和模型计量由[图文结果保留参考](../../../../packages/spill/spill-policy/README.zh.md)负责。省略配置时不安装监听器,存储失败时保留原结果;`read` 的模型可见结果跳过自动省略,避免重复读取产生循环。 ## 示例:web_fetch diff --git a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml deleted file mode 100644 index ee57a4b8cf..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery: - en: 8bfe0d8b79d9840f - zh: 6921dc88e5ef9c83 -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/problem: - en: 036da3e545debe29 - zh: 82e031a3120ca36c -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/decision/successful-pressure-runs-at-the-next-pre-step-boundary: - en: 5f5a656b38730ec6 - zh: aece66b85cc610cf -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/decision/request-recovery-is-limited-to-the-final-model-boundary: - en: fb7da851bf6a020e - zh: b40dc932d6c3101c -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/decision/compactionengine-exposes-intent-not-token-accounting: - en: be9505b3d73c7f6b - zh: ed753f1afec2787a -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/testing: - en: b0e1704bd3724301 - zh: 2279353ff615f183 -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/alternatives-considered: - en: 3cb5363732ef5758 - zh: 07746411cc80878f -/agent-note-after-call-compaction-pressure-and-context-overflow-recovery/consequences: - en: dfe515fb04127eb5 - zh: 1924b2e1e9997910 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index 5e078a7885..5b400d15a2 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml @@ -21,8 +21,8 @@ en: c35ea4b858448b49 zh: 1f8ff0a9f82ca881 /agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/build-pipeline-and-artifacts: - en: 53ba44431d2ce411 - zh: 4159475549273422 + en: d3bbd24ead02af4f + zh: 64623d88bee4314b /agent-note-single-file-executable-sdk-runtime-distribution-single-exe/decision/python-sdk-distribution-two-carriers-exe-for-production-node-for-development: en: 9bd60549d4a52ec0 zh: e2343b7210212ee0 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index 7260d11c84..27d37ed541 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -48,7 +48,7 @@ The [Office kit](2026-09-14-independent-libreoffice-kit.md) and its installed de ### Build pipeline and artifacts -[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`, including [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the carrier-root `runtime-bootstrap.mjs` → restore direct workspace packages omitted by legacy deploy and reject any remaining manifest gap → replace staged dependency symlinks with their target bytes, remove package-manager `.bin` links, and fail if any symlink remains → verify the deployed bootstrap and inject pkg configuration with that bin plus assets covering dynamic profile, bundle, frontend, preset, native-library, and configuration reads → stage the target `node-pty` addon → invoke `pkg --sea` once per target → write `deepseek-harness-sdk-runtime--` under `dist-exe/` and copy it with its target-specific Office directory into the runtime directory. The Python runtime owns that bootstrap. It calls the public CLI export for ordinary launches and dispatches a provider-private selection to the same `@deepseek-ai/dsh-subprocess-local/runner` core without changing CLI grammar or adding another executable; the [native-containment decision](2026-08-28-subprocess-native-containment.md) owns that private path. Linux CI rebuilds `pty.node` inside the matching manylinux 2.28 container because legacy deploy omits that install side effect. Every target copies its native `@vscode/ripgrep` binary beside the executable as the required `-rg` sidecar; pkg runtimes select that sidecar through `process.pkg`, while ordinary Node execution uses `@vscode/ripgrep` directly. macOS uses its target prebuild and also emits the required `-spawn-helper`. The deploy flags preserve the measured runtime layout: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted gives pkg a stable single-instance layout that the explicit materialization pass makes symlink-free; disabling automatic peer installation prevents undeclared peers from expanding the closure; link-workspace-packages selects direct workspace dependencies. [`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) overrides the transitive `@deepseek-ai/cosmokit` and `@deepseek-ai/schemastery` semver requests to the pinned vendor sources so legacy deploy never resolves those unpublished names from a registry. Production deployment permits unused workspace patches because development-only tools can be absent from its closure; included-package patch failures still fail deployment. Workspace installs retain strict unused-patch validation. +[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts): runtime closure verification → `pnpm run build` → (after clearing) `pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **directly into** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`, including [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the carrier-root `runtime-bootstrap.mjs` → restore direct workspace packages omitted by legacy deploy and reject any remaining manifest gap → replace staged dependency symlinks with their target bytes, remove package-manager `.bin` links, and fail if any symlink remains → verify the deployed bootstrap and inject pkg configuration with that bin plus assets covering dynamic profile, bundle, frontend, preset, native-library, and configuration reads → stage the target `node-pty` addon → invoke `pkg --sea` once per target → write `deepseek-harness-sdk-runtime--` under `dist-exe/` and copy it with its target-specific Office directory into the runtime directory. The Python runtime owns that bootstrap. It calls the public CLI export for ordinary launches and dispatches a provider-private selection to the same `@deepseek-ai/dsh-subprocess-local/runner` core without changing CLI grammar or adding another executable; the [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private path. Linux CI rebuilds `pty.node` inside the matching manylinux 2.28 container because legacy deploy omits that install side effect. Every target copies its native `@vscode/ripgrep` binary beside the executable as the required `-rg` sidecar; pkg runtimes select that sidecar through `process.pkg`, while ordinary Node execution uses `@vscode/ripgrep` directly. macOS uses its target prebuild and also emits the required `-spawn-helper`. The deploy flags preserve the measured runtime layout: `--legacy` is the mandatory path with inject-workspace-packages off; hoisted supplies package directories that staging makes symlink-free; staging removes nested workspace copies only when pnpm records the same dependency identity at the payload root, preserving singleton services; disabling workspace hoisting excludes unrelated workspace source packages; disabling automatic peer installation prevents undeclared peers from expanding the closure; link-workspace-packages selects direct workspace dependencies. [`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) overrides the transitive `@deepseek-ai/cosmokit` and `@deepseek-ai/schemastery` semver requests to the pinned vendor sources so legacy deploy never resolves those unpublished names from a registry. Production deployment permits unused workspace patches because development-only tools can be absent from its closure; included-package patch failures still fail deployment. Workspace installs retain strict unused-patch validation. CI: [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml) runs [installed-wheel validation](../testing/2026-08-23-installed-python-wheel-black-box-ci.md) on Linux/Windows x64 for pull requests and Linux ARM64 plus both macOS architectures for master pushes. The [public publication workflow](../../archived/process/2026-08-11-python-publication-workflow.md) calls it for all five targets; `workflow_dispatch` can still select a subset. Native builds run on linux-x64 / linux-arm64 (`ubuntu-24.04-arm`) / macos-arm64 / macos-x64 (`macos-15-intel`) / win-x64 (`windows-2025`), with `~/.pkg-cache` cached where applicable, and pkg handles macOS ad-hoc signing. Each leg installs the release-shaped SDK and runtime wheels into a clean venv outside the checkout, verifies their package and executable origin, then drives the complete keyless scenario set through the public SDK and direct NDJSON JSON-RPC. Trusted pull requests and master pushes additionally run a real DeepSeek two-turn tool smoke on their selected targets; fork and Dependabot heads receive no key. Linux inspects the executable and native addon's GLIBC requirements and runs an additional manylinux 2.28 smoke, while macOS checks the runtime, ripgrep, and PTY helper architectures and verifies that all three deployment targets fit the wheel tag. A full five-target run retains six artifacts, each containing one release file: the platform-independent SDK wheel and five native runtime wheels; a subset dispatch retains the SDK wheel and selected runtime wheels. Bare executables and source bundles remain intermediate test inputs. [`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) accepts `python-v` tag pipelines whose version matches the root `package.json`, builds one SDK wheel and five native runtime wheels, then a single serialized job checks and publishes all six to the project PyPI registry. The [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows target and the explicit exclusion of Windows arm64. diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md index 653fb3d9b7..4cc6e20459 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md @@ -48,7 +48,7 @@ exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真 ### 构建流水线与产物 -[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`,其中包含作为载体根目录 `runtime-bootstrap.mjs` 的 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) → 恢复 legacy deploy 遗漏的直接工作区包,并拒绝剩余的 manifest 缺口 → 将暂存依赖中的符号链接替换为目标文件内容,删除包管理器的 `.bin` 链接,并在仍有任何符号链接时失败 → 验证已部署的 bootstrap,并注入以该文件为 bin 的 pkg 配置及覆盖动态读取 profile、bundle、前端、preset、原生库与配置文件的 assets → 暂存目标平台的 `node-pty` addon → 每个构建目标调用一次 `pkg --sea` → 将 `deepseek-harness-sdk-runtime--` 写入 `dist-exe/`,并将它与对应目标的 Office 目录一同拷回运行时目录。该 bootstrap 由 Python runtime 拥有;普通启动时它调用公开 CLI export,而提供方私有选择会分派到同一个 `@deepseek-ai/dsh-subprocess-local/runner` 核心,不改变 CLI 语法,也不增加另一个可执行文件;[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责这条私有路径。Linux CI 会在匹配的 manylinux 2.28 容器中重新构建 `pty.node`,因为 legacy deploy 会遗漏这一安装副作用。每个目标都会把对应的原生 `@vscode/ripgrep` 二进制复制到可执行文件旁,作为必需的 `-rg` 伴随文件;pkg 运行时通过 `process.pkg` 选择该伴随文件,普通 Node 执行则直接使用 `@vscode/ripgrep`。macOS 使用对应目标的预构建产物,并额外生成所需的 `-spawn-helper`。部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 为 pkg 提供稳定的单实例布局,再由显式物化步骤消除符号链接;关闭对等依赖自动安装可防止未声明的对等依赖扩大闭包;`link-workspace-packages` 选择直接工作区依赖。[`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) 将传递的 `@deepseek-ai/cosmokit` 与 `@deepseek-ai/schemastery` semver 请求覆盖到固定的 vendor 源码,使 legacy deploy 不会从注册表解析这些未发布名称。 生产部署允许未使用的工作区补丁,因为开发工具可能不属于运行时闭包;闭包内包的补丁应用失败仍会中止部署。工作区安装继续严格校验未使用的补丁。 +[`scripts/build-exe-for-python-sdk.ts`](../../../../scripts/build-exe-for-python-sdk.ts):运行时闭包校验 → `pnpm run build` →(清空后)`pnpm --filter dsh-python-runtime-closure deploy --legacy --prod --config.allow-unused-patches=true --config.node-linker=hoisted --config.auto-install-peers=false --config.link-workspace-packages=true --config.hoist-workspace-packages=false` **直接写入** `python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/`,其中包含作为载体根目录 `runtime-bootstrap.mjs` 的 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) → 恢复 legacy deploy 遗漏的直接工作区包,并拒绝剩余的 manifest 缺口 → 将暂存依赖中的符号链接替换为目标文件内容,删除包管理器的 `.bin` 链接,并在仍有任何符号链接时失败 → 验证已部署的 bootstrap,并注入以该文件为 bin 的 pkg 配置及覆盖动态读取 profile、bundle、前端、preset、原生库与配置文件的 assets → 暂存目标平台的 `node-pty` addon → 每个构建目标调用一次 `pkg --sea` → 将 `deepseek-harness-sdk-runtime--` 写入 `dist-exe/`,并将它与对应目标的 Office 目录一同拷回运行时目录。该 bootstrap 由 Python runtime 拥有;普通启动时它调用公开 CLI export,而提供方私有选择会分派到同一个 `@deepseek-ai/dsh-subprocess-local/runner` 核心,不改变 CLI 语法,也不增加另一个可执行文件;[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责这条私有路径。Linux CI 会在匹配的 manylinux 2.28 容器中重新构建 `pty.node`,因为 legacy deploy 会遗漏这一安装副作用。每个目标都会把对应的原生 `@vscode/ripgrep` 二进制复制到可执行文件旁,作为必需的 `-rg` 伴随文件;pkg 运行时通过 `process.pkg` 选择该伴随文件,普通 Node 执行则直接使用 `@vscode/ripgrep`。macOS 使用对应目标的预构建产物,并额外生成所需的 `-spawn-helper`。部署标志都有实测依据:未启用 `inject-workspace-packages` 时必须使用 `--legacy`;`hoisted` 提供包目录,再由暂存步骤消除符号链接;只有 pnpm 在载荷根目录记录了相同依赖标识时,暂存步骤才删除对应的嵌套工作区副本,以保留服务单例;关闭工作区提升可排除无关的工作区源码包;关闭对等依赖自动安装可防止未声明的对等依赖扩大闭包;`link-workspace-packages` 选择直接工作区依赖。[`pnpm-workspace.yaml`](../../../../pnpm-workspace.yaml) 将传递的 `@deepseek-ai/cosmokit` 与 `@deepseek-ai/schemastery` semver 请求覆盖到固定的 vendor 源码,使 legacy deploy 不会从注册表解析这些未发布名称。 生产部署允许未使用的工作区补丁,因为开发工具可能不属于运行时闭包;闭包内包的补丁应用失败仍会中止部署。工作区安装继续严格校验未使用的补丁。 CI 使用 [`.github/workflows/build-exe-for-python-sdk.yml`](../../../../.github/workflows/build-exe-for-python-sdk.yml):[安装后 wheel 包验证](../testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md)在拉取请求上运行 Linux/Windows x64,在 master 推送上运行 Linux ARM64 与两种 macOS 架构。[公开发布工作流](../../archived/process/2026-08-11-python-publication-workflow.md)调用它构建全部五个目标;`workflow_dispatch` 仍可选择部分目标。linux-x64、linux-arm64(`ubuntu-24.04-arm`)、macos-arm64、macos-x64(`macos-15-intel`)与 win-x64(`windows-2025`)分别进行原生构建,并在适用平台缓存 `~/.pkg-cache`;macOS 的 ad-hoc 签名由 pkg 处理。每个平台都把发布形态的 SDK wheel 包与运行时 wheel 包安装到 checkout 外的干净 venv,证明包与可执行文件来源,再通过公开 SDK 与直接 NDJSON JSON-RPC 运行完整 keyless 场景。可信拉取请求与 master 推送还会在各自选定的目标上运行真实 DeepSeek 双轮工具冒烟测试;fork 与 Dependabot head 不会获得密钥。Linux 会检查可执行文件和原生 addon 各自的 GLIBC 依赖,并额外运行 manylinux 2.28 冒烟测试;macOS 则检查 runtime、ripgrep 与 PTY helper 的架构,并验证三个载荷的部署目标都符合 wheel 包标签。完整构建五个目标时保留 6 个产物,每个产物只含一个发布文件:平台无关的 SDK wheel 包与 5 个原生运行时 wheel 包;手动选择部分目标时保留 SDK wheel 与所选运行时 wheel。裸 exe 与源码包只作为测试中间输入。[`.gitlab-ci.yml`](../../../../.gitlab-ci.yml) 只接受版本与根目录 `package.json` 匹配的 `python-v` 标签流水线,构建一个 SDK wheel 包和 5 个原生运行时 wheel 包,再由单个串行任务校验并将这 6 个文件发布到项目的 PyPI 注册表。[`python/sdk-runtime` README](../../../../python/sdk-runtime/README.zh.md)负责 Windows 目标及对 Windows arm64 的明确排除。 diff --git a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml deleted file mode 100644 index 6d5a779689..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml +++ /dev/null @@ -1,157 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-12-agent-scope-runtime-design.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md -/agent-note-agent-scope-runtime-design-and-correctness: - en: b63ecbd21fc1f427 - zh: 4990d96d76888802 -/agent-note-agent-scope-runtime-design-and-correctness/problem: - en: ae7b688bd991a6df - zh: 73b6b28755852054 -/agent-note-agent-scope-runtime-design-and-correctness/decision: - en: cfd6abca8404c7d4 - zh: a9372aae85e49f55 -/agent-note-agent-scope-runtime-design-and-correctness/cordis-model-context-fiber-effect-receiver-and-waterfall: - en: 4d4bdb629a57139d - zh: 7b4d21d9eb42d94b -/agent-note-agent-scope-runtime-design-and-correctness/cordis-model-context-fiber-effect-receiver-and-waterfall/a-context-is-an-ownership-path-through-one-service-graph: - en: 201a1882518455e2 - zh: 830e25eab7ed37db -/agent-note-agent-scope-runtime-design-and-correctness/cordis-model-context-fiber-effect-receiver-and-waterfall/fibers-and-effects-make-cleanup-structural: - en: d0ef6e1b5d453350 - zh: 4a774ac9daf20849 -/agent-note-agent-scope-runtime-design-and-correctness/cordis-model-context-fiber-effect-receiver-and-waterfall/receivers-route-listeners-waterfalls-compose-decisions: - en: 594e198a8b566049 - zh: 50378e7a2a533dff -/agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer: - en: fa59d22549e9e44a - zh: 80904729042e87b3 -/agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer/scope-identity-uses-object-identity: - en: 3a05a7bfb2197f58 - zh: fbb599031788b70b -/agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer/registry-reads-overlay-one-exact-layer: - en: 28636ae6d55935d7 - zh: 33a989f049de2f3e -/agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer/fused-dispatch-helpers-prevent-subject-drift: - en: 638e3711206e3c41 - zh: 61d370123d30c1ed -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation: - en: ef58215c8554180b - zh: 44571074148d2467 -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation/registry-entries-are-the-only-live-identity-records: - en: 092b49671e83df98 - zh: 330ea6e45c4deb99 -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation/the-transaction-owns-preparation-before-awaiting-it: - en: 3940920908ec6066 - zh: 888e36ca43611d30 -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation/setup-is-trusted-composition-inside-a-private-world: - en: 40a73b8eede856ee - zh: 7dab8f2548cb92ea -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation/publication-has-one-ordered-commit-path: - en: 227eff7a5adacd0e - zh: b9ee423c2dd4212f -/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation/teardown-preserves-work-before-revoking-registrations: - en: 1cbe77cff192815d - zh: 7f517936a8aa469e -/agent-note-agent-scope-runtime-design-and-correctness/session-append-materialize-validate-commit-notify: - en: cc37e93081fea4cb - zh: daf2234f84e4c9bf -/agent-note-agent-scope-runtime-design-and-correctness/session-append-materialize-validate-commit-notify/durable-data-is-materialized-once: - en: d0a57b0ac227d84c - zh: e9ae6ced0e65fe49 -/agent-note-agent-scope-runtime-design-and-correctness/session-append-materialize-validate-commit-notify/pre-commit-listeners-can-veto-post-commit-observers-cannot: - en: 15255d3d0b62fac0 - zh: c1995d5aa3c78f0a -/agent-note-agent-scope-runtime-design-and-correctness/trust-boundaries-copy-only-when-ownership-actually-changes: - en: 22b381902fe903f3 - zh: 8a2efe9a8b458bba -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes: - en: 456ac0359d3c02f6 - zh: 9099e15d22139751 -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/one-resolver-defines-the-tool-view: - en: 120e1c79f0b99b1a - zh: 1c21a5b1d6b08235 -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/tool-execution-owns-identity-and-boundary-materialization: - en: 41f1f2ad73d83d73 - zh: e6bff63958ede05e -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/the-assembly-waterfall-owns-the-final-model-visible-composition: - en: 5c5880ccea139058 - zh: 3e5b7d10cb4c6ff8 -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/structured-output-commits-only-authoritative-outcomes: - en: a9a711cf31559765 - zh: e9083b000fc89128 -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/three-execution-boundaries-are-deliberately-one-way: - en: a109f3fadf338061 - zh: 12eb46fe775fa46a -/agent-note-agent-scope-runtime-design-and-correctness/tools-and-prompts-one-view-authoritative-assembly-committed-outcomes/skill-and-approval-services-trust-typed-callers: - en: 9572833d47f779e6 - zh: 7ff491d309060030 -/agent-note-agent-scope-runtime-design-and-correctness/subagents-publication-is-the-start-promise: - en: b9216021f8f50bcc - zh: fda52f9434cc8191 -/agent-note-agent-scope-runtime-design-and-correctness/subagents-publication-is-the-start-promise/the-service-contract-has-one-cancellation-channel: - en: 84a4be83b78c045b - zh: 043e14cf628413cd -/agent-note-agent-scope-runtime-design-and-correctness/subagents-publication-is-the-start-promise/in-process-providers-reuse-the-core-transaction: - en: b5e0b3dcf0dff750 - zh: 63905010383781b9 -/agent-note-agent-scope-runtime-design-and-correctness/subagents-publication-is-the-start-promise/acp-providers-own-the-process-until-readiness-or-cleanup: - en: aa0c00cee368c7e6 - zh: 767539b0f4d56f55 -/agent-note-agent-scope-runtime-design-and-correctness/workflows-and-acp-processes-retain-only-independent-async-facts: - en: effbb0acf7f66bed - zh: e0cc9ad6ac0777de -/agent-note-agent-scope-runtime-design-and-correctness/workflows-and-acp-processes-retain-only-independent-async-facts/workflow-children-are-pending-starts-or-published-records: - en: 9f05eed800f3413c - zh: 8230a28a0f94bffa -/agent-note-agent-scope-runtime-design-and-correctness/workflows-and-acp-processes-retain-only-independent-async-facts/terminal-result-and-physical-cleanup-remain-separate: - en: 5ad141c6df9f4fc3 - zh: 63effa4f14f3c530 -/agent-note-agent-scope-runtime-design-and-correctness/workflows-and-acp-processes-retain-only-independent-async-facts/acp-prompt-settlement-does-not-depend-on-update-delivery: - en: d39f73f50f0370bd - zh: 3889c1db46de560e -/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement: - en: 840564e766d2f05c - zh: 059243afea7eb9c0 -/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/types-make-the-ordinary-path-hard-to-misuse: - en: 9183eb29dddfd97f - zh: e6ce13295e6c3e65 -/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/runtime-invariants-cover-cross-service-facts: - en: fabafbebc0ab4f95 - zh: 06d94a98ed5aa40c -/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/generated-artifacts-keep-public-contracts-aligned: - en: d0ddc5f38289225d - zh: 781d910228d5e334 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered: - en: 5eef7873a795c8f0 - zh: 3e8ba109a6e29ba5 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/use-a-transparent-proxy-as-the-scope-carrier: - en: 199c7345a237e7e9 - zh: b262d5ad0c7cb562 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/reserve-agent-and-session-ids-before-setup: - en: 3a8cb6969139dd84 - zh: 2913bb68433c9ec7 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/snapshot-every-typed-same-process-argument: - en: 3e1f19f909f8d138 - zh: 0727777e34964afe -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/give-readiness-cancellation-and-disposal-separate-controllers: - en: f2c6ecbd139bbdb0 - zh: 0ff5b1e192dcb376 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/keep-synchronous-subagent-start-plus-run-started: - en: 7f5669933cb35c9c - zh: c55e35d4c175af36 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/restore-selected-prompt-or-tool-contributions-after-assembly: - en: a2b2e48d9ccda310 - zh: 3a4e925fde9d05b0 -/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered/remove-worker-process-lifecycle-guards-with-same-process-hardening: - en: 662e0904d90a3a94 - zh: 84c5e49f7cc59b32 -/agent-note-agent-scope-runtime-design-and-correctness/consequences: - en: 0d781b2dc4c41ddb - zh: 844652a4aaac37aa -/agent-note-agent-scope-runtime-design-and-correctness/consequences/what-the-design-guarantees: - en: eb2001592d7bd7b1 - zh: e534dbb6daa9af32 -/agent-note-agent-scope-runtime-design-and-correctness/consequences/costs-and-limits: - en: e9383713d56b9e10 - zh: d3ab00aea8ecf4e0 diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml index 7d88d4ba73..ca2693afef 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml @@ -15,11 +15,11 @@ en: 4921a060c657b790 zh: 2f9632469c6f0b60 /agent-note-provider-routed-llm-adapters-and-a-generic-pi-ai-backend/decision/explicit-pi-ai-provider-profiles: - en: 5461685553dac663 - zh: e5c7e0ad7ac84e29 + en: e24610c474a94bc4 + zh: 96a89035cb8eabf9 /agent-note-provider-routed-llm-adapters-and-a-generic-pi-ai-backend/decision/recorded-assistant-route-and-replay-state: - en: 8439e90c9f6d7642 - zh: daeda3ce5146c9f5 + en: ed60d078eacc67df + zh: a22631faf93deee2 /agent-note-provider-routed-llm-adapters-and-a-generic-pi-ai-backend/decision/propagate-the-target-through-every-request-producer: en: ba1908bc1517ed23 zh: 1c9353a422cf74e1 diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md index 1001b10e18..b205104489 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.md @@ -30,7 +30,7 @@ A provider has exactly one adapter owner in a Cordis context. `dsh-llm-deepseek` `dsh-llm-pi-ai` takes one non-empty list of provider profiles. Provider names must be unique within the list and present in pi-ai's `getProviders()` result. Each profile contains the provider name plus optional `apiKey`, `baseURL`, headers, reasoning level and budgets, cache retention, transport, SDK timeouts, a Harness stream-idle timeout, and a provider-owned `retryPolicy`. The adapter forces pi-ai's `maxRetries` to zero so one `stream()` call makes one visible provider attempt, while `dsh-llm-retry` executes the resolved policy at the agent failed-step extension point. Credentials are never global: an explicit key applies only to its profile, while an absent key lets pi-ai resolve its standard environment variable, OAuth token, AWS credential chain, Google ADC, or other provider-native ambient authentication. An explicitly empty key is invalid configuration rather than an environment fallback. -The plugin registers configured provider names against one `PiAiAdapter` in one atomic call. Each immutable request snapshot combines the effective profiles and their serviceable model descriptors. Catalog-external models require an explicit or inferable protocol and endpoint. Stored catalog errors remain visible and repairable under the [settings catalog recovery decision](../bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md), while writes validate changed providers and requests reject the selected failed model before network I/O. +The plugin registers configured provider names against one `PiAiAdapter` in one atomic call. Each immutable request snapshot combines the effective profiles and their serviceable model descriptors. Catalog-external models require an explicit or inferable protocol and endpoint. Stored catalog errors remain visible and repairable under the [settings catalog recovery reference](../../../../packages/llm/llm-pi-ai/README.md), while writes validate changed providers and requests reject the selected failed model before network I/O. The adapter calls pi-ai's `streamSimple()` so each catalog model chooses its registered API implementation, including OpenAI Responses instead of Chat Completions where the descriptor says `openai-responses`. Harness temperature, maximum tokens, signal, session id, and the profile's common stream options flow through directly. Profile headers merge with the mandatory Harness attribution headers, with Harness attribution winning its reserved names. The adapter no longer maintains DeepSeek-specific payload rewrites or a provider-protocol matrix. @@ -42,7 +42,7 @@ Assistant messages carry the request's `provider` and `model`, plus an optional A terminal successful `finish` chunk may carry replay state as a `ReplayEnvelope`: opaque response-level metadata plus optional per-block entries aligned with the emitted block sequence. `BlockAssembler` makes one keep/drop decision for content and metadata — when max-token assembly drops a tool call, the envelope loses the entry at the same position — so the state the loop attaches to the assembled assistant message's model source always describes the stored blocks, per the [max-token replay-state alignment decision](../../archived/bug-fix/2026-08-15-max-token-replay-state-alignment.md). The loop exposes no response-rewrite hook. Error and aborted responses do not produce a normal assistant message and therefore do not enter future model history. -The pi-ai replay state fills that envelope with a versioned, minimal projection of its successful `AssistantMessage`: a response half (source API/provider/model, response id/model, optional provider-native effort, stop reason) and per-block text, thinking, and tool-call signatures. The [pi-ai upgrade compatibility decision](../bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md#decision) defines requested-versus-native Anthropic model identity and effort preservation. It does not duplicate text or tool arguments already carried by Harness content blocks, and it omits diagnostics, timestamps, usage, and errors. On a later request, `LlmRuntime` gives replay state to the target adapter only when the historical provider and target provider are currently owned by the same adapter instance. That adapter combines the logged Harness content with replay state when it can restore the historical response, and owns any required cross-model or cross-provider conversion. Durable content stays authoritative: an adapter receiving replay state it cannot use — an unknown kind or version, malformed metadata, or a block shape that no longer matches the content — degrades that message to provider-neutral conversion with a diagnostic; a different adapter receives only provider-neutral content plus provider/model fields. +The pi-ai replay state fills that envelope with a versioned, minimal projection of its successful `AssistantMessage`: a response half (source API/provider/model, response id/model, optional provider-native effort, stop reason) and per-block text, thinking, and tool-call signatures. The [pi-ai upgrade compatibility reference](../../../../packages/llm/llm-pi-ai/README.md) defines requested-versus-native Anthropic model identity and effort preservation. It does not duplicate text or tool arguments already carried by Harness content blocks, and it omits diagnostics, timestamps, usage, and errors. On a later request, `LlmRuntime` gives replay state to the target adapter only when the historical provider and target provider are currently owned by the same adapter instance. That adapter combines the logged Harness content with replay state when it can restore the historical response, and owns any required cross-model or cross-provider conversion. Durable content stays authoritative: an adapter receiving replay state it cannot use — an unknown kind or version, malformed metadata, or a block shape that no longer matches the content — degrades that message to provider-neutral conversion with a diagnostic; a different adapter receives only provider-neutral content plus provider/model fields. This state is model-visible replay input and therefore follows the existing [reconstructable-request rule](2026-07-05-reconstructable-requests.md): it is present in both the terminal `finish` chunk and the assembled `assistant/message` model source that drives derivation. Resume and fork preserve it verbatim. Compaction that shadows the assistant message also removes its replay state from the active surface; the summary is ordinary provider-neutral content. diff --git a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md index c5bff0ef40..b838d7255a 100644 --- a/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.zh.md @@ -30,7 +30,7 @@ Status: implemented `dsh-llm-pi-ai` 接受一个非空的提供方配置列表。列表内的提供方名称必须唯一,并且存在于 pi-ai 的 `getProviders()` 结果中。每项配置包含提供方名称,以及可选的 `apiKey`、`baseURL`、headers、推理级别和预算、缓存保留设置、传输方式、SDK 超时、Harness 流空闲超时,以及由提供方拥有的 `retryPolicy`。适配器强制将 pi-ai 的 `maxRetries` 设为零,使一次 `stream()` 调用只发起一次可见的提供方请求;`dsh-llm-retry` 则在 agent 失败步骤扩展点上执行解析后的策略。凭据不设全局值:显式密钥仅对所属配置生效;未提供密钥时,pi-ai 使用标准环境变量、OAuth token、AWS 凭据链、Google ADC 或其他提供方原生环境认证。显式空密钥属于无效配置,不会回退到环境认证。 -插件通过一次原子调用,将已配置的提供方名称注册到同一个 `PiAiAdapter`。每个不可变请求快照组合有效 profile 与可服务模型的描述符。目录外模型需要显式指定或可推断的协议与端点。根据[设置目录恢复决策](../bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.zh.md),已存储的目录错误保持可见、可修复,写入仍会校验已修改提供方,请求则在网络 I/O 前拒绝所选错误模型。 +插件通过一次原子调用,将已配置的提供方名称注册到同一个 `PiAiAdapter`。每个不可变请求快照组合有效 profile 与可服务模型的描述符。目录外模型需要显式指定或可推断的协议与端点。根据[设置目录恢复参考](../../../../packages/llm/llm-pi-ai/README.zh.md),已存储的目录错误保持可见、可修复,写入仍会校验已修改提供方,请求则在网络 I/O 前拒绝所选错误模型。 适配器调用 pi-ai 的 `streamSimple()`,因此每个目录模型会选择其注册的 API 实现;描述符为 `openai-responses` 时使用 OpenAI Responses,而非 Chat Completions。Harness 的 temperature、最大 token 数、signal、session ID,以及提供方配置中的通用流选项均直接传递。配置 headers 与 Harness 强制归因 headers 合并;发生保留名称冲突时,以 Harness 归因为准。适配器不再维护 DeepSeek 专用 payload 重写或提供方协议矩阵。 @@ -42,7 +42,7 @@ pi-ai 的通用流选项不支持停止序列。若 Harness `stop` 选项已定 成功的终止 `finish` 分片可以以 `ReplayEnvelope` 形式携带回放状态:不透明的响应级元数据,加上与发射块序列对齐的可选逐块条目。`BlockAssembler` 对内容与元数据只做一次保留/丢弃决定——max-token 组装丢弃工具调用时,数据同一位置的条目一并丢弃——因此 agent loop 附加到已组装助手消息模型来源中的状态始终描述存储的块,见 [max-token 回放状态对齐决定](../../archived/bug-fix/2026-08-15-max-token-replay-state-alignment.md)。agent loop 不公开响应改写钩子。错误或中止响应不会生成正常助手消息,因此不会进入后续模型历史。 -pi-ai 回放状态用其成功 `AssistantMessage` 的带版本最小投影填充该结构:一个响应半区(源 API/提供方/模型、响应 ID/模型、可选的提供方原生 effort、停止原因),以及逐块的文本签名、thinking 签名和工具调用签名。[pi-ai 升级兼容性决定](../bug-fix/2026-09-05-pi-ai-upgrade-compatibility.zh.md#decision) 定义请求模型与 Anthropic 原生模型身份的区别,以及 effort 的保留规则。它不会重复 Harness 内容块中已有的文本或工具参数,也不包含诊断信息、时间戳、用量或错误。后续请求中,只有历史提供方和目标提供方当前归同一个适配器实例所有时,`LlmRuntime` 才会把回放状态交给目标适配器。适配器在能够恢复历史响应时,将 Harness 记录的内容与回放状态组合,并负责所需的跨模型或跨提供方转换。持久化内容保持权威:适配器收到无法使用的回放状态——未知 kind 或版本、格式错误的元数据、或与内容不再匹配的块结构——会把该消息降级为提供方无关转换并带出诊断;其他适配器只能收到提供方无关的内容以及提供方/模型字段。 +pi-ai 回放状态用其成功 `AssistantMessage` 的带版本最小投影填充该结构:一个响应半区(源 API/提供方/模型、响应 ID/模型、可选的提供方原生 effort、停止原因),以及逐块的文本签名、thinking 签名和工具调用签名。[pi-ai 升级兼容性参考](../../../../packages/llm/llm-pi-ai/README.zh.md) 定义请求模型与 Anthropic 原生模型身份的区别,以及 effort 的保留规则。它不会重复 Harness 内容块中已有的文本或工具参数,也不包含诊断信息、时间戳、用量或错误。后续请求中,只有历史提供方和目标提供方当前归同一个适配器实例所有时,`LlmRuntime` 才会把回放状态交给目标适配器。适配器在能够恢复历史响应时,将 Harness 记录的内容与回放状态组合,并负责所需的跨模型或跨提供方转换。持久化内容保持权威:适配器收到无法使用的回放状态——未知 kind 或版本、格式错误的元数据、或与内容不再匹配的块结构——会把该消息降级为提供方无关转换并带出诊断;其他适配器只能收到提供方无关的内容以及提供方/模型字段。 该状态属于模型可见的回放输入,因此遵循现有的[请求可重建规则](2026-07-05-reconstructable-requests.zh.md):它同时存在于终止 `finish` 分片和驱动派生的已组装 `assistant/message` 模型来源中。恢复和 fork 会原样保留该状态。压缩(compaction)遮蔽助手消息时,也会从活动 surface 中移除其回放状态;摘要属于普通的提供方无关内容。 diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml index a90633f326..9c71ffc3fa 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml @@ -9,8 +9,8 @@ en: a26991200892c735 zh: 04a7dfaca64e3f0c /agent-note-initiating-agent-scope-over-asynclocalstorage/decision: - en: 43a4f0c503b30d95 - zh: a6a780584f94ed73 + en: a30ffc5c5e7b24a9 + zh: cc09b1d6e2b0228f /agent-note-initiating-agent-scope-over-asynclocalstorage/verification: en: 312f5c4b843d515e zh: a8993e3b84844fab diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md index ab11da116a..da316715f7 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md @@ -28,7 +28,7 @@ Initiator scope does not own detached work: registry drain tracks only the Promi A host-aware transport may derive a deployment-owned header such as `X-Harness-Session-Id` from `ctx.agents.requireInitiator().session.id`; the header is absent from model-visible schema and arguments. No production MCP or Web transport adopts such a header in this decision. A test-double transport proves the trusted boundary without assigning host routing policy to an existing provider-neutral seam. -This decision extends the [Agent registration-scope contract](2026-07-08-agent-scope-contexts.md) and its [runtime design](2026-07-12-agent-scope-runtime-design.md); it does not change their static `agent.ctx` meaning. The [explicit runtime-identity decision](2026-08-31-explicit-agent-runtime-identity.md) keeps initiator scope limited to private asynchronous chains while lifecycle, ownership, event, and wire interfaces carry their subjects directly. +This decision extends the [Agent registration-scope contract](../../../../packages/core/scope/README.md) and its [runtime design](../../../../packages/core/agent-loop/README.md); it does not change their static `agent.ctx` meaning. The [explicit runtime-identity decision](2026-08-31-explicit-agent-runtime-identity.md) keeps initiator scope limited to private asynchronous chains while lifecycle, ownership, event, and wire interfaces carry their subjects directly. ## Verification diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md index 343acba5f9..f23ca50f2b 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md @@ -28,7 +28,7 @@ harness 中存在两种有用但不同的上下文概念。Cordis `Context` 负 宿主感知的传输层可以从 `ctx.agents.requireInitiator().session.id` 推导由部署方拥有的 `X-Harness-Session-Id` 等请求头;模型可见 schema 和参数中不包含该请求头。本决策不让现有生产 MCP 或 Web 传输层采用此请求头。测试替身传输层用于证明可信边界,而不会把宿主路由策略分配给现有的提供方无关 seam。 -本决策扩展 [Agent 注册作用域约定](2026-07-08-agent-scope-contexts.zh.md)及其[运行时设计](2026-07-12-agent-scope-runtime-design.zh.md),不会改变其中 `agent.ctx` 的静态含义。[显式运行时身份决策](2026-08-31-explicit-agent-runtime-identity.zh.md)把发起方作用域限制在私有异步调用链内,同时让生命周期、归属、事件和协议接口直接携带各自的主体。 +本决策扩展 [Agent 注册作用域约定](../../../../packages/core/scope/README.zh.md)及其[运行时设计](../../../../packages/core/agent-loop/README.zh.md),不会改变其中 `agent.ctx` 的静态含义。[显式运行时身份决策](2026-08-31-explicit-agent-runtime-identity.zh.md)把发起方作用域限制在私有异步调用链内,同时让生命周期、归属、事件和协议接口直接携带各自的主体。 ## 验证 diff --git a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml deleted file mode 100644 index a7c6542f24..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-16-explicit-turn-cancellation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md -/agent-note-explicit-turn-cancellation-capability: - en: 729ed6291e9d4d2c - zh: 32ae526ef41199da -/agent-note-explicit-turn-cancellation-capability/problem: - en: 0a7826345be2a509 - zh: 430fccb5c4d6a5e2 -/agent-note-explicit-turn-cancellation-capability/decision: - en: 2c15edb35f38e7e5 - zh: fa5145d26e3bff20 -/agent-note-explicit-turn-cancellation-capability/verification: - en: a7ab1d06542be1c0 - zh: 1687979b7f2460fa -/agent-note-explicit-turn-cancellation-capability/alternatives-considered: - en: bf69bbf7eb280306 - zh: a434a0ecb5008b2b -/agent-note-explicit-turn-cancellation-capability/consequences: - en: 1066aff1aec4ed01 - zh: 31d6a60496e2e730 diff --git a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml deleted file mode 100644 index b48fcf3403..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.i18n.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-gui-web-client-architecture.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer: - en: e940be9ef95509ff - zh: c06cea53bb49c718 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/problem: - en: c5a3dda6ccd57c9e - zh: 122307b113bb1c09 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/decision: - en: 29742775aee9fbc3 - zh: 54a2df8816e2ea2e -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/the-client-cordis-tree-and-the-loading-chain: - en: 23b0b6c3f29e4f5e - zh: 53bf897b502c645b -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/the-slot-system-how-the-page-composes: - en: 1661f3dd74dc9df6 - zh: 3152467c7dd8e6ef -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/services-and-scope-addressing: - en: 9da9311f677c5da6 - zh: 9cb16a92cee87055 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/the-data-object-layer-packages-api-session-controller-src-client: - en: 909682e20c0d23a1 - zh: ed1defbcf7745e8e -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/the-react-face-packages-client-ui-renderer: - en: 0971d6196b5358fd - zh: 26175d648feccfd9 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/directory-shape: - en: 74c45680c8aab51c - zh: fd431ae5d3656038 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/how-to-develop: - en: cbc6abcd9f291868 - zh: 0078f15f2d08a02e -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/consequences: - en: 56a9086da5783306 - zh: 8147576101d22aa0 -/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/alternatives-considered: - en: 8bedf2e688b14322 - zh: 7cda12f7474832ee diff --git a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml deleted file mode 100644 index f8fe485684..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-package-invariant-runtime-contracts.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md -/agent-note-meaningful-package-invariant-contracts: - en: e5db3b3373f72761 - zh: 312b83d320d9aaab -/agent-note-meaningful-package-invariant-contracts/problem: - en: 220a4ceb3cd9b9c9 - zh: 9eac93e1762b2914 -/agent-note-meaningful-package-invariant-contracts/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-meaningful-package-invariant-contracts/decision/published-assertions-must-be-meaningful: - en: 11a1c080592def10 - zh: d1189de665f9e327 -/agent-note-meaningful-package-invariant-contracts/decision/representative-implemented-checks: - en: 2f7ae4ed588d6de8 - zh: ed7ea9ccc6c54e87 -/agent-note-meaningful-package-invariant-contracts/decision/repository-gate-and-tests: - en: 37e646754f13dd65 - zh: 355ebf3ce1403117 -/agent-note-meaningful-package-invariant-contracts/alternatives-considered: - en: 74c6a5a0cfadbf88 - zh: e41356bb1a8c1fdf -/agent-note-meaningful-package-invariant-contracts/consequences: - en: 1f8d53063fb5903e - zh: 5edd23739749b065 diff --git a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml deleted file mode 100644 index 9e8a30179e..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-zstandard-jsonl-session-logs.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md -/agent-note-zstandard-jsonl-session-logs: - en: b46a6b48493de93e - zh: 0e394abf1d11fd43 -/agent-note-zstandard-jsonl-session-logs/problem: - en: da8b3d94d10b5bc4 - zh: da9c6d98a300b7bb -/agent-note-zstandard-jsonl-session-logs/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-zstandard-jsonl-session-logs/decision/configuration-and-suffix-ownership: - en: 1f29970aecc71980 - zh: 94414765645bace0 -/agent-note-zstandard-jsonl-session-logs/decision/frame-and-write-path: - en: da213ed32dfa6000 - zh: a0f3cd11684dbdc9 -/agent-note-zstandard-jsonl-session-logs/decision/read-listing-and-crash-recovery: - en: 63d4fe7d9c2a0a8a - zh: 2c4ef3e6aad73195 -/agent-note-zstandard-jsonl-session-logs/decision/consumers-and-verification: - en: 6d9d16d610b80e55 - zh: efa083ffaa7301e0 -/agent-note-zstandard-jsonl-session-logs/alternatives-considered: - en: c5f447029fb644f7 - zh: 5b84162e87bc6c86 -/agent-note-zstandard-jsonl-session-logs/consequences: - en: e721552f16340e4c - zh: d5be8c3ffd1c7cc2 diff --git a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml deleted file mode 100644 index 531d05cdc9..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-20-canonical-tool-output-contract.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-20-canonical-tool-output-contract.md -/agent-note-canonical-tool-output-contract: - en: 01677f7875da9579 - zh: 41bae59177ba5125 -/agent-note-canonical-tool-output-contract/problem: - en: d0bc9840998f0530 - zh: 2601671ec694a78d -/agent-note-canonical-tool-output-contract/decision: - en: db088b8225a4bf3d - zh: bfb0caa015b2b7df -/agent-note-canonical-tool-output-contract/alternatives-considered: - en: d82a0ac0461ab120 - zh: bedea17fb4fb4f51 -/agent-note-canonical-tool-output-contract/consequences: - en: 32724ff8f6b3b03f - zh: bc7512ff05d00157 diff --git a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml deleted file mode 100644 index bf011b57b9..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-20-unified-json-value-schema-dsl.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md -/agent-note-unified-json-value-schema-dsl: - en: 8ba380205c472f4e - zh: de92e20dc90e8037 -/agent-note-unified-json-value-schema-dsl/problem: - en: 5c7e79e37c007896 - zh: 759f5803141b0960 -/agent-note-unified-json-value-schema-dsl/decision: - en: 8f08bbafc8939a46 - zh: 2ed8255c1e2b812b -/agent-note-unified-json-value-schema-dsl/alternatives-considered: - en: 1d32d6195d6fca08 - zh: ea3a30126dca95f7 -/agent-note-unified-json-value-schema-dsl/consequences: - en: 14d0a6a337503e80 - zh: c7ffec8760a710af diff --git a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.i18n.yaml index eeeda135fe..2ce9443416 100644 --- a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md /agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat: - en: 43ed5b2dfd8043fc - zh: 04b6fdd0435a265a + en: 0b4cb1d5ce96bedc + zh: 7c1bb3080595d445 /agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/problem: en: 4f8d6891a8ba7061 zh: 5a619e562e00458d /agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/decision: - en: a81cddbebc1a97a3 - zh: aa57738c9f08c07e + en: b6c8cb5ad821e582 + zh: 0a3fbb3752b33571 /agent-note-the-slot-system-standard-single-register-four-props-shares-and-the-framework-store-seat/decision/root-is-the-only-a-priori-slot: en: 43b856b4a8cf371f zh: cde298ab8b8a3815 diff --git a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md index 98585a42f7..b08c0f4be8 100644 --- a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md +++ b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-22-slot-type-chain-implementation.zh.md) -> Scope: the definitive slot-system design for the web client — how UI plugins compose the page, where render authority lives, how component props are typed, and where business live-data goes. The [web client architecture RFC](2026-07-19-gui-web-client-architecture.md) owns the surrounding context (loading chain, object layer, services) and defers its slot sections here. +> Scope: the definitive slot-system design for the web client — how UI plugins compose the page, where render authority lives, how component props are typed, and where business live-data goes. The [Web Client reference](../../../../docs/subsystems/web-client.md) documents the surrounding loading chain, object layer, and services. ## Problem @@ -12,7 +12,7 @@ The page is composed at runtime from independently loaded plugins, so the UI nee ## Decision -Global main-panel selection and its root lifetime are defined by the [global main-panels decision](2026-09-08-global-main-panels.md). +Global main-panel selection and its root lifetime are defined by the [global main-panels reference](../../../../packages/client/ui-layout/README.md). One sentence: **the ui-renderer renders only `'root'`; a plugin composes UI through a single `register` call that simultaneously occupies a slot, declares+authorizes its child slots, declares its store, and injects its business face; components are pure functions whose props arrive in four shares, each auto-derived from its single source of truth.** diff --git a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md index d1093537cc..d36cce53b7 100644 --- a/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-22-slot-type-chain-implementation.md) | 中文 -> 范围:Web 客户端 slot 体系的终版设计——UI 插件如何拼合页面、渲染权威落在哪里、组件 props 如何定型、业务活数据住在哪里。周边语境(装载链、对象层、服务)归 [Web 客户端架构 RFC](2026-07-19-gui-web-client-architecture.zh.md) 所有,其 slot 各节移交本文。 +> 范围:Web 客户端 slot 体系的终版设计——UI 插件如何拼合页面、渲染权威落在哪里、组件 props 如何定型、业务活数据住在哪里。[Web 客户端参考](../../../../docs/subsystems/web-client.zh.md)说明周边的装载链、对象层与服务。 ## 问题 @@ -12,7 +12,7 @@ Status: implemented ## 决策 -全局主面板选择及其 root 生命周期由[全局主面板决策](2026-09-08-global-main-panels.zh.md)定义。 +全局主面板选择及其 root 生命周期由[全局主面板参考](../../../../packages/client/ui-layout/README.zh.md)定义。 一句话:**ui-renderer 只渲染 `'root'`;插件用单独一次 `register` 调用组合 UI——这一次调用同时占用 slot、声明并授权子 slot、声明 store、注入业务面;组件是纯函数,props 分四份额到达,每一份额都从各自唯一的真源自动推导。** diff --git a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.i18n.yaml index 142497a1d9..163ebfc9fa 100644 --- a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md /agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload: - en: 874618fd5ad1d06a - zh: a5e4bfad490fab34 + en: 44c4ef732c381264 + zh: ce375eb5efd6ce2d /agent-note-client-plugin-loading-lazy-factories-cordis-lifecycle-and-hot-reload/problem: en: 46c4b208707e024e zh: b6f86a6374b251da diff --git a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md index b8c23108a7..9cbdbc2961 100644 --- a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md +++ b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-23-client-plugin-loading-model.zh.md) -> Scope: the browser-side plugin loading machinery — how code arrives, how Cordis governs it, and how hot reload rides on that model. This note owns the loading chain; the [client shell layering note](2026-08-15-client-shells-and-dynamic-packages.md) owns package categories, build faces, shared-module requests, and npm dependency declarations, while the [web client architecture note](2026-07-19-gui-web-client-architecture.md) owns slots and the data object layer. +> Scope: the browser-side plugin loading machinery — how code arrives, how Cordis governs it, and how hot reload rides on that model. This note owns the loading chain; the [client shell layering note](2026-08-15-client-shells-and-dynamic-packages.md) owns package categories, build faces, shared-module requests, and npm dependency declarations, while the [web client architecture reference](../../../../docs/subsystems/web-client.md) owns slots and the data object layer. ## Problem diff --git a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.zh.md b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.zh.md index efaea84312..93e59c2c76 100644 --- a/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-23-client-plugin-loading-model.md) | 中文 -> 范围:浏览器侧插件装载机件——代码如何到达、Cordis 如何治理代码,以及热重载如何搭乘这套模型。本 Note 拥有装载链;[client 外壳分层 Note](2026-08-15-client-shells-and-dynamic-packages.zh.md)拥有包分类、构建 face、共享模块请求与 npm 依赖声明,[Web 客户端架构笔记](2026-07-19-gui-web-client-architecture.zh.md)则拥有 slot 与数据对象层。 +> 范围:浏览器侧插件装载机件——代码如何到达、Cordis 如何治理代码,以及热重载如何搭乘这套模型。本 Note 拥有装载链;[client 外壳分层 Note](2026-08-15-client-shells-and-dynamic-packages.zh.md)拥有包分类、构建 face、共享模块请求与 npm 依赖声明,[Web 客户端架构笔记](../../../../docs/subsystems/web-client.zh.md)则拥有 slot 与数据对象层。 ## Problem diff --git a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.i18n.yaml deleted file mode 100644 index 28f986327b..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-24-project-session-directories.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-24-project-session-directories.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-project-session-directories.md -/agent-note-project-grouped-session-directories: - en: c8960daf61fa50fa - zh: d8c7c844180fcaae -/agent-note-project-grouped-session-directories/problem: - en: 7df6078347bd7176 - zh: bb769760d7abc903 -/agent-note-project-grouped-session-directories/decision: - en: cdefd1f4d9d0a55a - zh: 5916d456f3faab2b -/agent-note-project-grouped-session-directories/alternatives-considered: - en: ba4657ba95479806 - zh: 5c53c5c964efa932 -/agent-note-project-grouped-session-directories/consequences: - en: 228c46a599899be1 - zh: 265a233cd41b5c05 diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml index 7e61625e6d..9c16671ac1 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml @@ -15,14 +15,14 @@ en: 16005492be54cb4f zh: db460e0ebf838d54 /agent-note-separate-context-injection-from-turn-execution/extension-and-caller-semantics: - en: e184d8cd9676e24f - zh: 22465b570ae35f59 + en: 9c7a10ac4679f137 + zh: 0f19c11778a02929 /agent-note-separate-context-injection-from-turn-execution/alternatives-considered: en: 0efad389c38dfe95 zh: 13dd73de20226540 /agent-note-separate-context-injection-from-turn-execution/verification: - en: 3f3db450a3fc5848 - zh: b2027393d570339d + en: 56fe8195ed669f6d + zh: 503ca0ce723c69b1 /agent-note-separate-context-injection-from-turn-execution/consequences: en: 71079e4dede5e12f zh: deb46b5a4f151678 diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md index d22d396676..0d864e377a 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.md @@ -42,7 +42,7 @@ Caller-driven injection and current-step context deliberately use different timi Cross-session references use that domain composition: TUI prepares the snapshot, returns it from the idle direct message's pre-step beside that message, or injects it before waking steering during a running turn. The target log contains two simple messages, so later source mutation cannot change replay and transcript consumers do not need a prompt envelope. This supersedes the attachment mechanism in the [cross-session reference decision](../../archived/feature/2026-07-21-cross-session-references.md) while retaining its snapshot and trust-boundary rules. -This decision preserves the caller-owned framing decision from [unwrapped injected content](../simplification/2026-07-20-unwrap-injected-content-envelopes.md) and the one-item turn rule from [one send, one turn](../simplification/2026-07-17-one-send-one-turn.md). The later [standalone log-only event decision](../simplification/2026-07-28-remove-synthetic-log-only-turns.md) applies the same execution-only meaning to plugin-owned records. +This decision preserves the caller-owned framing decision from [unwrapped injected content](../../../../packages/core/session/README.md) and the one-item turn rule from [one send, one turn](../simplification/2026-07-17-one-send-one-turn.md). The later [standalone log-only event decision](../simplification/2026-07-28-remove-synthetic-log-only-turns.md) applies the same execution-only meaning to plugin-owned records. ## Alternatives considered @@ -64,7 +64,7 @@ This decision preserves the caller-owned framing decision from [unwrapped inject - Idle `inject()` immediately appends one durable inbox insertion but no model-visible `user/message`; a later waking delivery may start pre-step processing. - Active-turn injection is claimed at the nearest later pre-step boundary, after complete tool-result batches and before the request that consumes it. - Rejected or failed pre-step drops its claimed batch; input inserted after the claim remains pending. -- Unit, persistence/resume, invariant, and TUI coverage pin event order, claim ownership, and durable replay. +- Unit, persistence/resume, and TUI coverage pin event order, claim ownership, and durable replay. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md index 2c567511b2..2e07eed9dc 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md @@ -42,7 +42,7 @@ enter 分支的 `PreStepDecision.messages` 是拟议步骤的完整批次。wate 跨会话引用采用这种领域组合方式:TUI 先准备快照,然后在 idle 直接消息的 pre-step 中把快照与该消息一同返回,或在 running 轮次中先注入快照再唤醒 steering。目标日志包含两条简单消息,因此来源会话后续变化不会改变回放,transcript 消费方也不需要提示词封套。本决策取代[跨会话引用决策](../../archived/feature/2026-07-21-cross-session-references.md)中的附件机制,但保留其快照与信任边界规则。 -本决策保留[移除注入内容封套](../simplification/2026-07-20-unwrap-injected-content-envelopes.zh.md)确立的由调用方决定内容框架的原则,以及[一次 send、一个轮次](../simplification/2026-07-17-one-send-one-turn.zh.md)确立的单条目轮次规则。后续的[独立纯日志事件决策](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)将同样的「轮次仅表示执行」语义应用于插件所属记录。 +本决策保留[移除注入内容封套](../../../../packages/core/session/README.zh.md)确立的由调用方决定内容框架的原则,以及[一次 send、一个轮次](../simplification/2026-07-17-one-send-one-turn.zh.md)确立的单条目轮次规则。后续的[独立纯日志事件决策](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)将同样的「轮次仅表示执行」语义应用于插件所属记录。 ## 曾考虑的替代方案 @@ -64,7 +64,7 @@ enter 分支的 `PreStepDecision.messages` 是拟议步骤的完整批次。wate - idle 状态下的 `inject()` 会立即追加一条持久 inbox 插入记录,但不会追加模型可见的 `user/message`;后续可唤醒投递可能开始 pre-step 处理。 - 活跃轮次中的注入会在最近的后续 pre-step 边界领取,并位于完整工具结果批次之后、消费它的请求之前。 - pre-step reject 或失败会丢弃其已领取批次;领取后插入的输入继续保持待处理。 -- 单元测试、持久化与 resume 测试、不变量测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。 +- 单元测试、持久化与 resume 测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。 ## 后果 diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml index 8622ce8f91..170036d0fb 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.i18n.yaml @@ -9,8 +9,8 @@ en: fa4d3622fb18dd58 zh: 2b98640594c8344b /agent-note-one-harness-home-resolver/decision: - en: bbab99c7a1aae2fc - zh: 87453b98ca265699 + en: 816e5109718bc4ae + zh: af3f08f07c3cb761 /agent-note-one-harness-home-resolver/alternatives-considered: en: b1c9535540171d47 zh: 7df816d42945db94 diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md index 1191c52bfa..a128d4fe40 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.md @@ -21,7 +21,7 @@ One resolver owns the harness home, in `@deepseek-ai/dsh-home-paths`, single-roo explicit configured path > $DSH_HOME > ~/.dsh ``` -An empty or whitespace-only `$DSH_HOME` is treated as unset; otherwise `resolve('')` would silently place the home at the current working directory. The harness keeps all user data under one root; there is no XDG config/data/cache split. `dshHomePath(...segments)` joins deployment-owned children onto that root, and `dsh-app-boot` exposes it to Loader `!!js` config expressions before mounting entries, so shipped compositions derive `sessions` and `storages` without copying the resolver. `dshHomeDisplay()` names a resolved root symbolically for user-facing paths — `~/.dsh` for the default home, `$DSH_HOME` for any configured home — so the user-global `AGENTS.md` label never leaks an absolute machine path. It replaces agent-instructions's bespoke default-vs-`$DSH_HOME` check. +An empty or whitespace-only `$DSH_HOME` is treated as unset; otherwise `resolve('')` would silently place the home at the current working directory. The harness keeps all user data under one root; there is no XDG config/data/cache split. `dshHomePath(...segments)` joins deployment-owned children onto that root, and `dsh-app-boot` exposes it to Loader `!!js` config expressions before mounting entries, so shipped compositions derive `sessions` and `storages` without copying the resolver. `dshHomeDisplay()` names a resolved root symbolically for user-facing paths — `~/.dsh` for the default home, `$DSH_HOME` for any configured home — so the user-global `AGENTS.md` label never leaks an absolute machine path. It replaces agent-instructions's bespoke default-vs-`$DSH_HOME` check. Home is process policy rather than row configuration: `dsh-agent-instructions` accepts no home override, so a composition cannot point instruction loading at a root that sessions, skills, and storage would not read. `resolveAgentsHome()` and `agentsHomeDisplay()` apply the same precedence, tilde expansion, and symbolic labelling to the shared agent configuration root (`$DSH_AGENTS_HOME` or `~/.agents`), which stays outside the harness home because other agent tools share it ([shared agents root](2026-10-05-shared-agents-root-instructions.md)); `dsh-skill-filesystem` still accepts its own `dshHome` and `agentsHome` fields, which select its own skill roots and resolve through the shared resolver in the instruction-loading change. `dshCachePath(...segments)` derives paths below the resolved home's `cache` directory. An initial `{ dshHome }` option preserves a provider's explicit home override. It resolves paths without creating directories; callers own directory creation. `attachment-local` uses this helper for regenerable request-image variants while retaining durable attachment objects in their versioned storage tree, so clearing the cache cannot remove Session attachments. Existing request-image cache entries are left in place and are not read or copied; a cache miss regenerates the variant from its durable attachment. diff --git a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md index cefa124b7c..3f02908f86 100644 --- a/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-24-single-harness-home-resolver.zh.md @@ -21,7 +21,7 @@ Status: implemented explicit configured path > $DSH_HOME > ~/.dsh ``` -空或仅含空白的 `$DSH_HOME` 被当作未设置处理;否则,`resolve('')` 会悄悄把 home 落在当前工作目录。harness 把所有用户数据都放在同一个根目录下;不存在 XDG 的 config/data/cache 拆分。`dshHomePath(...segments)` 将部署负责的子路径拼接到该根目录下,`dsh-app-boot` 在挂载条目前向 Loader `!!js` 配置表达式暴露它,因此出厂组合无需复制解析器即可派生 `sessions` 和 `storages`。`dshHomeDisplay()` 为面向用户的路径以符号形式命名已解析的根目录——默认 home 显示为 `~/.dsh`,任何已配置的 home 显示为 `$DSH_HOME`——这样用户全局的 `AGENTS.md` 标签就绝不会泄露机器上的绝对路径。它取代了 agent-instructions 中自定义的「默认值 vs `$DSH_HOME`」判断。 +空或仅含空白的 `$DSH_HOME` 被当作未设置处理;否则,`resolve('')` 会悄悄把 home 落在当前工作目录。harness 把所有用户数据都放在同一个根目录下;不存在 XDG 的 config/data/cache 拆分。`dshHomePath(...segments)` 将部署负责的子路径拼接到该根目录下,`dsh-app-boot` 在挂载条目前向 Loader `!!js` 配置表达式暴露它,因此出厂组合无需复制解析器即可派生 `sessions` 和 `storages`。`dshHomeDisplay()` 为面向用户的路径以符号形式命名已解析的根目录——默认 home 显示为 `~/.dsh`,任何已配置的 home 显示为 `$DSH_HOME`——这样用户全局的 `AGENTS.md` 标签就绝不会泄露机器上的绝对路径。它取代了 agent-instructions 中自定义的「默认值 vs `$DSH_HOME`」判断。home 属于进程策略而非配置行:`dsh-agent-instructions` 不接受 home 覆盖,因此组合无法把指令加载指向会话、skill 与存储不会读取的根目录。`resolveAgentsHome()` 与 `agentsHomeDisplay()` 对共享 agent 配置根目录(`$DSH_AGENTS_HOME` 或 `~/.agents`)采用相同的优先级、波浪号展开与符号化标签;该根目录位于 harness home 之外,因为其他 agent 工具会共享它([共享 agents 根目录](2026-10-05-shared-agents-root-instructions.zh.md));`dsh-skill-filesystem` 仍接受自己的 `dshHome` 与 `agentsHome` 字段,用于选择它自己的 skill 根目录,并在指令加载改动中改用共享解析器。 `dshCachePath(...segments)` 在解析出的主目录下的 `cache` 目录中派生路径。首个 `{ dshHome }` 选项保留提供方显式配置的主目录覆盖值。它只解析路径,不创建目录;目录创建由调用方负责。`attachment-local` 将此函数用于可重新生成的请求图片版本,持久附件对象仍保留在其带版本的存储树中,因此清空缓存不会删除 Session 附件。已有请求图片缓存条目保留在原处,不再读取或复制;缓存未命中时从持久附件重新生成请求版本。 diff --git a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml deleted file mode 100644 index 63949a5c87..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-24-web-config-tree-boot-and-transport-layering.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md -/agent-note-dsh-web-config-tree-boot-and-the-web-transport-layering: - en: 2d06ffcaaeb373e7 - zh: 01822cfd7339167f -/agent-note-dsh-web-config-tree-boot-and-the-web-transport-layering/problem: - en: e5d603f77bff9dd7 - zh: c9e9a64f0a4db3c9 -/agent-note-dsh-web-config-tree-boot-and-the-web-transport-layering/decision: - en: 866ebaaf4fe1e0d1 - zh: 8075a708c9042941 -/agent-note-dsh-web-config-tree-boot-and-the-web-transport-layering/consequences: - en: 47f8a6beba1f320b - zh: 5f444699dc52cabf -/agent-note-dsh-web-config-tree-boot-and-the-web-transport-layering/alternatives-considered: - en: bf6fbf972eab0700 - zh: 0f3449e5e6ace7e2 diff --git a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml deleted file mode 100644 index cc78be86f5..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-25-web-client-session-scope-and-provide-channel.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.md -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide: - en: 64d8701a9f2574bd - zh: 938991d424943547 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/problem: - en: 1738c071cb9824bc - zh: 7af7770739d5430a -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision: - en: a397a3f2e0bd7c21 - zh: a179a646c148ecc1 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/the-parity-model-client-and-host-share-one-root-state-axis: - en: e117f541a2f55c07 - zh: c6a5cf9c9d6b7692 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/agent-scope-the-actx-is-the-sole-session-carrier-in-the-client-side-cordis-world: - en: 6e6488b3eab757c2 - zh: 4ee3d1cc415234df -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/scope-lifecycle-anchored-to-explicit-references: - en: e2ed0052678a2b9b - zh: e0fe1b87dc3766af -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/the-blank-bit-the-empty-session-s-visible-projection-conversion-and-reuse: - en: ee88908c085b80c5 - zh: 5ddb65bbac371f02 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/connectworkspace-the-sole-entry-point-of-new-session: - en: 833df42c5a87afbd - zh: 82d789a99fd2b968 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/per-session-provisioning-the-uisession-provide-standard-kit-channel: - en: 3c739c21ea490f09 - zh: 5508153f313a86d9 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/input-delivery: - en: 2073e96f851d1e6e - zh: f0433bf5dfd0a23d -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/decision/host-wire-smalls: - en: 5d1339b5a243812a - zh: a4be4d3c30056f42 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/alternatives-considered: - en: 2a69d71f4bdf0f76 - zh: dd768c273728d354 -/agent-note-web-client-agent-scope-parity-model-and-the-provisioning-channel-agents-scope-blank-reuse-provide/consequences: - en: 6750378aca400a75 - zh: 6eba20215ff255a1 diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml index babbdf4b6f..662d63f44c 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.i18n.yaml @@ -9,8 +9,8 @@ en: 959a378a04f28e89 zh: ff496169735b4d7b /agent-note-portable-consumers-over-filesystem-and-subprocess-execution-worlds/decision: - en: 23eca65b89ae572a - zh: 630d844a7c6438df + en: 69e97b0a63117d33 + zh: 3c4188c40166264b /agent-note-portable-consumers-over-filesystem-and-subprocess-execution-worlds/remote-provider-ownership: en: bde2e1fa8f3aeee0 zh: 42a13bb4cf540d6d diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md index 5a687a552f..9fc7c4c54f 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.md @@ -20,7 +20,7 @@ Foreground Bash and PowerShell calls use one executor deadline for asynchronous The filesystem interface owns the path facts that another capability needs without exposing its opaque target identity: a canonical process path, canonical `file:` URI, and containment. Existing whole and streaming text operations remain filesystem-owned; protocol consumers enforce their own retention limits while consuming the stream. -The subprocess interface owns executable lookup and process primitives: ordinary raw or collected process spawning and `spawnTerminal()`. An ordinary handle keeps target identity private: `.done` reports the direct target, while `terminate()` and `waitForExit()` control and observe the same provider-managed range. The [native-containment decision](2026-08-28-subprocess-native-containment.md) owns local Linux scopes, Windows Jobs, and their disclosed fallbacks. The terminal operation is one deep primitive whose handle owns text I/O, foreground groups, signalling, and one awaited TERM-to-KILL operation that settles in-flight handle calls and reaches quiescence for every member of its provider-owned range; an observational fallback limits that range to identities it can still observe. Its signal cancels allocation only; the published handle owns its lifetime. Prompt detection, idle inference, scrollback, sandbox policy, and owner lifecycle remain in the PTY consumer. +The subprocess interface owns executable lookup and process primitives: ordinary raw or collected process spawning and `spawnTerminal()`. An ordinary handle keeps target identity private: `.done` reports the direct target, while `terminate()` and `waitForExit()` control and observe the same provider-managed range. The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns local Linux scopes, Windows Jobs, and their disclosed fallbacks. The terminal operation is one deep primitive whose handle owns text I/O, foreground groups, signalling, and one awaited TERM-to-KILL operation that settles in-flight handle calls and reaches quiescence for every member of its provider-owned range; an observational fallback limits that range to identities it can still observe. Its signal cancels allocation only; the published handle owns its lifetime. Prompt detection, idle inference, scrollback, sandbox policy, and owner lifecycle remain in the PTY consumer. Generic consumers use that execution world: diff --git a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md index 6bc0e20bd2..78a75b61f4 100644 --- a/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-28-portable-execution-world-consumers.zh.md @@ -20,7 +20,7 @@ Status: implemented 文件系统接口负责其他能力需要的路径事实,同时不公开其不透明目标身份:规范化进程路径、规范化 `file:` URI 和包含关系。现有完整文本与流式文本操作仍归文件系统负责;协议消费方在消费流时执行各自的保留上限。 -进程管理接口负责可执行文件查找与进程原语:以原始或收集模式 spawn 普通进程,以及 `spawnTerminal()`。普通句柄把 target identity 保持为私有事实:`.done` 报告 direct target,`terminate()` 与 `waitForExit()` 则控制并观察同一个由提供方管理的范围。[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责本地 Linux scope、Windows Job 及其已声明的 fallback。终端操作是一项深层原语,其句柄负责文本 I/O、前台进程组、信号发送,以及一项须等待的 TERM→KILL 操作;该操作会结算所有在途句柄调用,并使提供方拥有的范围中每个成员完全停稳;观察型 fallback 只能把该范围限制为它仍可观察到的 identity。其信号只取消分配;句柄一经发布,便负责自身生命周期。提示符检测、空闲推断、scrollback、沙箱策略和所有者生命周期仍由 PTY 消费方负责。 +进程管理接口负责可执行文件查找与进程原语:以原始或收集模式 spawn 普通进程,以及 `spawnTerminal()`。普通句柄把 target identity 保持为私有事实:`.done` 报告 direct target,`terminate()` 与 `waitForExit()` 则控制并观察同一个由提供方管理的范围。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责本地 Linux scope、Windows Job 及其已声明的 fallback。终端操作是一项深层原语,其句柄负责文本 I/O、前台进程组、信号发送,以及一项须等待的 TERM→KILL 操作;该操作会结算所有在途句柄调用,并使提供方拥有的范围中每个成员完全停稳;观察型 fallback 只能把该范围限制为它仍可观察到的 identity。其信号只取消分配;句柄一经发布,便负责自身生命周期。提示符检测、空闲推断、scrollback、沙箱策略和所有者生命周期仍由 PTY 消费方负责。 通用消费方使用该执行世界: diff --git a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.i18n.yaml index 22d12f0aab..07c4b73971 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.i18n.yaml @@ -9,8 +9,8 @@ en: 081098cb08ca80aa zh: f829b6630a68fc3c /agent-note-projected-token-usage-and-context-occupancy/decision: - en: 231a6f4a4c00dca6 - zh: e90c76e425826ca0 + en: a2ee4260a00cd576 + zh: 15b5f187830539bd /agent-note-projected-token-usage-and-context-occupancy/context-occupancy-is-approximate-and-that-is-the-decision: en: 9df83c0a48f462d2 zh: 94c2ad19af4b13fc diff --git a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.md b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.md index 10056ac8ef..f60eb0c06a 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.md +++ b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.md @@ -26,7 +26,7 @@ Capacity deliberately stays out of `EpochHeader`. That type is the reconstructio Both units ride the standard projection lifecycle: history tail baselines, `session/projection` live frames, higher-seq-wins client storage, JSON checkpoints, cache recovery, and unit unload. There is no token-specific history field, mux frame, projector, revision counter, or client fence. -The Web [`StatsPills`](../feature/2026-09-07-composer-session-stats-pills.md) reads both through the standard `useProjection` seat. Window nodes still supply turn and step counts plus LLM and tool wall times as the no-projection fallback — those answer "what is on screen" and are correctly window-scoped. The durable usage pill remains when compaction leaves no visible assistant step. Cache writes count in billed input and in the cache-hit denominator. A deployment without token-meter drops the usage pill; context occupancy lives on the composer's ContextMeter ring. Exact token figures show in the usage pill's click-open dialog rather than a hover tooltip. +The Web [`StatsPills`](../../../../packages/client/ui-chat/src/client/chat/StatsPills.tsx) reads `tokenUsage` through the standard `useProjection` seat. Window nodes still supply turn and step counts plus LLM and tool wall times as the no-projection fallback — those answer "what is on screen" and are correctly window-scoped. The durable usage pill remains when compaction leaves no visible assistant step. Cache writes count in billed input and in the cache-hit denominator. A deployment without token-meter drops the usage pill; context occupancy lives on the composer's ContextMeter ring. Exact token figures show in the usage pill's click-open dialog rather than a hover tooltip. ## Context occupancy is approximate, and that is the decision diff --git a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.zh.md b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.zh.md index 0e4cd68791..8995bbb70d 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-projected-token-usage-and-request-context.zh.md @@ -26,7 +26,7 @@ token-meter 还拥有在持久事件上运行的共享纯 attempt/Turn fold。 两个单元都沿用标准投影生命周期:历史尾页基线、`session/projection` 实时帧、seq 高者胜的客户端存储、JSON 检查点、缓存恢复和单元卸载。系统没有任何 token 专用的历史字段、mux 帧、投影器、修订计数器或客户端栅栏。 -Web [`StatsPills`](../feature/2026-09-07-composer-session-stats-pills.zh.md) 通过标准 `useProjection` 席位读取两者。窗口内节点仍作为无投影回退提供轮次和步骤计数,以及 LLM(大语言模型)与工具的墙钟时间:它们回答的是「屏幕上有什么」,按窗口作用域正是正确的。压缩使可见 assistant 步骤归零后,持久用量 pill 仍会保留。缓存写入会计入计费输入和缓存命中率分母。未部署 token-meter 时会去掉用量 pill;上下文占用率由输入框旁的 ContextMeter 圆环承载。精确 token 数字显示在用量 pill 点击展开的弹层里,而非悬停提示。 +Web [`StatsPills`](../../../../packages/client/ui-chat/src/client/chat/StatsPills.tsx) 通过标准 `useProjection` 席位读取 `tokenUsage`。窗口内节点仍作为无投影回退提供轮次和步骤计数,以及 LLM(大语言模型)与工具的墙钟时间:它们回答的是「屏幕上有什么」,按窗口作用域正是正确的。压缩使可见 assistant 步骤归零后,持久用量 pill 仍会保留。缓存写入会计入计费输入和缓存命中率分母。未部署 token-meter 时会去掉用量 pill;上下文占用率由输入框旁的 ContextMeter 圆环承载。精确 token 数字显示在用量 pill 点击展开的弹层里,而非悬停提示。 ## 上下文占用率是近似值,而这正是决策本身 diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml index ddc9c2bc46..8e9357b6e9 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md /agent-note-terminal-llm-stream-failures: - en: 6195cb1d9b4dd423 - zh: 0c93d3a39e2bc1f4 + en: 2bb7cfe655fb5fe9 + zh: 48aefa99e0787bdd /agent-note-terminal-llm-stream-failures/problem: en: 41363fca57ae20e6 zh: 1d05eeeecaafa7f3 /agent-note-terminal-llm-stream-failures/decision: - en: c3bfde5f736f8b27 - zh: f51ebb4089df6406 + en: dd1dd01dda70bfb7 + zh: 8715075a5be2f7b7 /agent-note-terminal-llm-stream-failures/alternatives-considered: en: 46153a92b2135aae zh: 0b8fdaf1f7edeb77 diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md index c5d160c5df..32237b5b26 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-29-terminal-llm-stream-failures.zh.md) -This note supersedes only the thrown-error identity and call-local sidecar mechanism in [bounded LLM request recovery](2026-06-21-bounded-llm-request-recovery.md) and [after-call context-overflow recovery](2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md). Those notes continue to own structured failure facts, retry policy, durable attempts, and compaction recovery. +This note supersedes the thrown-error identity and call-local sidecar mechanism in [bounded LLM request recovery](2026-06-21-bounded-llm-request-recovery.md), which retains structured failure facts, retry policy, and durable attempts. The [compaction reference](../../../../packages/compaction/compaction-basic/README.md) defines current overflow recovery. ## Problem @@ -16,7 +16,7 @@ Retry policy had the same indirect ownership. It was discovered through the stre `LlmRuntime` is the normalization boundary for one adapter attempt. It catches only final-adapter selection, synchronous dispatch, iterator construction, and `next()` failures, converts the thrown value to immutable `LlmFailure`, and emits one terminal `finish`. Caller cancellation or an `ABORTED` failure selects the aborted reason; every other adapter failure selects error. An adapter may also emit either terminal reason directly. -The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open, so the stream invariant permits open blocks only for terminal error or aborted finishes. No assistant message or tool call is assembled from that incomplete output. +The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open before a terminal error or aborted finish. No assistant message or tool call is assembled from that incomplete output. `PreparedLlmCall` exposes the immutable retry policy captured with its config and registration. One-shot reuse and config mismatch remain synchronous `INVALID_PREPARED_CALL` misuse errors. A route served entirely by `llm/stream` middleware has no prepared registration and therefore no serving policy. diff --git a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md index 297898d8f9..17d7aa17e5 100644 --- a/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-29-terminal-llm-stream-failures.md) | 中文 -本说明仅取代[有界 LLM(大语言模型)请求恢复](2026-06-21-bounded-llm-request-recovery.zh.md)与[调用后上下文溢出恢复](2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md)中关于抛出错误身份和调用局部 sidecar 的机制。上述说明继续规定结构化失败事实、重试策略、持久尝试与压缩(compaction)恢复。 +本说明取代[有界 LLM(大语言模型)请求恢复](2026-06-21-bounded-llm-request-recovery.zh.md)中的抛出错误身份和调用局部 sidecar 机制;该说明仍负责结构化失败事实、重试策略与持久尝试。[压缩(compaction)参考](../../../../packages/compaction/compaction-basic/README.zh.md)定义当前溢出恢复。 ## Problem @@ -16,7 +16,7 @@ Status: implemented `LlmRuntime` 是一次适配器尝试的规范化边界。它只捕获最终适配器选择、同步分发、iterator 构造与 `next()` 失败,将抛出值转换为不可变 `LlmFailure`,并发出一个终止 `finish`。调用方取消或 `ABORTED` 失败选择 aborted 结束原因;其他适配器失败选择 error 结束原因。适配器也可以直接发出任一终止原因。 -适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能留下未关闭块,因此流 invariant 只允许在终止 finish 的结束原因为 error 或 aborted 时存在未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。 +适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能在结束原因为 error 或 aborted 的终止 finish 之前留下未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。 `PreparedLlmCall` 公开随其配置和注册捕获的不可变重试策略。一次性句柄复用与配置不匹配仍是同步的 `INVALID_PREPARED_CALL` 误用错误。完全由 `llm/stream` middleware 提供服务的路由没有准备完成的注册,因此也没有服务策略。 diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml index a4c259b654..bbbf53a526 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml @@ -9,8 +9,8 @@ en: 6228c81e4965b688 zh: 04b702be0cfbf195 /agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/decision: - en: d2aa88cf1e9ec97b - zh: da9422723fc9ac93 + en: 052787311a4e6edc + zh: 963eed632ea198de /agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/alternatives-considered: en: 90c5883b247f7301 zh: 6d2a660d8653f805 diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md index 0e65bb1093..03ea573307 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.md @@ -22,7 +22,7 @@ Two request-path defects sat beside them. DeepSeek resolved connection and crede **Route replacement is a registry operation, not a caller sequence.** `registerAdapter` returns a handle carrying `replace(providers)`: the candidate set is validated in full first (conflicts, names, provider metadata), then swapped in one synchronous section. A refused replacement leaves the previous routes registered and serving, and the caller's facts cache only advances after the registry actually holds the new set, so reverting to a working configuration re-applies. pi-ai's registration facts are sorted by provider, so a settings document that merely reorders its keys is no longer a route change. -**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome, and `INVARIANT`-coded failures rethrow after every listener ran — the same shape the settings seam uses for `settings/updated`. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown. +**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md index cbc66efa26..9f822b8bee 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.zh.md @@ -26,7 +26,7 @@ Status: implemented **路由替换是注册表的操作,不是调用方的一串步骤。**`registerAdapter` 返回一个携带 `replace(providers)` 的句柄:候选集合先被完整校验(冲突、名称、提供方元数据),再在一个同步区段内完成替换。被拒绝的替换会让先前的路由保持注册并继续服务,而调用方的事实缓存只有在注册表确实持有新集合之后才会推进,因此改回可用配置时会重新生效。pi-ai 的注册事实按提供方排序,因此仅仅调换键顺序的设置文档不再算作路由变更。 -**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果,而带 `INVARIANT` 代码的失败会在每个监听器都运行完之后重抛——与 settings seam 处理 `settings/updated` 的形状相同。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。 +**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml deleted file mode 100644 index 18ab603d7b..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-30-session-end-seed-log-boundary.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-30-session-end-seed-log-boundary.md -/agent-note-the-end-seed-log-boundary: - en: 0f53ca9c0166d7ad - zh: e16b2376c2afb1bc -/agent-note-the-end-seed-log-boundary/problem: - en: e7192f854b24129c - zh: 74a38ee95988e338 -/agent-note-the-end-seed-log-boundary/decision: - en: 519aa4faadd01960 - zh: 1430cc9bd81e7df9 -/agent-note-the-end-seed-log-boundary/persistence-needs-no-changes: - en: 5df69ed0156e50c8 - zh: 9ad6caad6d33b183 -/agent-note-the-end-seed-log-boundary/scope-of-the-guarantee: - en: 31c23e4e25640fb1 - zh: d207c2a8aa7d936c -/agent-note-the-end-seed-log-boundary/alternatives-considered: - en: cd1ed7dfa77722ae - zh: 4ad834f3cf3b5443 -/agent-note-the-end-seed-log-boundary/consequences: - en: 69eb86480b191135 - zh: 51ef88fb78701b1f diff --git a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml deleted file mode 100644 index 7151c1e20c..0000000000 --- a/.agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-31-claimed-pre-step-inbox-lifecycle.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md -/agent-note-claim-inbox-input-before-one-pre-step-decision: - en: 4ee4c56f27c9bf27 - zh: 72321b4c735e106f -/agent-note-claim-inbox-input-before-one-pre-step-decision/problem: - en: f2a920b8770c2f99 - zh: 1c111f175131714e -/agent-note-claim-inbox-input-before-one-pre-step-decision/decision: - en: 2e58afbd8ffbce60 - zh: 40e0c0e3d24fc7ad -/agent-note-claim-inbox-input-before-one-pre-step-decision/alternatives-considered: - en: 03496db1c0547f71 - zh: c0932cfcb5d5d93b -/agent-note-claim-inbox-input-before-one-pre-step-decision/verification: - en: 007a79d6db18cbab - zh: 611306fcb5edfda0 -/agent-note-claim-inbox-input-before-one-pre-step-decision/consequences: - en: ba68c3ce7ed64bcc - zh: 8fe29186526b258a diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml index 18c1983fdb..f7b30f24e6 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml @@ -15,5 +15,5 @@ en: 7d79dfa834d9168f zh: 0b3e82a7d0ef71b9 /agent-note-goal-owned-durable-events/consequences: - en: 0e4513ea5e3e1e2e - zh: 69d81ba52ee2f4b6 + en: b2a2f902f126f318 + zh: 8a38fe8acb0a2241 diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md index 0d3e632ba2..ad02b4d989 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.md @@ -28,6 +28,6 @@ The domain does not automatically project each mutation into model input. Goal t ## Consequences -Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold and invariant companion reject. +Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold rejects. Focused goal, goal-round-driver, command, TUI, and Client RemoteMock tests pin durable replay, positive-round accounting, inbox independence, projection updates, and restored-session behavior. The keyless process test inspects the persisted `goal/change` event and verifies that creation alone starts no continuation round. diff --git a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md index fd29a6f36e..a9730425dc 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.zh.md @@ -28,6 +28,6 @@ Goal 领域需要持久状态,但不需要拥有待处理的模型输入。继 ## 后果 -Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠与 invariant 配套模块会拒绝这些变更。 +Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠会拒绝这些变更。 聚焦的 goal、goal-round-driver、command、TUI 与 Client RemoteMock 测试固定持久回放、正数 Round 计数、inbox 独立性、投影更新和恢复会话行为。无密钥进程测试检查持久的 `goal/change` 事件,并验证仅创建 goal 不会启动继续执行 Round。 diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml index 85084f80cb..b790e7c558 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.i18n.yaml @@ -3,23 +3,23 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md /agent-note-the-ptc-runtime-python-fd-3-frame-protocol: - en: fc43bcfc72cc4476 - zh: c81279cb0da1481c + en: 0130850e16ce6f60 + zh: 7b70b03b5019277f /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/problem: en: 2c36946bbff637b7 zh: f290de0866ba7caa /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/decision: - en: 1f93159850fc7f48 - zh: dcc2a4788e3254f7 + en: f0d0d5ba5a9066ad + zh: fbdab3731c33eb26 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/wire-contract: - en: 913f9465320dc191 - zh: f8e0338645d6c204 + en: 1225a32af91fcf84 + zh: 1dab4ecbd9ff7e34 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/mirror-alignment: en: 26128b65f2763920 zh: df24359faebcbfed /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/alternatives-considered: - en: cc42636bd7793305 - zh: e196f980daec3037 + en: 2f52b91980d013ed + zh: 12dc9a24c03c3c68 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/consequences: en: 16df6efd46d71e85 zh: 4062475bec04ab5f diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md index 097bcfd55f..b22ea4cddf 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.md @@ -2,7 +2,7 @@ Status: implemented -The CPython PTC runtime lives at `packages/experimental/ptc-runtime-python` and publishes as `@deepseek-ai/dsh-experimental-ptc-runtime-python`; the [publication decision](../process/2026-09-12-publish-all-experimental-packages.md) preserves its experimental status. +The CPython PTC runtime lives at `packages/experimental/ptc-runtime-python` and publishes as `@deepseek-ai/dsh-experimental-ptc-runtime-python`; the [publication policy](../../../../packages/experimental/README.md) preserves its experimental status. English | [中文](2026-07-31-ptc-runtime-python-fd3-protocol.zh.md) @@ -22,11 +22,11 @@ The experimental package contains both the protocol and runtime implementation: `py/protocol.py` mirrors the message shapes as `TypedDict`s and re-declares the two surfaces both sides EXECUTE against — `PROTOCOL_FD = 3` and `log_truncation_marker` — with byte-identical text. -The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages///package.json` unconditionally, while the coverage and invariant-topology checks exercise the package as soon as its directory exists. +The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages///package.json` unconditionally, while the coverage check exercises the package as soon as its directory exists. ## Wire contract -Frames are JSON-lines on fd 3, one object per line, leaving stdout/stderr free for the program's own output. Child → host: `boot-ack`, `call`, `log`, `done`. Host → child: `boot` (first frame), `run` (after `boot-ack`), and one `reply` per `call`. The `log` frame's `truncated` flag marks the frame that IS the child ledger's own truncation marker, so the host stops capturing at the same point the child did instead of inferring it from its own budget. The `log` frame's `open` flag marks an unterminated line committed by an explicit flush: the host holds it and appends the next frame to the same entry, so an explicit flush followed by more text reads back as one line rather than a fake newline. The one exception is truncation: when a later over-budget frame trips the ledger, the already-billed prefix is committed as its own entry and the truncation marker follows it (marker last, no re-charge). The merged entry's wire cost is billed exactly once, split incrementally across its fragments on both sides (O(k) for k fragments, never a re-walk of the whole hold): the FIRST fragment pays the full JSON-string cost plus the separator, each continuation and the closing frame pay only their content; the host's exact-cost caps are `logBudget - 1` for a first fragment (the ledger's reserved byte, matching `admit`) and `logBudget + 2` for a continuation or closing frame (billed without the two quotes), and `jsonStringCostUpTo` returns `undefined` below a 2-byte cap; the child keys its split billing off `_open_started` alone, so a closing frame bills as the merged tail. `done.error.kind` is one of `exception`, `invalid-output`, `output-limit`; wall/CPU budgets, aborts, and substrate death are observed host-side, not carried as frames. +Frames are JSON-lines on fd 3, one object per line, leaving stdout/stderr free for the program's own output. Child → host: `boot-ack`, `call`, `log`, `done`. Host → child: `boot` (first frame), `run` (after `boot-ack`), and one `reply` per `call`. The `log` frame's `truncated` flag marks the frame that IS the child ledger's own truncation marker, so the host stops capturing at the same point the child did instead of inferring it from its own budget. The `log` frame's `open` flag marks an unterminated line committed by an explicit flush: the host holds it and appends the next frame to the same entry, so an explicit flush followed by more text reads back as one line rather than a fake newline. The one exception is truncation: when a later over-budget frame trips the ledger, the already-billed prefix is committed as its own entry and the truncation marker follows it (marker last, no re-charge). The merged entry's wire cost is billed exactly once, split incrementally across its fragments on both sides (O(k) for k fragments, never a re-walk of the whole hold): the FIRST fragment pays the full JSON-string cost plus the separator, each continuation and the closing frame pay only their content; the host's exact-cost caps are `logBudget - 1` for a first fragment (the ledger's reserved byte, matching `admit`) and `logBudget + 2` for a continuation or closing frame (billed without the two quotes), and `jsonStringCostUpTo` returns `undefined` below a 2-byte cap; the child keys its split billing off `_open_started` alone, so a closing frame bills as the merged tail. `done.error.kind` is one of `exception`, `invalid-output`, `output-limit`, or `timeout` from the CPU check after model code returns; wall deadlines, aborts, and substrate death are observed host-side. The post-return CPU check attempts a diagnostic on fd 3 before mandatory signal termination because a sandbox launcher can convert that signal into an ambiguous exit code. Ordinary soft-limit expiry retains default `SIGXCPU` termination without a Python handler, so native calls cannot postpone enforcement until Python resumes. These frames remain untrusted outcome reports; kernel resource limits enforce the CPU ceiling independently. ## Mirror alignment @@ -36,7 +36,7 @@ Frames are JSON-lines on fd 3, one object per line, leaving stdout/stderr free f **Require a future Python JSON codec (`_encode_json_plain` / `_decode_json_plain`) to live in `py/protocol.py` for cross-side symmetry with `protocol.ts`.** Rejected. The repository's "prefer symmetry for parallel values" rule points at genuinely parallel values; these are not. The host-side codec in `protocol.ts` validates hostile input and is self-contained. A child-side codec would produce trusted output and belong with bootstrap-owned emission and cost accounting; forcing only its entry points into `protocol.py` would couple the vocabulary mirror to runtime internals or create an import cycle. `protocol.py` remains a pure wire-vocabulary mirror; the codec (`_encode_json_plain` / `_decode_json_plain`) lives in `bootstrap.py` with the runtime it serves. -**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint, coverage, and invariant-topology checks require every directory under `packages//` to be a buildable package, and the protocol has independent tests and a public wire vocabulary. +**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint and coverage checks require every directory under `packages//` to be a buildable package, and the protocol has independent tests and a public wire vocabulary. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md index ac656e421b..a8e7ea4ef4 100644 --- a/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-31-ptc-runtime-python-fd3-protocol.zh.md @@ -2,7 +2,7 @@ Status: implemented -CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@deepseek-ai/dsh-experimental-ptc-runtime-python` 名称发布;[发布决策](../process/2026-09-12-publish-all-experimental-packages.zh.md)保留其实验性状态。 +CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@deepseek-ai/dsh-experimental-ptc-runtime-python` 名称发布;[发布策略](../../../../packages/experimental/README.zh.md)保留其实验性状态。 [English](2026-07-31-ptc-runtime-python-fd3-protocol.md) | 中文 @@ -22,11 +22,11 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d `py/protocol.py` 用 `TypedDict` 镜像消息形状,并重新声明两侧都会 EXECUTE 的两个面——`PROTOCOL_FD = 3` 与 `log_truncation_marker`——文本逐字节一致。 -该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages///package.json`,coverage 与 invariant-topology 检查则会在包目录存在时立即覆盖该包。 +该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages///package.json`,coverage 检查则会在包目录存在时立即覆盖该包。 ## Wire contract -帧是 fd 3 上的 JSON-lines,每行一个对象,让 stdout/stderr 空出给程序自己的输出。Child → host:`boot-ack`、`call`、`log`、`done`。Host → child:`boot`(首帧)、`run`(在 `boot-ack` 之后)、以及每个 `call` 对应一个 `reply`。`log` 帧的 `truncated` 标志标记那个本身就是子进程 ledger 截断标记的帧,使 host 在与子进程相同的点停止捕获,而不是从自己的预算去推断。`log` 帧的 `open` 标志标记由显式 flush 提交的未结束行:宿主持有它并把下一个帧追加到同一条目,因此显式 flush 后接更多文本读回为一行而不是假换行。唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。合并条目的线上成本恰好计费一次,在两侧按片段增量分摊(k 个片段 O(k),绝不对整个持有重走):首片段付完整 JSON 字符串成本加分隔符,每个续接与闭合帧只付内容;宿主精确成本 cap 是首片段 `logBudget - 1`(账本预留字节,与 `admit` 一致)、续接或闭合帧 `logBudget + 2`(不含两个引号计费),且 `jsonStringCostUpTo` 在低于 2 字节 cap 时返回 `undefined`;子进程按 `_open_started` 单独键控拆分计费,因此闭合帧按合并尾部计费。`done.error.kind` 是 `exception`、`invalid-output`、`output-limit` 之一;wall/CPU 预算、abort、substrate 死亡都在 host 侧观测,不作为帧携带。 +帧是 fd 3 上的 JSON-lines,每行一个对象,让 stdout/stderr 空出给程序自己的输出。Child → host:`boot-ack`、`call`、`log`、`done`。Host → child:`boot`(首帧)、`run`(在 `boot-ack` 之后)、以及每个 `call` 对应一个 `reply`。`log` 帧的 `truncated` 标志标记那个本身就是子进程 ledger 截断标记的帧,使 host 在与子进程相同的点停止捕获,而不是从自己的预算去推断。`log` 帧的 `open` 标志标记由显式 flush 提交的未结束行:宿主持有它并把下一个帧追加到同一条目,因此显式 flush 后接更多文本读回为一行而不是假换行。唯一例外是截断:当后续超预算帧触发账本时,已计费的前缀作为独立条目先提交,截断 marker 跟在后面(marker 保持末位,无重复计费)。合并条目的线上成本恰好计费一次,在两侧按片段增量分摊(k 个片段 O(k),绝不对整个持有重走):首片段付完整 JSON 字符串成本加分隔符,每个续接与闭合帧只付内容;宿主精确成本 cap 是首片段 `logBudget - 1`(账本预留字节,与 `admit` 一致)、续接或闭合帧 `logBudget + 2`(不含两个引号计费),且 `jsonStringCostUpTo` 在低于 2 字节 cap 时返回 `undefined`;子进程按 `_open_started` 单独键控拆分计费,因此闭合帧按合并尾部计费。`done.error.kind` 是 `exception`、`invalid-output`、`output-limit` 或模型代码返回后 CPU 检查产生的 `timeout` 之一;wall 截止、abort 和 substrate 死亡在 host 侧观测。返回后的 CPU 检查会在强制信号终止前尝试通过 fd 3 报告诊断,因为沙箱 launcher 可能把该信号转换为有歧义的退出码。普通软限制到期保留默认的 `SIGXCPU` 终止行为,不安装 Python handler,因此原生调用不能把强制执行延后到 Python 恢复运行。这些帧仍是不可信的结果报告;内核资源限制独立强制执行 CPU 上限。 ## Mirror alignment @@ -36,7 +36,7 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d **要求未来的 Python JSON codec(`_encode_json_plain` / `_decode_json_plain`)放进 `py/protocol.py`,以便与 `protocol.ts` 跨侧对称。**拒绝。仓库的 “prefer symmetry for parallel values” 规则指向真正平行的值;这两者不是。`protocol.ts` 中的 host 侧 codec 校验敌意输入且自包含。Child 侧 codec 会产出受信任输出,应与 bootstrap 拥有的发出逻辑和成本核算放在一起;只把入口强塞进 `protocol.py` 会让 vocabulary 镜像耦合 runtime 内部实现,或制造 import 环。`protocol.py` 保持纯 wire-vocabulary 镜像;codec(`_encode_json_plain`/`_decode_json_plain`)与它所服务的 runtime 一起位于 `bootstrap.py`。 -**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint、coverage 与 invariant-topology 检查要求 `packages//` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。 +**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint 与 coverage 检查要求 `packages//` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。 ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml index b86fbe45fb..7afdf2c22c 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml @@ -24,8 +24,8 @@ en: 9f7448898cf5cadc zh: 419573c589ed6f7d /agent-note-typert-gateway-targeted-method-calls/invocationdescriptor: - en: aad682229b400d49 - zh: 60c2857fca1f4b2b + en: 282b4528f458d929 + zh: e97aba9fd26bd554 /agent-note-typert-gateway-targeted-method-calls/typert-runtime-registry: en: 14ad652e20e699ca zh: 9272bc04a35e3c6a diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md index 2a12aaf78f..8e91598f7d 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md @@ -147,7 +147,7 @@ The strict generator writes `scope` only when a direct method has exactly one lo Parameter order comes from the method signature. HTTP fields come from parameter names or lookup declarations. A cancellation descriptor reserves only the final `signal` position and keeps it outside named `args`; Connection or a direct Gateway caller supplies the actual signal. The Gateway does not infer optional fields, Context types, lookup types, or missing arguments from request contents, and it does not synthesize business defaults. -A LIB codec contains a success-cached Zod schema factory and a canonical `typeSymbol` consisting of "package + public subpath + export name." The Host Gateway invokes parameter and identity factories when it first decodes strict input. The Client contribution retains the same codec metadata for strict input checks at mount but does not materialize invocation schemas; [Host-only Remote input validation](../simplification/2026-09-15-host-only-remote-input-validation.md) owns this placement. An SRC codec is marked only as `src-json`. +A LIB codec contains a success-cached Zod schema factory and a canonical `typeSymbol` consisting of "package + public subpath + export name." The Host Gateway invokes parameter and identity factories when it first decodes strict input. The Client contribution retains the same codec metadata for strict input checks at mount but does not materialize invocation schemas; [Host-only Remote input validation](../../../../packages/api/gateway/README.md) owns this placement. An SRC codec is marked only as `src-json`. Descriptors exist only in the local registry on each side. The wire carries only the `/api` channel, endpoint, and `{ args }` payload. The Client uses its descriptor to map positional arguments and Context identity into named fields. The Host uses its descriptor to validate those fields, resolve the receiver, and invoke the method. diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md index bf4a7b0179..fd6cd2bcfc 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md @@ -147,7 +147,7 @@ InvocationDescriptor { 参数顺序来自方法签名,HTTP 字段来自参数名或 lookup 声明。取消 descriptor 只保留最后一个 `signal` 位置,并使其不进入具名 `args`;实际 signal 由 Connection 或直接调用 Gateway 的调用方提供。Gateway 不根据请求内容推断可选字段、Context 类型、lookup 类型或缺失参数,也不会合成业务默认值。 -LIB codec 带有只缓存成功结果的 Zod schema factory 和「package + 公共 subpath + export name」的规范 `typeSymbol`。Host Gateway 首次解码严格输入时调用参数与身份 factory。Client contribution 保留同一 codec 元数据,以在挂载时检查严格输入,但不实例化调用 schema;[仅在 Host 校验 Remote 输入](../simplification/2026-09-15-host-only-remote-input-validation.zh.md)规定了这个位置。SRC codec 只标记 `src-json`。 +LIB codec 带有只缓存成功结果的 Zod schema factory 和「package + 公共 subpath + export name」的规范 `typeSymbol`。Host Gateway 首次解码严格输入时调用参数与身份 factory。Client contribution 保留同一 codec 元数据,以在挂载时检查严格输入,但不实例化调用 schema;[仅在 Host 校验 Remote 输入](../../../../packages/api/gateway/README.zh.md)规定了这个位置。SRC codec 只标记 `src-json`。 descriptor 只存在于两端本地 registry。wire 上只有 `/api` channel、endpoint 和 `{ args }` payload。Client 用自己的 descriptor 把位置参数和 Context identity 映射为具名字段;Host 用自己的 descriptor 校验这些字段、解析 receiver 并调用方法。 diff --git a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml deleted file mode 100644 index 0e07d79d52..0000000000 --- a/.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-04-draft-provider-endpoint-interrogation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md -/agent-note-interrogating-a-draft-provider-endpoint: - en: a465a218bbc2f74e - zh: 60d1eae3c79337a0 -/agent-note-interrogating-a-draft-provider-endpoint/problem: - en: c50e44492b4b30a2 - zh: 74a17b3d3ec6cf0d -/agent-note-interrogating-a-draft-provider-endpoint/decision: - en: dc323940d59008f4 - zh: fc94284fcda71542 -/agent-note-interrogating-a-draft-provider-endpoint/decision/why-not-pi-ai-s-own-refresh-machinery: - en: a9654499e32c91b3 - zh: cfb5a76500b6fe1a -/agent-note-interrogating-a-draft-provider-endpoint/alternatives-considered: - en: 3b4b4c33401d862a - zh: bb79062a431576f1 -/agent-note-interrogating-a-draft-provider-endpoint/consequences: - en: a4911a205e713f60 - zh: 04adc6be1f4f34c7 -/agent-note-interrogating-a-draft-provider-endpoint/testing: - en: 40cf0cf6141eba6e - zh: 54a6097cf125b846 diff --git a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.i18n.yaml index 9ac900a05f..f32414668b 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.i18n.yaml @@ -9,14 +9,14 @@ en: 1fe344c95a0de36d zh: 656c3e0f1117f64e /agent-note-forked-children-preserve-the-parent-request-prefix/decision: - en: 7461852090348b34 - zh: ad6c9e0ce65a3e85 + en: b70aafc9ce30dd00 + zh: 74e3ff915bd968a9 /agent-note-forked-children-preserve-the-parent-request-prefix/alternatives-considered: en: 80504ffd8331e6c5 zh: 193261bdde24b4bb /agent-note-forked-children-preserve-the-parent-request-prefix/consequences: - en: 54c4ce3ee0fee4d2 - zh: 8a63fb3486c76551 + en: 0738d36f17c889b5 + zh: b71ee47f75d6d4e0 /agent-note-forked-children-preserve-the-parent-request-prefix/consequences/accepted-risks: en: f634caa9973eaedb zh: 820291096c8e7352 diff --git a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md index 0edf6ca934..4b52122e86 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md +++ b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md @@ -12,9 +12,11 @@ The earlier shipped composition avoided this mismatch by keeping forked children ## Decision +The shared activation entry point, external execution ownership, and caller-versus-parent result delivery are governed by [Unified subagent activations](../simplification/2026-09-17-unified-subagent-activations.md). This record retains the independent rationale described below. + The model-facing `send_message` tool is registered globally for every Agent in a composition. A continuable forked child therefore receives the same tool name, description, schema, and ordering as its parent. Its initial task is appended after the inherited Session seed, and the task includes the direct parent id plus guidance to return results with `send_message({ agent_id, message })` when that tool is visible to the child. -The base and headless compositions retain one-shot fork as their conservative lifecycle policy. The `cordis`, `standard`, and `ptc` CLI presets may bind fork to the continuable lifecycle because that binding no longer inserts child-only request-head fields. `ForkInProcessProvider.prepareContinuable()` and `ctx.subagents.startContinuable()` remain the implementation seam for those presets. +The base bundle and the `cordis`, `standard`, and `ptc` CLI presets bind fork to the continuable lifecycle. Base-backed profiles, including headless, inherit that default. `ForkInProcessProvider.prepareContinuable()` and `ctx.subagents.startActivation()` preserve the same request-prefix behavior across these compositions. Byte-identical prefix reuse is qualified by explicit deployment choices. A fork delegation that applies a child persona or `toolFilter` may still change the request head. In particular, filtering out `send_message` removes both the schema and the return guidance from the child; the runtime does not bypass an explicit allow-list. @@ -32,8 +34,8 @@ Byte-identical prefix reuse is qualified by explicit deployment choices. A fork - Parent and continuable-fork child expose byte-identical ordered tool schemas when the delegation does not request a persona or tool filter. - The inherited Session seed precedes the child's initial task and return guidance. -- The base and headless profiles keep one-shot fork, while selected CLI presets exercise continuable fork without a child-only request-head addition. -- A child sends zero or more messages to its direct parent explicitly; its final answer is not implicitly copied. The manager-owned settlement notice remains unconditional and separate. +- The base bundle and full CLI presets use local continuable fork for both parent and caller delivery, without a child-only request-head addition. +- The child can send messages to its direct parent through `send_message`. Parent delivery adds a separate runtime settlement notice; caller delivery returns the result through the activation receipt. - Keyless snapshots and package tests pin schema equality, inherited-history ordering, parent-id guidance, and child-to-parent delivery through the same `send_message` operation used in the other direction. ### Accepted risks diff --git a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md index c2e3504bd2..2c426a1e7c 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.zh.md @@ -12,9 +12,11 @@ fork 与 spawn 的差异在于:fork 会用 parent 已完成轮次的前缀作 ## 决策 +共享 activation 入口、外部执行所有权,以及调用方与父级之间的结果投递选择由[统一 subagent activation](../simplification/2026-09-17-unified-subagent-activations.zh.md) 决策拥有。本记录保留下述独立理由。 + 面向模型的 `send_message` 工具在组合中的每个 Agent 上全局注册。因此,可继续 fork child 获得与 parent 相同的工具名称、描述、schema 和顺序。其初始任务追加在继承的 Session 种子之后;当 child 可以看到该工具时,任务还会包含直接 parent id,以及使用 `send_message({ agent_id, message })` 返回结果的指引。 -base 与 headless 组合保留 one-shot fork 作为其保守生命周期策略。`cordis`、`standard` 和 `ptc` CLI preset 可以把 fork 绑定为可继续生命周期,因为该绑定不再插入 child-only 请求头字段。`ForkInProcessProvider.prepareContinuable()` 与 `ctx.subagents.startContinuable()` 仍是这些 preset 使用的实现 seam。 +base 组合包与 `cordis`、`standard` 和 `ptc` CLI preset 将 fork 绑定为可继续生命周期。基于 base 的 profile(包括 headless)继承这一默认值。`ForkInProcessProvider.prepareContinuable()` 与 `ctx.subagents.startActivation()` 在这些组合中保持相同的请求前缀行为。 逐字节相同的前缀复用受显式部署选择约束。配置 child persona 或 `toolFilter` 的 fork 委派仍可能改变请求头。尤其是过滤掉 `send_message` 时,child 会同时失去该 schema 与返回指引;运行时不会绕过显式 allow-list。 @@ -32,8 +34,8 @@ base 与 headless 组合保留 one-shot fork 作为其保守生命周期策略 - 未请求 persona 或工具过滤时,parent 与可继续 fork child 暴露逐字节相同且顺序一致的工具 schema。 - 继承的 Session 种子位于 child 初始任务及其返回指引之前。 -- base 与 headless profile 保持 one-shot fork;选定的 CLI preset 会在没有 child-only 请求头增量的前提下使用可继续 fork。 -- child 显式向直接 parent 发送零条或多条消息;最终回答不会被隐式复制。管理器负责的结算通知仍然无条件执行,并且与 Agent 消息分离。 +- base 组合包与完整 CLI preset 对 parent 和 caller 投递均使用本地可继续 fork,不增加 child-only 请求头字段。 +- child 可以通过 `send_message` 向直接 parent 发消息。parent 投递会追加独立的运行时结算通知;caller 投递通过 activation 回执返回结果。 - keyless snapshot 与包测试固定 schema 相等性、继承历史顺序、parent-id 指引,以及通过同一个 `send_message` 操作完成的 child-to-parent 投递。 ### 已接受风险 diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml index 93f0597f19..6512532f4a 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.i18n.yaml @@ -6,17 +6,17 @@ en: 4b22d7def09af6eb zh: 89b43fb2761cb17b /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/problem: - en: 5a5d4de7d40ed86b - zh: abfae08f9c291c54 + en: f060b06cffd3ab9b + zh: 350ce35558846227 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/decision: - en: 60be2f515ff5f46d - zh: ee8514deadb08260 + en: be767ee6f00ac6c3 + zh: 7742a77e3ea12a5e /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/testing: - en: a7b7ca354607dd21 - zh: f88356bfc7dc770e + en: 960fc4b18dff9f38 + zh: d549e33100351124 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/alternatives-considered: - en: eac46307265e647d - zh: acfad02f8ca1dc3f + en: ca7c9088d0793e1e + zh: ea648bac9e726c64 /agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/consequences: - en: d8576d7db9dea547 - zh: 428bb5a7e609d170 + en: de26cde94adda770 + zh: 7f39760b77958966 diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md index daf48f27c2..546423f32e 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.md @@ -6,7 +6,7 @@ English | [中文](2026-08-10-host-plane-ownership-after-presets.zh.md) ## Problem -[Per-session agent presets](2026-09-18-declarative-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([child agents join their parent's preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)). +[Per-session agent presets](2026-09-18-declarative-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([historical child agents join their parent's preset](../../archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)). Two more readers were still on the wrong side of that line. @@ -18,7 +18,7 @@ Nothing named an agent that joined no preset. The join is a scope-parent link; w **The meter is host-plane.** `dsh-token-meter` returns to the host composition and leaves the presets' `isolate` map, so `compaction-basic` and `tool-result-pruner` resolve the one host instance from inside their realm. The presets keep the realm and the backend — what a preset chooses is whether its agent compacts, not whether its tokens are counted. This is the criterion `tasks` and `goals` are already read by, applied to a Service whose *projection* reach is what made preset ownership wrong: a unit whose empty value is indistinguishable from a real one cannot be per-composition while the table it registers into is per-process. -**An unjoined agent fails at model use.** The invariant companion checks `system-prompt/assemble`, because a bare Agent can legally be bound later through `recompose`. Host-only and cold-scope prompt reads have no Agent and remain outside this check. +**Entry points join Agents explicitly.** No runtime check rejects an unjoined Agent, because a bare Agent can legally be bound later through `recompose`. Projection key presence is not a per-session capability signal ([session projection](../../../../packages/session/session-projection/README.md)). Temporary plugins mounted through `cordis_mount` belong to the composition rather than the invoking session ([tool Cordis](../../../../packages/extensions/tool-cordis/README.md)). [Declarative presets](2026-09-18-declarative-agent-presets.md) own revision retention and reclaim retired trees after their final reference releases. @@ -26,15 +26,13 @@ Projection key presence is not a per-session capability signal ([session project `apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any Agent is created — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units. -`packages/preset/agent-preset-registry/tests/invariant.spec.ts` rejects an unjoined Agent's assembly and accepts a joined Agent, a Host read and a cold-scope read. - ## Alternatives considered **Keep the meter in the preset and scope-layer the projection registry.** The precise fix, and much larger: `snapshot`, `checkpoint`, and the eager drive would each need a session→scope resolution that a cold read does not have without the api-proxy's `presenterScopeFor`. Rejected as disproportionate to one Service with no per-preset state at all; the general rule is documented on the registry instead. **Veto publication for an unjoined agent.** Loud beats silent, and the registry supports it — a synchronous `agent/created` listener that throws rolls the creation back. Rejected because composing an agent outside the roster is legal: `recompose` documents the bare agent it then binds, and the ACP bridge, the SDK server, and the headless bundle all create one. A veto would convert a capability gap into an outage. -**Check the join at `agent/created` in the companion too.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path. Prompt assembly can distinguish them. +**Check the join at `agent/created`.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path. **Move `plan-mode` and `tool-todo` off the agent plane for the same projection reason.** Rejected: both are genuinely per-preset capabilities, and their units compute an empty value for a session that never uses them, which clients already read by value (`plan.active`, an empty list). Only a unit whose empty value is indistinguishable from a real one — the meter — forces host ownership. @@ -42,4 +40,4 @@ Projection key presence is not a per-session capability signal ([session project The context meter becomes a per-session fact instead of a function of mount history. A preset can no longer opt out of token accounting; no shipped preset did, and `minimal` now says it drops auto-compaction rather than the accounting. -The invariant reaches compositions loading `dsh-invariants`. Production entry points must compose Agents explicitly; installing the registry alone does not bind them. +Production entry points must compose Agents explicitly; installing the registry alone does not bind them. An unjoined Agent receives no tools, prompt sections, or skills and reports no error. diff --git a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md index 56babf5e71..59d318b410 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-host-plane-ownership-after-presets.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -[逐会话 agent preset](2026-09-18-declarative-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([子 agent 加入父方 preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md))。 +[逐会话 agent preset](2026-09-18-declarative-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([历史子 agent 加入父方 preset](../../archived/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md))。 还有两个读取点仍站在这条线的错误一侧。 @@ -18,7 +18,7 @@ Status: implemented **meter 属于宿主平面。** `dsh-token-meter` 回到宿主组装,并离开各 preset 的 `isolate` 映射,于是 `compaction-basic` 与 `tool-result-pruner` 在自己的 realm 内部解析到那一份宿主实例。preset 保留 realm 与压缩后端——preset 选择的是它的 agent 是否压缩,而不是它的 token 是否被计。这正是 `tasks` 与 `goals` 已经采用的判据,只是这次适用于一个因**投影**触达面而不该归 preset 所有的 Service:当一个单元的空值与真实值无法区分时,只要它注册进的那张表是进程级的,它就不能是逐组装的。 -**未加入的 Agent 在使用模型时失败。** 不变量 companion 检查 `system-prompt/assemble`,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。纯 Host 和历史 scope 提示词读取没有 Agent,不属于此检查范围。 +**入口显式让 Agent 加入。** 没有运行时检查会拒绝未加入的 Agent,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。 投影 key 是否存在不能代表逐会话能力([会话投影](../../../../packages/session/session-projection/README.zh.md))。通过 `cordis_mount` 挂载的临时插件属于组装,而非调用会话([Cordis 工具](../../../../packages/extensions/tool-cordis/README.zh.md))。[声明式 preset](2026-09-18-declarative-agent-presets.zh.md) 负责代际保留,在最后一个引用释放后回收退役树。 @@ -26,15 +26,13 @@ Status: implemented `apps/cli/tests/web-agent-presets.e2e.ts` 在创建任何 Agent **之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。 -`packages/preset/agent-preset-registry/tests/invariant.spec.ts` 拒绝未加入的 Agent 组装,并接受已加入的 Agent、Host 读取和历史 scope 读取。 - ## 考虑过的替代方案 **把 meter 留在 preset,改为给投影注册表分层。** 这是更精确的修法,代价也大得多:`snapshot`、`checkpoint` 与主动驱动都需要一次「会话 → 作用域」的解析,而冷读在没有 api-proxy 的 `presenterScopeFor` 时并不具备。相对于一个完全没有 per-preset 状态的 Service,这不成比例,因此改为把通则写在注册表上。 **对未加入的 agent 否决发布。** 大声胜过静默,注册表也支持这么做——同步的 `agent/created` 监听器抛出会把创建整体回滚。否决的理由是:在名单之外组装 agent 是合法的——`recompose` 写明了它随后绑定的那个裸 agent,而 ACP 桥、SDK server 与 headless bundle 都会创建一个。否决会把能力缺口变成一次故障。 -**让配套也在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。提示词组装分得清。 +**在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。 **基于同样的投影理由,把 `plan-mode` 与 `tool-todo` 也搬离 agent 平面。** 否决:两者确实是逐 preset 的能力,且对从不使用它们的会话,其单元算出的就是空值,而客户端本来就按值读取(`plan.active`、空列表)。只有空值与真实值无法区分的单元——meter——才被迫归宿主所有。 @@ -42,4 +40,4 @@ Status: implemented context meter 成为逐会话的事实,而不再是挂载历史的函数。代价是 preset 不能再选择不做 token 记账;随附的 preset 没有一个这么做,`minimal` 现在也写明它放弃的是自动压缩而非记账。 -不变量覆盖加载了 `dsh-invariants` 的组装。生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。 +生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。未加入的 Agent 不会获得工具、提示词段落或 skill,也不会报告错误。 diff --git a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml deleted file mode 100644 index b0f473168f..0000000000 --- a/.agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.i18n.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-10-remote-event-delivery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-remote-event-delivery.md -/agent-note-remote-event-delivery-ctx-remote-on: - en: 4b64b9fab76b5185 - zh: 57a11b8a480a49dd -/agent-note-remote-event-delivery-ctx-remote-on/problem: - en: d3016e40cefe047f - zh: 83e83c25a13383e5 -/agent-note-remote-event-delivery-ctx-remote-on/decision: - en: 83f6f48ce1fc517b - zh: abcfcd1c0eb777ea -/agent-note-remote-event-delivery-ctx-remote-on/decision/consumer-contract-dsh-typert-protocol: - en: 8f30b121dd59a3a3 - zh: 92a5e0e63936c393 -/agent-note-remote-event-delivery-ctx-remote-on/decision/the-allowlist-one-declaration-read-by-both-faces: - en: ce8c2e030baf7049 - zh: bb6beb40228e40ae -/agent-note-remote-event-delivery-ctx-remote-on/decision/wire-protocol-api-gateway-remote-mux: - en: 5c89d238a469e79f - zh: d0cf485f0d245e7d -/agent-note-remote-event-delivery-ctx-remote-on/decision/the-apps-web-browser-e2e-belongs-to-the-host-face: - en: a29e07b8013f7f9f - zh: 4f066efa96f2d22f -/agent-note-remote-event-delivery-ctx-remote-on/decision/change-inventory: - en: 49fc44d242a87b69 - zh: 42c4c36296849a88 -/agent-note-remote-event-delivery-ctx-remote-on/alternatives-considered: - en: b75743ab277c1557 - zh: e8e05f41ce1bc2f3 -/agent-note-remote-event-delivery-ctx-remote-on/verification: - en: 4ec0e1c76da8d3b6 - zh: 23de66f8fc659c15 -/agent-note-remote-event-delivery-ctx-remote-on/consequences: - en: c50a0968e3f65df7 - zh: 786f33cf1e95dae4 -/agent-note-remote-event-delivery-ctx-remote-on/related: - en: b64e771dafa6c05b - zh: 4c2f47884f5a2d19 diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml index 94fd1f56d0..bc46999689 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.i18n.yaml @@ -21,5 +21,5 @@ en: ef7c5f6fa346c1d8 zh: 439b0d8394015f1e /agent-note-credential-records-and-authorization-flows/testing: - en: 6c8c01f75e6f916b - zh: 7c5c7491f64f2936 + en: 89f5714501a92b19 + zh: c92fac2c35532db1 diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md index 6bf65761c9..ad7c402aed 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.md @@ -61,7 +61,7 @@ Two limits are recorded in the package READMEs rather than fixed. An attempt is ## Testing -The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error. The invariant companion pins that a settled key is a free key, because a wedged one is otherwise invisible. +The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error. `llm-pi-ai` covers the three translations against a real `$DSH_HOME` document — an api-key credential field by field, an OAuth credential verbatim including its refresh half, a foreign plugin's record skipped by scope, and the write refusal without a credentials service — plus every `AuthEvent` and `AuthPrompt` member restated, with `Models.login()` mocked at the collection boundary since a real one opens a browser. Two real-composition tests boot the plugin with and without the authorization seam. diff --git a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md index 72ec310c2f..8600104f36 100644 --- a/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-13-credential-records-and-authorization-flows.zh.md @@ -61,7 +61,7 @@ seam 的边缘与写入路径同一纪律。prompt 被拒是结果而非故障 ## Testing -seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。invariant companion 钉住"已结算的键就是空闲的键",因为被卡住的键否则不可见。 +seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。 `llm-pi-ai` 针对一份真实的 `$DSH_HOME` 文档覆盖三处翻译——逐字段的 api-key 凭据、连 refresh 半边一起原样保存的 OAuth 凭据、按 scope 跳过的他插件记录,以及没有凭据服务时的写入拒绝——外加每一个 `AuthEvent` 与 `AuthPrompt` 成员的重述;`Models.login()` 在集合边界处被 mock,因为真实登录会打开浏览器。两个真实组合测试分别在挂载与不挂载授权 seam 的情况下启动插件。 diff --git a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml deleted file mode 100644 index c0aa66e83f..0000000000 --- a/.agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-18-experimental-agent-teams-packages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-18-experimental-agent-teams-packages.md -/agent-note-publish-agent-teams-under-experimental-package-names: - en: 4c772309d60d4ccc - zh: 91bf0acc208e5167 -/agent-note-publish-agent-teams-under-experimental-package-names/problem: - en: e45938d278a6c12d - zh: 93c790d466f7bd12 -/agent-note-publish-agent-teams-under-experimental-package-names/decision: - en: a2555b0c5a6f6f53 - zh: f7082ceb8542b0fb -/agent-note-publish-agent-teams-under-experimental-package-names/alternatives-considered: - en: 869decdfbc7b3466 - zh: b51f5ab731d1a84a -/agent-note-publish-agent-teams-under-experimental-package-names/consequences: - en: f827547f976bf92b - zh: d94e17b8fa2e8304 diff --git a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.i18n.yaml index 478f9b70b4..23935ef2ad 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.i18n.yaml @@ -54,5 +54,5 @@ en: 7328f28ba3e32f4c zh: 2150f20437a473af /agent-note-session-history-control-state-and-remote-event-transport/consequences: - en: 808ce6d1310876a2 - zh: d9e8fe2aa5cda617 + en: 7e130587088a18c5 + zh: 5a7a02049aa74a5b diff --git a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.md b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.md index db5c403562..de05a0e62b 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.md +++ b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.md @@ -380,6 +380,6 @@ The general stream objects add three explicit layers while deleting the retry, c Remote waterfalls preserve first claim across multiple Clients, continuation of the Host chain after every Client calls `next`, reconnect replay of pending calls, and end-to-end cancellation. The current protocol supports only top-level Agent scope and lossless-JSON requests and results. -This decision extends the allowlist and single Cordis-signature design from [Remote event delivery](2026-08-10-remote-event-delivery.md): ordinary notifications use `emit`, while Agent-scoped async waterfalls use the same `ctx.remote.$on` surface with explicit `waterfall` mode. It creates no second invocation map. +This decision extends the allowlist and single Cordis-signature design from [Remote event delivery](../../../../packages/api/remotes/README.md): ordinary notifications use `emit`, while Agent-scoped async waterfalls use the same `ctx.remote.$on` surface with explicit `waterfall` mode. It creates no second invocation map. -This decision takes over the Session, Workspace, and Host-event carriers retained by [simple unary API Proxy migration](../../archived/architecture/2026-08-10-unary-apiproxy-remote-migration.md) while preserving the complete jobs snapshot, process-local lifecycle, and “observation does not resume an Agent” semantics required by [background job display](../feature/2026-08-08-web-background-job-display.md). +This decision takes over the Session, Workspace, and Host-event carriers retained by [simple unary API Proxy migration](../../archived/architecture/2026-08-10-unary-apiproxy-remote-migration.md) while preserving the complete jobs snapshot, process-local lifecycle, and “observation does not resume an Agent” semantics required by [background job display](../../../../packages/api/session-controller/README.md). diff --git a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.zh.md b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.zh.md index 41ff3ea2dd..004da80fee 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-18-session-history-and-event-transport.zh.md @@ -380,6 +380,6 @@ Session 与 Workspace 的 Host API、stream adapter 和 Client 数据模型各 Remote waterfall 保留多 Client 首个 claim、全体 `next` 后继续 Host chain、断线重放 pending 和端到端取消;代价是当前协议只支持一级 Agent scope 与无损 JSON 请求/结果。 -本决定扩展[Remote 事件投递](2026-08-10-remote-event-delivery.zh.md)的 allowlist 与单一 Cordis 签名设计:普通通知继续使用 `emit`,Agent-scoped async waterfall 使用同一 `ctx.remote.$on` 面和显式 `waterfall` mode;不建立第二套 invocation map。 +本决定扩展[Remote 事件投递](../../../../packages/api/remotes/README.zh.md)的 allowlist 与单一 Cordis 签名设计:普通通知继续使用 `emit`,Agent-scoped async waterfall 使用同一 `ctx.remote.$on` 面和显式 `waterfall` mode;不建立第二套 invocation map。 -本决定接管[简单一元 API Proxy 迁移](../../archived/architecture/2026-08-10-unary-apiproxy-remote-migration.md)中保留的 Session、Workspace 与 Host event carrier,并保留[后台任务展示](../feature/2026-08-08-web-background-job-display.zh.md)所要求的完整 jobs snapshot、进程内生命周期和“观察不恢复 Agent”语义。 +本决定接管[简单一元 API Proxy 迁移](../../archived/architecture/2026-08-10-unary-apiproxy-remote-migration.md)中保留的 Session、Workspace 与 Host event carrier,并保留[后台任务展示](../../../../packages/api/session-controller/README.zh.md)所要求的完整 jobs snapshot、进程内生命周期和“观察不恢复 Agent”语义。 diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml index 9de76f79b1..8cc0c37e4d 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.i18n.yaml @@ -21,8 +21,8 @@ en: eb69576e756f4aa1 zh: 5c1923159da3f7a2 /agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/verification: - en: 0bda0074b5e11b44 - zh: 4989c1bb840c78ce + en: 9935338a0970d09d + zh: a9a36702bc72a9f7 /agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/alternatives-considered: en: 5faf7699272f99b4 zh: 7a402e24c80131cf @@ -33,5 +33,5 @@ en: b6eff63f49220dcc zh: 8e4a6c785ed2256a /agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/consequences: - en: 144fc01ad3b2b9bd - zh: 0acff935bd7835de + en: 853cd7b21db6208b + zh: bfaf459f006fe2db diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md index 15dc88a3b6..c2ad167f14 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.md @@ -50,7 +50,7 @@ The process-lifetime manifest-identity cache remains separate because in-process ## Verification -Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, out-of-order acceptance, and late invariant loading. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests. +Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, and out-of-order acceptance. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests. ## Alternatives considered @@ -89,4 +89,4 @@ Official DeepSeek requests carry active package versions to their resolved `base The `delivery-accepted` event becomes part of the canonical log and is itself delivered on a later request. Crash recovery can duplicate a suffix but does not infer acceptance from assistant output or create a second local cursor store. Direct calls without a live Session omit the session field; host package inventory remains available. -The [DeepSeek request-identity decision](../feature/2026-08-11-deepseek-request-user-id-header.md) continues to own user/session headers, which remain outside the body. The [session-telemetry decision](../feature/2026-07-23-session-telemetry-otel-revival.md) remains current until a separate change removes that seam and backend; this request path does not alter OTel capture or sharing modes. +The [DeepSeek reference](../../../../packages/llm/llm-deepseek/README.md) defines user/session headers outside the body. The [Session telemetry reference](../../../../packages/session/session-telemetry/README.md) owns capture and redaction; this request path does not alter OTel capture or sharing modes. diff --git a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md index 4358e0f86e..91d44f59e0 100644 --- a/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-21-deepseek-llm-api-request-extensions.zh.md @@ -50,7 +50,7 @@ Status: implemented ## 验证 -注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏、乱序接受与 invariant 延迟加载。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。 +注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏与乱序接受。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。 ## 考虑过的替代方案 @@ -89,4 +89,4 @@ DeepSeek 官方请求会把存活包版本发送到解析后的 `baseURL`,包 `delivery-accepted` 事件会成为权威日志的一部分,并在后续请求中自行交付。崩溃恢复可能重复后缀,但不会根据 assistant 输出推断接受,也不会创建第二份本地游标存储。缺少存活会话的直接调用会省略会话字段;宿主包清单仍然可用。 -[DeepSeek 请求身份决策](../feature/2026-08-11-deepseek-request-user-id-header.zh.md)继续拥有 user/session header,且这些 header 仍位于正文之外。[会话遥测决策](../feature/2026-07-23-session-telemetry-otel-revival.zh.md)在另一项变更删除该 seam 与后端之前仍保持当前有效;本请求路径不改变 OTel 捕获或共享模式。 +[DeepSeek 参考](../../../../packages/llm/llm-deepseek/README.zh.md)定义正文之外的 user/session header。[会话遥测参考](../../../../packages/session/session-telemetry/README.zh.md)负责捕获与脱敏;本请求路径不改变 OTel 捕获或共享模式。 diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml index 2390a9244b..5e193c9063 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.i18n.yaml @@ -15,20 +15,20 @@ en: 9cea027468d5d37c zh: 359c6cb7842b8b7f /agent-note-one-dsh-launcher-for-application-profiles/decision/profile-applications: - en: 44028538e06420d3 - zh: 71cf992b7847d822 + en: b2f0ae09c3343874 + zh: 189ce711cd3138b3 /agent-note-one-dsh-launcher-for-application-profiles/decision/typescript-sdk-customization: en: 8ee88035cf64eeb3 zh: 6184e50fc4d47348 /agent-note-one-dsh-launcher-for-application-profiles/decision/python-runtime: - en: e154533170f1cdb6 - zh: 20f8f0d5e111f447 + en: 5962abadfdece709 + zh: 5db20e96deed49d1 /agent-note-one-dsh-launcher-for-application-profiles/decision/enforcement: en: 9ce8d86b65064f38 zh: 27b8ad426cb319a2 /agent-note-one-dsh-launcher-for-application-profiles/existing-decisions-and-supersession: - en: 71599a4df773fe78 - zh: d12ae1625d11cc9c + en: 0db3a51bbdbd7dcd + zh: 5c1f3cfc6b7ee07f /agent-note-one-dsh-launcher-for-application-profiles/alternatives-considered: en: 763d9f86e10af3db zh: e87b35f1f7425da7 diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md index 46a627eed6..7d4bb1a9c2 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.md @@ -34,7 +34,7 @@ Profile manifests own patch reload: Custom profiles default to `live`. A startup profile still applies its bundle, profile, home-level, and invocation `--patch` layers, but it does not watch them after boot. `dsh-base` inserts the module-HMR row disabled; a profile with a tested source-module reload lifecycle must enable it explicitly. None of the shipped profiles enable server module HMR: `patchReload: live` uses the launcher's config-only watcher while the startup profiles install no watcher. SDK and ACP cannot safely replace their server, agents, persistence, or tool registry inside one owned stdio connection. -The shipped protocol profiles reserve stdout for protocol frames, expose help without starting transport, and route stdin EOF and signals through bounded root disposal. ACP remains automation-only. The SDK JSON-RPC methods, notification fields, and `initialize.serverInfo.name` remain stable. Full-profile model-visible tool and persistence defaults come from `dsh-base`, including its [default editor selection](../simplification/2026-09-05-base-default-file-editor.md); `sdk-minimal` owns its explicit defaults. Runnable snapshots own the assembled application outputs. +The shipped protocol profiles reserve stdout for protocol frames, expose help without starting transport, and route stdin EOF and signals through bounded root disposal. ACP remains automation-only. The SDK JSON-RPC methods, notification fields, and `initialize.serverInfo.name` remain stable. Full-profile model-visible tool and persistence defaults come from `dsh-base`, including its [default editor selection](../../../../packages/bundle/base/README.md); `sdk-minimal` owns its explicit defaults. Runnable snapshots own the assembled application outputs. ### TypeScript SDK customization @@ -46,7 +46,7 @@ Direct SDK use follows normal Harness-home resolution: explicit `dshHome`, inher ### Python runtime -The Python runtime wheel stages [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the `dsh-python-runtime-closure` entry. Its ordinary branch calls the public CLI export; a provider-private selector dispatches to the internal subprocess runner before CLI parsing and is not an application entry point. The [native-containment decision](2026-08-28-subprocess-native-containment.md) owns that private dispatch. The Python client selects `dsh --profile sdk` by default, ordered patch files, and an explicit Harness home; the runnable example under `python/sdk/examples` selects `sdk-minimal`. The installed `dsh` console command exposes the same profile grammar, including the `web` profile. +The Python runtime wheel stages [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) as the `dsh-python-runtime-closure` entry. Its ordinary branch calls the public CLI export; a provider-private selector dispatches to the internal subprocess runner before CLI parsing and is not an application entry point. The [native-containment reference](../../../../packages/subprocess/subprocess-local/README.md) owns that private dispatch. The Python client selects `dsh --profile sdk` by default, ordered patch files, and an explicit Harness home; the runnable example under `python/sdk/examples` selects `sdk-minimal`. The installed `dsh` console command exposes the same profile grammar, including the `web` profile. The executable family is `deepseek-harness-sdk-runtime--`. The SDK wire, wheel and import distribution names, sidecar names, and wire identity `deepseek-harness-sdk-runtime` remain stable. The SDK package family is `@deepseek-ai/dsh-sdk-client`, `@deepseek-ai/dsh-sdk-protocol`, and `@deepseek-ai/dsh-sdk-jsonrpc-server`; `@deepseek-ai/dsh-acp` remains the ACP protocol plugin. There is no Python-specific Node application, checked-in complete config, compatibility package, forwarding executable, fallback parser, or SDK/ACP launcher alias. [docs/architecture.md](../../../../docs/architecture.md) owns this launch, and the [`python/sdk-runtime` README](../../../../python/sdk-runtime/README.md) owns the Windows carrier. @@ -56,7 +56,7 @@ The executable family is `deepseek-harness-sdk-runtime--`. The S ## Existing decisions and supersession -[Profile command shorthand](../feature/2026-09-15-profile-command-shorthand.md) supersedes this note's Web-only shorthand mechanism; this note retains authority over application composition and lifecycle ownership. +The [CLI reference](../../../../apps/cli/reference/README.md) documents shorthand for every profile; this note retains authority over application composition and lifecycle ownership. This decision supersedes the application-launch and package-name facts in [profile plugin bundles](2026-08-05-profile-plugin-bundles.md), [TypeScript SDK client and subagent backend](../../archived/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md), [remove the SDK project toolchain](../../archived/simplification/2026-08-11-remove-sdk-project-toolchain.md), and [single-file Python SDK runtime distribution](2026-07-10-single-file-executable-sdk-runtime-distribution.md). Those notes retain independent authority for profile layering, client/wire semantics, deleted project tooling, and native packaging. diff --git a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md index b7fa0f2b35..eb7d5ff8eb 100644 --- a/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-22-single-dsh-application-launcher.zh.md @@ -34,7 +34,7 @@ Profile manifest 负责 patch 重载: 自定义 profile 默认为 `live`。`startup` profile 仍会应用组合包、profile、home 级与调用时 `--patch` 各层,但启动后不会监视这些文件。`dsh-base` 插入的模块 HMR(热模块替换)配置项默认禁用;具有经过验证的源码模块重载生命周期的 profile 必须显式启用它。随附 profile 均不启用服务器模块 HMR:`patchReload: live` 使用启动器的仅配置 watcher,`startup` profile 则不安装 watcher。SDK 与 ACP 无法在一个自有 stdio 连接内安全替换其服务器、agent、持久化或工具注册表。 -随附协议 profile 将 stdout 保留给协议帧,显示帮助时不启动 transport,并通过有界根节点 dispose(资源释放)处理 stdin EOF 与信号。ACP 继续仅用于自动化。SDK JSON-RPC 方法、通知字段与 `initialize.serverInfo.name` 保持稳定。完整 profile 的模型可见工具与持久化默认值来自 `dsh-base`,包括其[默认编辑器选择](../simplification/2026-09-05-base-default-file-editor.zh.md);`sdk-minimal` 拥有自己的显式默认值。可运行快照负责固定已组装的应用输出。 +随附协议 profile 将 stdout 保留给协议帧,显示帮助时不启动 transport,并通过有界根节点 dispose(资源释放)处理 stdin EOF 与信号。ACP 继续仅用于自动化。SDK JSON-RPC 方法、通知字段与 `initialize.serverInfo.name` 保持稳定。完整 profile 的模型可见工具与持久化默认值来自 `dsh-base`,包括其[默认编辑器选择](../../../../packages/bundle/base/README.zh.md);`sdk-minimal` 拥有自己的显式默认值。可运行快照负责固定已组装的应用输出。 ### TypeScript SDK 自定义 @@ -46,7 +46,7 @@ SDK 用户通过 profile 自定义插件。`dsh plugin --profile ...` 管 ### Python 运行时 -Python 运行时 wheel 将 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) 暂存为 `dsh-python-runtime-closure` 入口。其普通分支调用公开 CLI export;提供方私有选择会在 CLI 解析前分派到内部子进程 runner,而不是应用入口。[原生 containment 决策](2026-08-28-subprocess-native-containment.zh.md)负责该私有分派。Python 客户端默认选择 `dsh --profile sdk`、有序 patch 文件与显式 Harness home;`python/sdk/examples` 下的可运行示例选择 `sdk-minimal`。安装的 `dsh` 控制台命令暴露相同的 profile 语法,包括 `web` profile。 +Python 运行时 wheel 将 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../../../python/sdk-runtime/runtime-bootstrap.mjs) 暂存为 `dsh-python-runtime-closure` 入口。其普通分支调用公开 CLI export;提供方私有选择会在 CLI 解析前分派到内部子进程 runner,而不是应用入口。[原生 containment 参考](../../../../packages/subprocess/subprocess-local/README.zh.md)负责该私有分派。Python 客户端默认选择 `dsh --profile sdk`、有序 patch 文件与显式 Harness home;`python/sdk/examples` 下的可运行示例选择 `sdk-minimal`。安装的 `dsh` 控制台命令暴露相同的 profile 语法,包括 `web` profile。 可执行文件族是 `deepseek-harness-sdk-runtime--`。SDK 协议格式、wheel 与 import 分发名称、伴随文件名称,以及协议 identity `deepseek-harness-sdk-runtime` 保持稳定。SDK 包族是 `@deepseek-ai/dsh-sdk-client`、`@deepseek-ai/dsh-sdk-protocol` 与 `@deepseek-ai/dsh-sdk-jsonrpc-server`;`@deepseek-ai/dsh-acp` 继续作为 ACP 协议插件。仓库不保留 Python 专用 Node 应用、检入的完整配置、兼容包、转发可执行文件、后备解析器或 SDK/ACP 启动别名。[docs/architecture.md](../../../../docs/architecture.zh.md)负责该启动方式,[`python/sdk-runtime` README](../../../../python/sdk-runtime/README.zh.md)负责 Windows 载体。 @@ -56,7 +56,7 @@ Python 运行时 wheel 将 [`python/sdk-runtime/runtime-bootstrap.mjs`](../../.. ## 既有决策与取代关系 -[Profile 命令简写](../feature/2026-09-15-profile-command-shorthand.zh.md)取代本 Note 中仅为 Web 提供简写的机制;本 Note 继续负责应用组合与生命周期的所有权。 +[CLI 参考](../../../../apps/cli/reference/README.zh.md)说明每个 profile 的命令简写;本 Note 继续负责应用组合与生命周期的所有权。 本决策取代 [profile 插件组合包](2026-08-05-profile-plugin-bundles.zh.md)、[TypeScript SDK 客户端与 SDK subagent 后端](../../archived/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md)、[移除 SDK 项目工具链](../../archived/simplification/2026-08-11-remove-sdk-project-toolchain.md)和[单文件 Python SDK 运行时分发](2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md)中的应用启动与包名事实。这些 Note 对 profile 分层、客户端/协议语义、已删除的项目工具链与原生打包仍分别具有独立权威。 diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml index d09a4b6a82..bf89beeba0 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.i18n.yaml @@ -9,11 +9,11 @@ en: 9f34c53e647c69ca zh: 551776c7cc9618b5 /agent-note-client-derived-presentation-from-raw-session-tool-events/decision: - en: 5c8de416b8fa0fb1 - zh: 1662926ba8e17405 + en: bdbb99c01b4c4db0 + zh: 570040471ad1f7f4 /agent-note-client-derived-presentation-from-raw-session-tool-events/decision/goals-and-non-goals: - en: 4ff7b07cb79bc9de - zh: a74ec6e7ebbede0a + en: 7e7a937dc4fe1b18 + zh: 530f508b7e17b607 /agent-note-client-derived-presentation-from-raw-session-tool-events/terminology: en: 021106dd41176715 zh: 131caff7bd37251b @@ -183,8 +183,8 @@ en: bc8905288acaa820 zh: ff551936bd5ea93f /agent-note-client-derived-presentation-from-raw-session-tool-events/alternatives-considered/allow-presentation-enhancements: - en: fc270977e320fd0a - zh: bb77a11ec6973d03 + en: 5ed1954f7399ed95 + zh: 82ea5a8fd5a66967 /agent-note-client-derived-presentation-from-raw-session-tool-events/alternatives-considered/accept-temporary-generic-degradation: en: 1a8fd108d32bd199 zh: 5cd1c7adf3fe824d @@ -222,8 +222,8 @@ en: d7e581086940fafa zh: d452320fee6ef875 /agent-note-client-derived-presentation-from-raw-session-tool-events/relationship-to-existing-decisions: - en: f7501cfad1c94062 - zh: 2574582ee86c3c4a + en: ecd63b5a47a1ec5e + zh: c98c8a27a261419a /agent-note-client-derived-presentation-from-raw-session-tool-events/deferred: - en: dcd92bc33fbb1885 - zh: 3d1ea8a6b3f3f3a7 + en: a60c9007c9d26dfd + zh: 225b1f7544bca324 diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md index 931276fa6d..ee1b54f6ee 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md +++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.md @@ -22,7 +22,7 @@ The required result is one raw Session journal and one Client presentation owner ## Decision -The visual-equivalence requirements below exclude the separately approved [nested terminal-card fix](../bug-fix/2026-09-05-nested-terminal-cards.md) and [compact tool details](2026-09-10-compact-tool-details.md); all other presentation and ownership constraints remain. +The visual-equivalence requirements below exclude the separately approved [nested terminal-card fix](../../../../packages/client/ui-tool/README.md) and [recorded tool details](../../../../packages/client/ui-tool/README.md); all other presentation and ownership constraints remain. The Session Remote journal sends only raw, validated, persistable Session events. `session.page` and `session.follow` do not parse tool arguments, query the Tools registry, restore a presenter scope, execute `presentCall` or `presentResult`, or construct or clone any tool view. @@ -51,7 +51,7 @@ The Host `ToolDefinition.presentCall`, `ToolDefinition.presentResult`, `ToolCall | Retained | the Session log format, Remote journal lifecycle, and Conversation identity/topology | | Retained | the existing keyed slot, Generic fallback, and Chat, Details, and Trajectory structure | | Forbidden | a new Client presenter service, parallel registry, or wire renderer id | -| Forbidden | new cards, visual redesign, interaction redesign, or PTC dispatch rich-card enhancements except the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) and [compact tool details](2026-09-10-compact-tool-details.md) | +| Forbidden | new cards, visual redesign, interaction redesign, or PTC dispatch rich-card enhancements except the [nested terminal-card exception](../../../../packages/client/ui-tool/README.md) and [recorded tool details](../../../../packages/client/ui-tool/README.md) | | Forbidden | compatibility dual-writing, version negotiation, or retention of the old `view` field | ## Terminology @@ -634,7 +634,7 @@ An on-demand RPC would turn one page read into N network calls and would still r ### Allow presentation enhancements -Bundling richer PTC dispatch cards, missing-call-head inference, or other historical presentation enhancements with the ownership change would prevent snapshots from proving equivalence. This decision rejects that coupling; the [nested terminal-card exception](../bug-fix/2026-09-05-nested-terminal-cards.md) does not relax nonterminal child restrictions. +Bundling richer PTC dispatch cards, missing-call-head inference, or other historical presentation enhancements with the ownership change would prevent snapshots from proving equivalence. This decision rejects that coupling; the [nested terminal-card exception](../../../../packages/client/ui-tool/README.md) does not relax nonterminal child restrictions. ### Accept temporary Generic degradation @@ -686,7 +686,7 @@ The absence of optional `view` is a prerelease wire-type decision shared by all ## Relationship to Existing Decisions -[Nested terminal cards](../bug-fix/2026-09-05-nested-terminal-cards.md) partially supersedes only the terminal child-card prohibition and its visual-equivalence requirement. This note remains active for raw-journal ownership, Client derivation, and the diff/read/search/web child restrictions. +The current [terminal-card rules](../../../../packages/client/ui-tool/README.md) permit nested terminal cards, replacing only the terminal child-card prohibition and its visual-equivalence requirement. This note remains active for raw-journal ownership, Client derivation, and the diff/read/search/web child restrictions. This note partially supersedes the implementation fact in [Client tool presentation ownership](../../archived/architecture/2026-08-08-client-tool-presentation-ownership.md) that “card models receive Host views.” Its core decisions remain: `ui-tool` owns presentation, business plugins use keyed slots, and Conversation owns only lifecycle and topology. @@ -698,12 +698,12 @@ This note updates the entry contract from [Session history and Remote event tran This note follows [Conversation Node assembly](2026-08-09-client-conversation-node-assembly.md): the Tool Definition owns event pairing and the call tree, while concrete card models remain in `ui-tool`. -This note preserves result metadata from the [canonical tool output contract](2026-07-20-canonical-tool-output-contract.md), because it is the lossless, replayable input to Client derivation. +This note preserves result metadata from the [canonical tool output contract](../../../../packages/core/tools/README.md), because it is the lossless, replayable input to Client derivation. ## Deferred - A separate explicit decision may evaluate deleting Host presenters if they remain without production consumers; this decision does not prejudge it. -- Specialized diff, read, search, and web cards for PTC dispatch subcalls require a separate design and visible-snapshot updates; terminal calls are covered by the linked partial supersession. +- Specialized diff, read, search, and web cards for PTC dispatch subcalls require a separate design and visible-snapshot updates; terminal calls follow the linked current card rules. - A third-party mutation tool that joins Deliverables requires a new Client-owned contribution; this decision does not create a registry for an absent consumer. - Distinct Client presentation for same-named providers first requires a stable, non-presentational identity; it must not restore per-page Host views. - If Client card-model performance needs measurement, an immutable-block microbenchmark can be added; the shipped architecture already prohibits scanning the Session window. diff --git a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md index b930f98da1..332c30df9b 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-23-client-derived-tool-presentation.zh.md @@ -22,7 +22,7 @@ Host presenter 与 Client keyed renderer 分担展示会形成对同一事件的 ## Decision -下述展示对等要求不包含已独立批准的[嵌套 terminal 卡片修复](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)和[紧凑工具详情](2026-09-10-compact-tool-details.zh.md);其他展示与所有权约束全部保留。 +下述展示对等要求不包含已独立批准的[嵌套 terminal 卡片修复](../../../../packages/client/ui-tool/README.zh.md)和[记录结果的工具详情](../../../../packages/client/ui-tool/README.zh.md);其他展示与所有权约束全部保留。 Session Remote journal 只下发原始、已验证、可持久化的 Session event。`session.page` 和 `session.follow` 不解析工具参数,不查询 Tools registry,不恢复 presenter scope,不执行 `presentCall`/`presentResult`,也不构造或克隆任何 tool view。 @@ -51,7 +51,7 @@ Host 的 `ToolDefinition.presentCall`、`ToolDefinition.presentResult`、`ToolCa | 保留 | Session 日志格式、Remote journal 生命周期与 Conversation identity/topology | | 保留 | 现有 keyed slot、Generic fallback、Chat、Details 与 Trajectory 结构 | | 禁止 | 新 Client presenter service、平行 registry 或 wire renderer id | -| 禁止 | 新卡片、视觉改版、交互改版或 PTC dispatch rich-card 增强,[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)和[紧凑工具详情](2026-09-10-compact-tool-details.zh.md)除外 | +| 禁止 | 新卡片、视觉改版、交互改版或 PTC dispatch rich-card 增强,[嵌套 terminal 卡片例外](../../../../packages/client/ui-tool/README.zh.md)和[记录结果的工具详情](../../../../packages/client/ui-tool/README.zh.md)除外 | | 禁止 | 为兼容保留双写、版本协商或旧 `view` 字段 | ## 术语 @@ -634,7 +634,7 @@ read 行结构、applied diff、search 分组、web sources 和有效 truncation ### 允许展示增强 -将更丰富的 PTC dispatch 卡片、缺失 call head 的推断或其他历史展示增强与所有权变更捆绑,会使快照无法证明对等。本决定拒绝这种捆绑;[嵌套 terminal 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)不放宽非 terminal 子调用限制。 +将更丰富的 PTC dispatch 卡片、缺失 call head 的推断或其他历史展示增强与所有权变更捆绑,会使快照无法证明对等。本决定拒绝这种捆绑;[嵌套 terminal 卡片例外](../../../../packages/client/ui-tool/README.zh.md)不放宽非 terminal 子调用限制。 ### 接受临时 Generic 退化 @@ -686,7 +686,7 @@ optional `view` 的缺失是所有 consumer 共同遵守的预发布 wire 类型 ## 与现有决策的关系 -[嵌套 terminal 卡片](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)仅部分取代 terminal 子调用卡片禁令及其展示对等要求。本文继续负责原始 journal 所有权、Client 派生以及 diff/read/search/web 子调用限制。 +当前[terminal 卡片规则](../../../../packages/client/ui-tool/README.zh.md)允许嵌套 terminal 卡片,仅替代 terminal 子调用卡片禁令及其展示对等要求。本文继续负责原始 journal 所有权、Client 派生以及 diff/read/search/web 子调用限制。 本文部分取代 [Client 工具展示所有权](../../archived/architecture/2026-08-08-client-tool-presentation-ownership.md) 中“card model 接收 Host view”的实现事实;`ui-tool` 拥有展示、业务插件使用 keyed slot、Conversation 只拥有生命周期与拓扑的核心决定保持不变。 @@ -698,12 +698,12 @@ optional `view` 的缺失是所有 consumer 共同遵守的预发布 wire 类型 本文遵循 [Conversation Node 组装](2026-08-09-client-conversation-node-assembly.zh.md):Tool Definition 负责事件配对与调用树,具体 card model 留在 `ui-tool`。 -本文保留 [规范工具输出约定](2026-07-20-canonical-tool-output-contract.zh.md) 的 result metadata,因为它是无损、可重放 Client 派生的输入。 +本文保留 [规范工具输出约定](../../../../packages/core/tools/README.zh.md) 的 result metadata,因为它是无损、可重放 Client 派生的输入。 ## Deferred - Host presenter 若长期没有生产消费者,可由另一项明确决策评估删除;本决定不预判。 -- PTC dispatch 子调用的 diff、read、search 和 web 专用卡片仍需独立设计并更新可见快照;terminal 调用由链接的部分取代决策负责。 +- PTC dispatch 子调用的 diff、read、search 和 web 专用卡片仍需独立设计并更新可见快照;terminal 调用遵循链接的当前卡片规则。 - 第三方 mutation tool 若要加入 Deliverables,需新增 Client-owned 贡献;本决定不为尚无消费者的扩展性建 registry。 - 同名 provider 若要不同 Client 展示,需先定义稳定、非展示性的 identity;不得恢复按页 Host view。 - Client card model 若需量化性能,可以增加 immutable-block 微基准;已交付架构禁止扫描 Session window。 diff --git a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml index 7dc21e59f4..981cb258f7 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md /agent-note-package-and-update-the-electron-desktop-application: - en: eb6e843bc5a11195 - zh: b245e9a3615e01f0 + en: 477aadc664db857a + zh: fa18c8ec5fe00746 /agent-note-package-and-update-the-electron-desktop-application/problem: en: 5a39322836f06cd9 zh: 7a7ab48fe38a5788 /agent-note-package-and-update-the-electron-desktop-application/decision: - en: bd6d1e19da0d5a87 - zh: f194ae90331a27ea + en: b4d04297a7abae71 + zh: 9b770c81c2267869 /agent-note-package-and-update-the-electron-desktop-application/ownership: en: 50e16f318c5d0e1a zh: 84b2ba8f1a6932b0 @@ -21,11 +21,11 @@ en: 29def175ab33b7d6 zh: 87e55ec28c20c7c1 /agent-note-package-and-update-the-electron-desktop-application/updates-and-recovery: - en: 9c9e8a54a11bec8b - zh: 305cf1f6ed374d0b + en: e9f140cd22670dfa + zh: bf85cfe9c12a0345 /agent-note-package-and-update-the-electron-desktop-application/security-and-release-policy: - en: 4fb110927a3ed7a9 - zh: 5a263577f097490a + en: 97ee608e96fc3602 + zh: 932b98e7d344cd87 /agent-note-package-and-update-the-electron-desktop-application/implementation: en: 041a6d35ed61b149 zh: 617c6b90a6f4fa4d diff --git a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md index a985e1c91a..26a6b9ba99 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md +++ b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md @@ -6,7 +6,7 @@ English | [中文](2026-08-25-electron-desktop-packaging-and-updates.zh.md) Plugin management and native recovery follow the [shared Web wrapper decision](2026-09-10-desktop-web-wrapper.md). -The [Electron runtime decision](2026-09-11-desktop-electron-node-runtime.md) supersedes the separate upstream Node executable; other decisions in this note remain applicable. +Desktop uses Electron as its Node runtime under the [current runtime reference](../../../../apps/desktop/README.md); other decisions in this note remain applicable. ## Problem @@ -18,7 +18,7 @@ The current GUI protocol binds the Web client and backend release. Independently ## Decision -Ship a small Electron shell and pinned pnpm; the [runtime decision](2026-09-11-desktop-electron-node-runtime.md) owns the executable choice. The [thin-wrapper decision](2026-09-10-desktop-web-wrapper.md) owns Host boot and transport: the private Host runs the shared Web profile runner, Electron loads its authenticated HTTP URL, and child IPC carries lifecycle messages. +Ship a small Electron shell and pinned pnpm; the [runtime reference](../../../../apps/desktop/README.md) owns the executable choice. The [thin-wrapper decision](2026-09-10-desktop-web-wrapper.md) owns Host boot and transport: the private Host runs the shared Web profile runner, Electron loads its authenticated HTTP URL, and child IPC carries lifecycle messages. Electron owns the reserved profile at `.dsh/profiles/desktop`. The [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md) owns core resource storage, external plugin dependencies, shared package links, and profile reconciliation. The private Desktop Host remains outside the public CLI package and is never published to npm. @@ -71,7 +71,7 @@ Core materialization, first launch, plugin installation, and shared-module resol Electron update uses one `electron-updater` release stream and signed `electron-builder` artifacts. Its version is the Desktop release version; there is no independent dsh manifest, compatibility range, or dsh-only update operation. A foreground install waits for an in-flight background check rather than reusing its result as an install result. The update dialog downloads and installs the Electron artifact, then restarts into the new release. -The [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-start.md) owns the local loading page, direct Host startup, and recovery in the main window. Profile reconciliation follows the [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md). +The [immediate-window reference](../../../../apps/desktop/README.md) owns the local loading page, direct Host startup, and recovery in the main window. Profile reconciliation follows the [bundled-runtime decision](2026-09-08-desktop-bundled-runtime-and-external-plugins.md). `DSH_DESKTOP_AUTO_UPDATE_ENV` selects the test deployment by default or the production deployment for both the target-specific generic-provider URL and COS destination. Release automation supplies the test HTTPS origin through `DOWNLOAD_TEST_ORIGIN` and each deployment's bucket through `DOWNLOAD_TEST_COS_BUCKET` or `DOWNLOAD_PROD_COS_BUCKET`; keeping mutable test routing and COS storage identities out of source lets deployment infrastructure change without a code release, while the public production origin remains fixed. Packaging resolves only the public updater URL, disables electron-builder publishing, removes every COS credential field from its subprocess environment, and writes a completion record only after electron-builder and every signing or notarization hook succeeds. Target upload additionally requires the selected bucket, then requires the completion record, root dsh version, Desktop version, version-derived channel metadata, artifact names, sizes, and SHA-512 values to agree before it reads the selected credentials or sends data. It uploads immutable versioned updater payloads and any separate blockmaps before replacing the channel metadata emitted by electron-builder, and it never deletes historical objects. Stable versions use the `latest` metadata name; prereleases use the first semantic-version prerelease identifier. NSIS embeds its blockmap in the signed executable; the macOS ZIP carries a separate blockmap. Both let electron-updater download changed blocks when supported, while application replacement and the local pnpm package operation remain separate operations. @@ -81,7 +81,7 @@ Test releases require `DOWNLOAD_TEST_RELEASE_ID` from the platform dotenv file. Core dsh and the private Desktop Host come only from the signed application resource tree. Plugin installation forwards package specs to pnpm, including local and remote sources, but never accepts raw pnpm commands. pnpm owns dependency resolution and the profile’s `allowBuilds` policy; the Host loads activated bundles. -Electron release artifacts are signed; macOS artifacts are notarized. Package and upload commands read release settings from the Git-ignored target `.env.windows` or `.env.macos`; subprocesses receive the fields selected by orchestration. The target file exclusively owns release fields so stale shell or system credentials cannot override local selection, and loading does not mutate the parent environment. Packaging validates the mode-specific application ID, update origin, signing identity, and local files before builds, downloads, or release-record cleanup; macOS also requires one complete notarization strategy. The separate `check:package` command runs the same validation without accessing the token or Apple; actual signing and notarization still verify authentication. Configuration loading rejects missing or malformed identifiers and incomplete notarization credentials, while macOS packaging requires signing so certificate discovery cannot silently select another installed identity or emit an unsigned release. Runtime preparation verifies the exact Authority and Team ID plus the timestamp and hardened-runtime flags on every embedded Mach-O file. An after-sign hook performs Apple's deep strict application verification and requires the same leaf Authority and Team ID before artifact creation continues. The fixed-target installer command uses [isolated App copies for parallel notarization](../process/2026-09-09-parallel-macos-notarization.md): the ZIP contains a stapled App, while the signed DMG carries the ticket covering its unstapled inner App. The DMG artifact-completion hook requires the configured identity, a valid ticket, and Gatekeeper acceptance. Both artifact lanes must succeed before the command promotes their outputs and writes the release completion record; directory-only commands still notarize and staple the App. DMG blockmaps are disabled because macOS updates consume the signed ZIP, and stapling would otherwise invalidate an already-generated DMG blockmap. +Electron release artifacts are signed; macOS artifacts are notarized. Release package and upload commands read release settings from the Git-ignored target `.env.windows` or `.env.macos`; subprocesses receive the fields selected by orchestration. The target file exclusively owns release fields so stale shell or system credentials cannot override local selection, and loading does not mutate the parent environment. Packaging validates the mode-specific application ID, update origin, signing identity, and local files before builds, downloads, or release-record cleanup; macOS also requires one complete notarization strategy. The separate `check:package` command runs the same validation without accessing the token or Apple; actual signing and notarization still verify authentication. Configuration loading rejects missing or malformed identifiers and incomplete notarization credentials, while macOS release packaging requires signing so certificate discovery cannot silently select another installed identity or emit an unsigned release. Runtime preparation verifies the exact Authority and Team ID plus the timestamp and hardened-runtime flags on every embedded Mach-O file. An after-sign hook performs Apple's deep strict application verification and requires the same leaf Authority and Team ID before artifact creation continues. The fixed-target installer command uses [isolated App copies for parallel notarization](../process/2026-09-09-parallel-macos-notarization.md): the ZIP contains a stapled App, while the signed DMG carries the ticket covering its unstapled inner App. The DMG artifact-completion hook requires the configured identity, a valid ticket, and Gatekeeper acceptance. Both artifact lanes must succeed before the command promotes their outputs and writes the release completion record; directory-only commands still notarize and staple the App. DMG blockmaps are disabled because macOS updates consume the signed ZIP, and stapling would otherwise invalidate an already-generated DMG blockmap. The [pinned osx-sign patch](../../../../patches/@electron__osx-sign@1.3.3.patch) uses `lstat` in both published module builds, so Framework file and directory aliases do not trigger duplicate signing. The patch remains necessary until the selected upstream release skips those aliases. PAK files are resources sealed by the enclosing bundle; individual signatures add serial timestamp requests without additional resource integrity. Desktop preserves all locale files and skips only their standalone signatures. Executable code retains Developer ID signatures, secure timestamps, and hardened runtime. The [signer traversal regression](../../../../apps/desktop/tests/macos-signing-walk.spec.ts) exercises the installed dependency with real Framework aliases; release qualification still requires strict application verification, notarization, and startup. @@ -89,9 +89,9 @@ Windows release packaging supplies the public EV leaf certificate named by `DSH_ Windows package invocations force `ELECTRON_BUILDER_7Z_FILTER=BCJ`. The bundled 7-Zip 24.09 encoder automatically selects ARM64 filters for ARM64 PE files, but the NSIS decoder from `nsis-resources-3.4.1` omits those entries during extraction. A native extraction probe with the actual NSIS plugin loses both `node-pty` ARM64 binaries under automatic filtering and restores both byte-for-byte with BCJ. Keeping a compatible filter preserves dependency contents and runtime integrity instead of removing architecture-specific files or weakening verification. -Local Windows installation testing uses an explicit `--unsigned` package invocation with the same build and runtime preparation. It strips certificate inputs, isolates artifacts in `unsigned-artifacts`, and omits updater configuration and the release completion record. The regular package command explicitly selects signed mode even when its parent environment requests unsigned mode. This separation permits installation diagnosis without an EV token while preventing local test output from qualifying for release upload. +Local Windows and macOS installation testing uses an explicit `--unsigned` package invocation with the same build and runtime preparation. It strips certificate inputs, isolates artifacts in `unsigned-artifacts`, and omits updater configuration and the release completion record. macOS local builds ignore dotenv files and inherited release settings, use the standard application ID, and omit mandatory-update policy so local testing needs no deployment configuration. Their App receives a certificate-free ad-hoc signature; their DMG is unsigned and unnotarized. Windows local builds retain their platform configuration requirements. The regular package command explicitly selects signed mode even when its parent environment requests unsigned mode. This separation permits installation diagnosis without release credentials while preventing local test output from qualifying for release upload. -Windows application replacement follows the [directory-installation decision](2026-09-11-windows-directory-installation.md): extract beside the destination with a command-line tool that returns failure status, then rename complete directories on the same volume. The installer retains the old directory through staging and restores it if promotion fails. Registration, shortcuts, and the signed uninstaller remain owned by electron-builder. +Windows application replacement follows the [directory-installation reference](../../../../apps/desktop/README.md): extract beside the destination with a command-line tool that returns failure status, then rename complete directories on the same volume. The installer retains the old directory through staging and restores it if promotion fails. Registration, shortcuts, and the signed uninstaller remain owned by electron-builder. Packaged applications ignore development resource and project environment overrides. Only an unpackaged Electron process can replace the pnpm entry, dsh resources, or active project. diff --git a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md index 17ace86547..21ef91d0e0 100644 --- a/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md @@ -6,7 +6,7 @@ Status: implemented 插件管理和原生恢复遵循[共享 Web 薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)。 -[Electron 运行时决策](2026-09-11-desktop-electron-node-runtime.zh.md)替代独立上游 Node 可执行文件的选择;本文其他决策仍然适用。 +Desktop 按[当前运行时参考](../../../../apps/desktop/README.zh.md)使用 Electron 作为 Node 运行时;本文其他决策仍然适用。 ## 问题 @@ -18,7 +18,7 @@ DeepSeek Harness 需要一个复用 Web UI 的 Electron 桌面应用。该应用 ## 决策 -交付小型 Electron 壳和固定版本 pnpm;[运行时决策](2026-09-11-desktop-electron-node-runtime.zh.md)持有可执行文件选择。[薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责 Host 启动与传输:私有 Host 运行共享 Web profile runner,Electron 加载其认证 HTTP URL,子进程 IPC 承载生命周期消息。 +交付小型 Electron 壳和固定版本 pnpm;[运行时参考](../../../../apps/desktop/README.zh.md)持有可执行文件选择。[薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责 Host 启动与传输:私有 Host 运行共享 Web profile runner,Electron 加载其认证 HTTP URL,子进程 IPC 承载生命周期消息。 Electron 拥有 `.dsh/profiles/desktop` 保留 profile。[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)负责核心资源存储、外部插件依赖、共享包链接和 profile 协调。私有 Desktop Host 保持独立于公共 CLI 包,且不会发布到 npm。 @@ -71,7 +71,7 @@ Electron 进程生命周期锁是 Desktop 的权威所有者。profile 准备和 Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-builder` 产物。该版本就是 Desktop 发布版本;不存在独立 dsh manifest、兼容范围或仅更新 dsh 的操作。前台安装会等待正在进行的后台检查,而不会把检查结果复用成安装结果。更新弹窗下载并安装 Electron 产物,然后重启进入新发布。 -[立即显示窗口决策](2026-09-09-desktop-immediate-window-and-direct-start.zh.md)负责本地加载页、直接启动 Host 和主窗口恢复。profile 协调遵循[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)。 +[立即显示窗口参考](../../../../apps/desktop/README.zh.md)负责本地加载页、直接启动 Host 和主窗口恢复。profile 协调遵循[内置运行时决策](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md)。 `DSH_DESKTOP_AUTO_UPDATE_ENV` 默认为测试部署,也可以选择生产部署,并同时决定目标专用的 generic-provider URL 与 COS 目标。发布自动化通过 `DOWNLOAD_TEST_ORIGIN` 提供测试 HTTPS origin,并通过 `DOWNLOAD_TEST_COS_BUCKET` 或 `DOWNLOAD_PROD_COS_BUCKET` 提供各部署的 bucket;可变的测试路由与 COS 存储身份不写入源码,部署基础设施变更时无需发布新代码,而公开的生产 origin 仍固定。打包只解析公开更新 URL、禁止 electron-builder 发布、从子进程环境中删除每个 COS 凭据字段,并且只有在 electron-builder 以及每个签名或公证 hook 成功后才写入完成记录。目标上传还必须提供所选 bucket,随后会先要求完成记录、根 dsh 版本、Desktop 版本、根据版本得出的频道元数据、产物名称、大小与 SHA-512 全部一致,再读取所选凭据或发送数据。它先上传不可变且带版本的更新载荷与所有独立 blockmap,最后替换 electron-builder 生成的频道元数据,并且不会删除历史对象。稳定版本使用 `latest` 元数据名称,预发布版本则使用语义化版本的第一个预发布标识符。NSIS 把 blockmap 嵌入已签名的可执行文件,macOS ZIP 则使用独立 blockmap;两者都让 electron-updater 在平台支持时只下载变化的数据块,而应用替换与本地 pnpm 包操作仍是两个独立操作。 @@ -81,7 +81,7 @@ Electron 更新只使用一个 `electron-updater` 发布流和签名 `electron-b 核心 dsh 和私有 Desktop Host 只来自签名应用的资源树。插件安装把包规格交给 pnpm,包括本地和远程来源,但不接受原始 pnpm 命令。pnpm 负责依赖解析和 profile 的 `allowBuilds` 策略;Host 加载已启用的 bundle。 -Electron 发布产物必须签名;macOS 产物必须公证。打包与上传命令从 Git 忽略的目标 `.env.windows` 或 `.env.macos` 读取发布配置,子进程通过编排器选择的环境字段接收配置。目标文件是发布字段的唯一来源,避免旧的 shell 或系统凭据覆盖本地选择;配置加载不修改父进程环境。打包在构建、下载或清理发布记录前校验该模式必需的应用 ID、更新地址、签名身份及本地文件,macOS 还要求一套完整公证凭据。单独的 `check:package` 执行同一校验而不访问 Token 或 Apple;凭据真实性仍由实际签名与公证验证。配置加载会拒绝缺失或格式错误的标识符和不完整的公证凭据,macOS 打包还会强制签名,避免证书发现过程静默选择其他已安装身份或生成未签名发布。运行时准备会验证每个内嵌 Mach-O 文件的精确 Authority 与 Team ID,以及时间戳和 hardened-runtime 标记。签名后钩子会执行 Apple 的深度严格应用验证,并要求同一叶证书 Authority 与 Team ID 完全匹配,验证通过后才继续生成产物。固定目标安装包命令使用[隔离的 App 副本并行公证](../process/2026-09-09-parallel-macos-notarization.zh.md):ZIP 包含已钉票的 App,签名 DMG 则携带覆盖其中未钉票 App 的票据。DMG 的 artifact-completion hook 要求其使用配置的身份、具备有效票据并通过 Gatekeeper。只有两条产物流都成功,命令才会移入其输出并写入发布完成记录;仅生成目录的命令仍会公证 App 并钉票。macOS 更新使用签名 ZIP,因此 DMG 不生成 blockmap;否则钉票会让已经生成的 DMG blockmap 失效。 +Electron 发布产物必须签名;macOS 产物必须公证。发布打包与上传命令从 Git 忽略的目标 `.env.windows` 或 `.env.macos` 读取发布配置,子进程通过编排器选择的环境字段接收配置。目标文件是发布字段的唯一来源,避免旧的 shell 或系统凭据覆盖本地选择;配置加载不修改父进程环境。打包在构建、下载或清理发布记录前校验该模式必需的应用 ID、更新地址、签名身份及本地文件,macOS 还要求一套完整公证凭据。单独的 `check:package` 执行同一校验而不访问 Token 或 Apple;凭据真实性仍由实际签名与公证验证。配置加载会拒绝缺失或格式错误的标识符和不完整的公证凭据,macOS 发布打包还会强制签名,避免证书发现过程静默选择其他已安装身份或生成未签名发布。运行时准备会验证每个内嵌 Mach-O 文件的精确 Authority 与 Team ID,以及时间戳和 hardened-runtime 标记。签名后钩子会执行 Apple 的深度严格应用验证,并要求同一叶证书 Authority 与 Team ID 完全匹配,验证通过后才继续生成产物。固定目标安装包命令使用[隔离的 App 副本并行公证](../process/2026-09-09-parallel-macos-notarization.zh.md):ZIP 包含已钉票的 App,签名 DMG 则携带覆盖其中未钉票 App 的票据。DMG 的 artifact-completion hook 要求其使用配置的身份、具备有效票据并通过 Gatekeeper。只有两条产物流都成功,命令才会移入其输出并写入发布完成记录;仅生成目录的命令仍会公证 App 并钉票。macOS 更新使用签名 ZIP,因此 DMG 不生成 blockmap;否则钉票会让已经生成的 DMG blockmap 失效。 [固定版本的 osx-sign 补丁](../../../../patches/@electron__osx-sign@1.3.3.patch)在两种已发布模块构建中使用 `lstat`,因此 Framework 的文件和目录别名不会触发重复签名。选定的上游版本能够跳过这些别名前,仍需保留该补丁。PAK 文件由外层 bundle 签名记录完整性;逐个签名会增加串行时间戳请求,但不会增加资源完整性保护。Desktop 保留全部语言文件,只跳过其单独签名。可执行代码仍使用 Developer ID 签名、安全时间戳和 hardened runtime。[签名器遍历回归测试](../../../../apps/desktop/tests/macos-signing-walk.spec.ts)使用真实 Framework 别名执行已安装依赖;发布验收仍要求严格应用验证、公证和启动。 @@ -89,9 +89,9 @@ Windows 发布打包通过 `/f` 向已配置且与 SafeNet 兼容的 SignTool Windows 打包调用强制设置 `ELECTRON_BUILDER_7Z_FILTER=BCJ`。内置的 7-Zip 24.09 编码器会为 ARM64 PE 文件自动选择 ARM64 过滤器,但 `nsis-resources-3.4.1` 中的 NSIS 解码器会在解压时遗漏这些条目。使用实际 NSIS 插件的原生解压验证表明,自动过滤会丢失两个 `node-pty` ARM64 二进制文件,而 BCJ 可以逐字节还原二者。使用兼容的过滤器能够保留依赖内容与运行时完整性,无需删除特定架构的文件或削弱校验。 -本地 Windows 安装测试使用显式的 `--unsigned` 打包调用,并执行相同的构建和运行时准备。它清除证书输入,将产物隔离到 `unsigned-artifacts`,并省略更新器配置和发布完成记录。即使父进程环境请求未签名模式,常规打包命令也会显式选择签名模式。这样既能在没有 EV Token 时诊断安装问题,也能防止本地测试产物通过发布上传校验。 +本地 Windows 和 macOS 安装测试使用显式的 `--unsigned` 打包调用,并执行相同的构建和运行时准备。它清除证书输入,将产物隔离到 `unsigned-artifacts`,并省略更新器配置和发布完成记录。macOS 本地构建忽略 dotenv 文件和继承的发布配置,使用标准应用 ID,并省略强制更新策略,使本地测试无需部署配置。其 App 使用无需证书的 ad-hoc 签名,DMG 不签名也不公证。Windows 本地构建保留平台配置要求。即使父进程环境请求未签名模式,常规打包命令也会显式选择签名模式。这样既能在没有发布凭据时诊断安装问题,也能防止本地测试产物通过发布上传校验。 -Windows 应用替换遵循[目录安装决策](2026-09-11-windows-directory-installation.zh.md):使用能返回失败状态的命令行工具解压到目标旁边,再在同卷内改名替换完整目录。安装器在暂存期间保留旧目录,正式替换失败时恢复旧目录。注册信息、快捷方式和签名卸载器仍由 electron-builder 持有。 +Windows 应用替换遵循[目录安装参考](../../../../apps/desktop/README.zh.md):使用能返回失败状态的命令行工具解压到目标旁边,再在同卷内改名替换完整目录。安装器在暂存期间保留旧目录,正式替换失败时恢复旧目录。注册信息、快捷方式和签名卸载器仍由 electron-builder 持有。 打包应用会忽略开发资源和项目环境变量覆盖。只有未打包的 Electron 进程可以替换 pnpm 入口、dsh 资源 或活跃项目。 diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml index 231ee0bf64..207508cad2 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.i18n.yaml @@ -21,5 +21,5 @@ en: 1f62d5d938144666 zh: dec0f30228c5b7a7 /agent-note-adjacent-agents-share-one-steer-send-message-operation/consequences: - en: 6a01449bef67c9ab - zh: 9ed7184a4d48038c + en: d55de74742c3af4b + zh: d894f9a33c8e4691 diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md index 47d9bd05c2..43048f9a8c 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md @@ -79,4 +79,4 @@ The standalone `@deepseek-ai/dsh-tool-subagent-report` package, `report` schema, - The initial task carries JSON-encoded dynamic parent addressing after a fork prefix, while the request-head system prompt and tool ordering remain reusable. - Human prompts, settlement notices, QueueDock, and the base bundle's one-shot fork policy remain separate decisions. -This decision consolidates and removes the fully superseded report-tool and child-report-obligation records. It supersedes the `followup` naming choice in [Intent-named subagent continuation operations](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md) and retains the accepted-order guarantee in [Child Agent messages precede their settlement notices](../bug-fix/2026-08-17-subagent-message-settlement-ordering.md). +This decision consolidates and removes the fully superseded report-tool and child-report-obligation records. It supersedes the `followup` naming choice in [Intent-named subagent continuation operations](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md) and retains the accepted-order guarantee in [Child Agent messages precede their settlement notices](../../../../packages/subagent/subagent/README.md). diff --git a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md index 1fd0fc39b9..6d1e2d4923 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md @@ -79,4 +79,4 @@ parent 与 child 以相同注册表顺序继承相同定义。标准定义携带 - 初始任务在 fork 前缀之后携带经过 JSON 编码的动态 parent 地址,而请求头系统提示词与工具顺序保持可复用。 - 人类提示、结算通知、QueueDock 与 base bundle 的一次性 fork 策略仍是独立决策。 -本决策合并并删除了已完全被取代的 report 工具与 child report 义务记录。它取代[按意图命名的 subagent 继续执行操作](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md)中的 `followup` 命名选择,并保留[Child Agent 消息先于其结算通知](../bug-fix/2026-08-17-subagent-message-settlement-ordering.zh.md)中的接受顺序保证。 +本决策合并并删除了已完全被取代的 report 工具与 child report 义务记录。它取代[按意图命名的 subagent 继续执行操作](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md)中的 `followup` 命名选择,并保留[Child Agent 消息先于其结算通知](../../../../packages/subagent/subagent/README.zh.md)中的接受顺序保证。 diff --git a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.i18n.yaml index 29242480fe..47c7d96304 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.i18n.yaml @@ -18,5 +18,5 @@ en: 5917a573cbdc5fa4 zh: 3c3769070539a0a9 /agent-note-handle-based-session-persistence/related: - en: 07a1b7d5a0f9b0f7 - zh: 9bfa134b62ff3df7 + en: 6b160ed89d2f91a7 + zh: 23ee33d03847d0d0 diff --git a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md index e2f07856b7..a336c45550 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md +++ b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md @@ -36,7 +36,7 @@ Resume, fork, subagent, ACP, webhook, and SDK sessions all persist through one e ## Related -- [Session persistence as an abstract service](2026-06-14-session-persistence.md) — the seam this reshapes; its interface list reflects the handle API. +- [Session persistence as an abstract service](../../../../packages/session/session-persistence/README.md) — the seam this reshapes; its interface list reflects the handle API. - [Persistence export() and pre-release trims](../../archived/simplification/2026-08-27-persistence-export-and-pre-release-trims.md) — the preparatory removals, including the retired cold blank probe. - [Retain ignorable external session events](2026-08-30-retain-ignorable-external-session-events.md) — the read-side refusal contract, now shared through `storage-contract` helpers. - [Bounded session-persistence write batching](../../archived/architecture/2026-08-08-bounded-session-persistence-write-batching.md) — the batching semantics the routed write path preserves as internal scheduling policy. diff --git a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md index 472a102488..4dfe30be5c 100644 --- a/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md @@ -36,7 +36,7 @@ Status: implemented ## 相关 -- [作为抽象服务的会话持久化](2026-06-14-session-persistence.zh.md)——本 Note 重塑的 seam;其接口列表已反映句柄 API。 +- [作为抽象服务的会话持久化](../../../../packages/session/session-persistence/README.zh.md)——本 Note 重塑的 seam;其接口列表已反映句柄 API。 - [持久化 export() 与预发布读取路径精简](../../archived/simplification/2026-08-27-persistence-export-and-pre-release-trims.md)——预备性的移除,包括已退役的冷空白探测。 - [保留可忽略的外部会话事件](2026-08-30-retain-ignorable-external-session-events.zh.md)——读取侧的拒绝约定,现经由 `storage-contract` 辅助函数共享。 - [为会话持久化写入批处理设定上界](../../archived/architecture/2026-08-08-bounded-session-persistence-write-batching.md)——被路由写路径作为内部调度策略保留的批处理语义。 diff --git a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml deleted file mode 100644 index 76b43c4348..0000000000 --- a/.agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-28-subprocess-native-containment.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-28-subprocess-native-containment.md -/agent-note-native-owners-contain-escaped-subprocess-descendants: - en: 161c05f390ea3f51 - zh: 1c1c83ad4f9780dd -/agent-note-native-owners-contain-escaped-subprocess-descendants/problem: - en: a88b160c6c1f57e5 - zh: 0b1a7cb68e9aaabd -/agent-note-native-owners-contain-escaped-subprocess-descendants/decision: - en: 0469a956a95bccdf - zh: 2c5ea38a2841e1ae -/agent-note-native-owners-contain-escaped-subprocess-descendants/decision/linux-scope-and-one-shot-bootstrap: - en: 582e6f5f5f7cc940 - zh: b47cee599d22aaa2 -/agent-note-native-owners-contain-escaped-subprocess-descendants/decision/windows-runner-and-job: - en: a6401ee73bb173b7 - zh: 45b9cc7fea6f7921 -/agent-note-native-owners-contain-escaped-subprocess-descendants/decision/private-dispatch-and-protocol: - en: bf25e4b385bf9299 - zh: 6ed1062fbe49f3cf -/agent-note-native-owners-contain-escaped-subprocess-descendants/decision/fallback-and-cleanup: - en: 19da14bf2234744f - zh: 8f723b1c9c8a3a58 -/agent-note-native-owners-contain-escaped-subprocess-descendants/existing-decisions-and-supersession: - en: 8f24a2276aff4bd2 - zh: a3a36dd28f3ab0e0 -/agent-note-native-owners-contain-escaped-subprocess-descendants/verification: - en: 900e10a3f9cd3b67 - zh: efc5d9f09a0e8e5a -/agent-note-native-owners-contain-escaped-subprocess-descendants/alternatives-considered: - en: 12bbc028fbb14667 - zh: 9fea4359308337bd -/agent-note-native-owners-contain-escaped-subprocess-descendants/consequences: - en: 0151145ee6281267 - zh: 5bd2e53ad58107e9 diff --git a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.i18n.yaml index 63a2bfdc06..232b137972 100644 --- a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.i18n.yaml @@ -9,11 +9,11 @@ en: d2df1eb3602af45d zh: fd162b1c214f1494 /agent-note-retain-ignorable-session-events-for-external-plugins/decision: - en: a4162e65cf6768f3 - zh: bb700d9630642e7e + en: c78e92c0cc5a7eb2 + zh: 62b29ccc5cd142f2 /agent-note-retain-ignorable-session-events-for-external-plugins/alternatives-considered: - en: 30ec097e3ba4e951 - zh: 3e90fc035d04b09b + en: c91cdee1dda42176 + zh: 870dfbda591abb76 /agent-note-retain-ignorable-session-events-for-external-plugins/consequences: - en: 5f65bfb81f172d49 - zh: 069d41d6bda9f8af + en: b14df3e7f7489748 + zh: ac227743cf5c11f7 diff --git a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md index e085e429a6..8c01a5aae2 100644 --- a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md +++ b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md @@ -16,6 +16,10 @@ The canonical `SessionEvent` envelope retains `ignorable?: true`, and every repr The field is removable only after a replacement supports the current third-party plugin across event production, persistence, reload, and transport, with an explicit cutover for sessions already containing the marker. The [session log versioning decision](2026-08-10-session-log-version-mechanism.md) continues to own the default-required safety rule and format-version policy. +`appendPluginRecord()` in `@deepseek-ai/dsh-session` is the one first-party writer of the marker. It appends ignorable records whose type is in the `plugin:` namespace, which the V3-to-V4 edge already assigns to unknown ignorable V3 events, and only production source under `packages/experimental/` may call it; the `verify-plugin-record-callers` gate enforces that, and the persistence catalog generator rejects a `SessionEventMap` member in the namespace. Experimental packages keep state there that their owners can lose: a format migration retains records on a best-effort basis, so a release package declares its events instead. + +Experimental packages declare record names and payload types in the separate `PluginRecordMap`. The map types writes and supplies the dedicated [experimental persistence catalog](../../../../docs/experimental-persistence-catalog.md), making plugin-owned persistence discoverable from current source without expanding its payload schemas or recording released type history. It does not add names to `KNOWN_SESSION_EVENT_TYPES` or change stored-event admission. The reader returns unknown payloads for owner validation, including records whose declarations have changed or disappeared. + Historical format migration is deliberately stricter in the alpha implementation. The v0-to-v1 edge refuses every unknown v0 type, including an ignorable one, because an opaque payload may contain references that a format edge cannot validate. The [alpha historical-event decision](2026-08-31-alpha-historical-unknown-event-refusal.md) owns that bounded exception; equal-version append and reload continue to follow this note. ## Alternatives considered @@ -26,8 +30,16 @@ Historical format migration is deliberately stricter in the alpha implementation **Treat every repository-external event as ignorable.** Rejected because a reader cannot infer that an unknown durable event is informational. An external event may change later reconstruction or plugin-owned state. +**Declare a `SessionEventMap` member for each experimental package's state.** Rejected because each member is required-on-read and enters the released-schema inventory and type history: a build without the experimental package refuses the Session, and removing the package leaves a released type behind. + +**Keep no catalog of plugin records.** Rejected because maintainers need to find in-repository persistence writers. The dedicated experimental catalog provides that inventory from current-source declarations while their payloads retain best-effort compatibility; a full schema history would impose the released-event obligations that these records deliberately avoid. + +**Let any package call the record writer.** Rejected because records survive a format migration only on a best-effort basis, and a release package must not keep state that an upgrade can drop. + +**Restrict the writer with a runtime capability.** Not adopted because the writer runs in its caller's process, where a runtime check cannot tell experimental code from release code; the repository gate checks every reference instead. + **Register mounted plugin event names as known.** Not adopted as the removal mechanism because event-name registration alone does not classify whether absence is safe, and acceptance would depend on the reader's current composition rather than the stored record. ## Consequences -Third-party informational events can remain reloadable when their stored records carry the explicit marker, while unknown required events still fail loudly. The field remains part of the public event envelope, JSONL representation, transport types, generated references, and their tests until a replacement satisfies the cutover condition. +Third-party informational events can remain reloadable when their stored records carry the explicit marker, while unknown required events still fail loudly. The field remains part of the public event envelope, JSONL representation, transport types, generated references, and their tests until a replacement satisfies the cutover condition, which now also covers plugin records written by experimental packages. diff --git a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md index 1fe4f81441..63fefff2c3 100644 --- a/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md @@ -16,6 +16,10 @@ Status: implemented 只有替代机制在事件生产、持久化、重新加载与传输中都支持当前第三方插件,并为已包含该标记的会话提供显式切换方案后,才能删除此字段。[Session log 版本决策](2026-08-10-session-log-version-mechanism.zh.md)继续定义默认读取必需的安全规则与格式版本策略。 +`@deepseek-ai/dsh-session` 中的 `appendPluginRecord()` 是该标记唯一的第一方写入方。它追加类型位于 `plugin:` 命名空间的 ignorable 记录(V3-to-V4 迁移边已把未知的 ignorable V3 事件放入该命名空间),且只有 `packages/experimental/` 下的生产源码可以调用它;`verify-plugin-record-callers` 门禁强制这一点,持久化目录生成器也拒绝该命名空间中的 `SessionEventMap` 成员。实验性包在这里保存其所有者可以丢失的状态:格式迁移以尽力而为的方式保留记录,因此正式发布的包改为声明自己的事件。 + +实验性包在独立的 `PluginRecordMap` 中声明记录名称与 payload 类型。该映射约束写入类型,并生成独立的[实验性持久化目录](../../../../docs/experimental-persistence-catalog.zh.md),让维护者能够从当前源码找到插件自有的持久化数据,而无须展开其 payload schema 或记录已发布类型历史。它不向 `KNOWN_SESSION_EVENT_TYPES` 添加名称,也不改变已存事件的准入。读取方返回未知类型的 payload,由所属包校验,其中也包括声明已变更或消失的记录。 + Alpha 实现中的历史格式迁移有意更严格。v0-to-v1 迁移边会拒绝每个未知 v0 类型,包括 ignorable 类型,因为不透明 payload 可能包含格式迁移边无法校验的引用。[Alpha 历史事件决策](2026-08-31-alpha-historical-unknown-event-refusal.zh.md)定义该有限例外;同版本 append 与 reload 继续遵循本记录。 ## 曾考虑的替代方案 @@ -26,8 +30,16 @@ Alpha 实现中的历史格式迁移有意更严格。v0-to-v1 迁移边会拒 **把所有仓库外事件都视为可忽略。** 不予采用,因为读取器无法推断未知持久事件是否属于信息性事件。外部事件可能改变后续重建或插件自有状态。 +**为每个实验性包的状态声明 `SessionEventMap` 成员。** 不予采用,因为每个成员都是读取必需的,并会进入已发布 schema 清单及类型历史:没有该实验性包的构建会拒绝该会话,移除该包后还会留下一个已发布的类型。 + +**不维护插件记录目录。** 不予采用,因为维护者需要找到仓库内的持久化写入方。独立的实验性目录根据当前源码声明提供这份清单,而 payload 仍然只有尽力而为的兼容性;完整的 schema 历史会施加这些记录刻意不承担的已发布事件义务。 + +**允许任何包调用记录写入方。** 不予采用,因为记录只能以尽力而为的方式经受格式迁移,而正式发布的包不得保存升级可能丢弃的状态。 + +**用运行时能力限制写入方。** 不予采用,因为写入方运行在调用方的进程中,运行时检查无法区分实验性代码与正式发布的代码;仓库门禁改为检查每一处引用。 + **把已挂载插件的事件名称注册为已知。** 不作为删除机制采用,因为只注册事件名称无法判定缺失该事件是否安全,而且接受结果会依赖读取器的当前组合,而不是已存记录。 ## 影响 -第三方信息性事件的已存记录带有显式标记时可以继续重新加载,未知必需事件则仍会明确失败。在替代机制满足切换条件前,该字段继续属于公开事件信封、JSONL 表示、传输类型、生成引用及其测试。 +第三方信息性事件的已存记录带有显式标记时可以继续重新加载,未知必需事件则仍会明确失败。在替代机制满足切换条件前,该字段继续属于公开事件信封、JSONL 表示、传输类型、生成引用及其测试;该切换条件现在也涵盖实验性包写入的插件记录。 diff --git a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.i18n.yaml index c28606ddd1..ddf55f9063 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.i18n.yaml @@ -6,11 +6,11 @@ en: f5e76516df025fc5 zh: 462fe05b46e6c1ea /agent-note-explicit-agent-identity-at-runtime-boundaries/problem: - en: 0b737a098e4e19ce - zh: 97b4254c4533114f + en: 98b49b900b8d2f84 + zh: 557faac2976592be /agent-note-explicit-agent-identity-at-runtime-boundaries/decision: - en: dfee8c7c574e0001 - zh: 3894ffa7beba4348 + en: 1c628cf9c743c639 + zh: d969b2475fe4a155 /agent-note-explicit-agent-identity-at-runtime-boundaries/verification: en: 85130faaa171036b zh: 06e5e9fab32dbfb4 diff --git a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.md b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.md index f52b8ec116..16cd668f65 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.md +++ b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.md @@ -10,7 +10,7 @@ An Agent's Cordis Context owns registrations and their cleanup. Agent identity i The reverse association also required compensating mechanisms after type erasure. Host Remote forwarding inspected a routed subject for its Context, creation inferred runtime parentage from the caller Context, and adapters maintained reverse identity scans. These mechanisms duplicated identity already present in typed requests and obscured which caller owned an Agent at runtime. -Without an explicit owner, `SubagentContinuationManager` creates and resumes children through its private plugin Context, so Context-based inference classifies every continuable child as a runtime root even though the manager holds its exact parent. Root-only consumers could then attach scheduling tools, grant direct-human goal authority, or route user questions as if the child were top-level. +Without an explicit owner, `SubagentManager` creates and resumes children through its private plugin Context, so Context-based inference classifies every continuable child as a runtime root even though the manager holds its exact parent. Root-only consumers could then attach scheduling tools, grant direct-human goal authority, or route user questions as if the child were top-level. ## Decision @@ -18,9 +18,9 @@ Runtime interfaces carry Agent identity at the point that owns it. `AgentSetup` Scope-aware registries continue to use the opaque scope key only for registration membership. Tool-subagent does not classify that key or resolve an Agent from Context. A direct `AgentSetup` passes the unpublished Session explicitly and installs through the supplied Context before publication. For a settings-backed standing preset, the event payload supplies the Agent, its Session supplies the policy target, and its Context owns the registrations. -`SubagentContinuationManager` puts the exact parent in both fresh-creation and cold-resume options. A live continuable child is therefore excluded from `AgentRegistry.roots()` and satisfies `isOwnedBy(child.id, parent)`. Durable `parentSession` metadata does not substitute for this relation: a fork or resumed Session may be a runtime root when no live Agent owns it. +`SubagentManager` puts the exact parent in both fresh-creation and cold-resume options. A live continuable child is therefore excluded from `AgentRegistry.roots()` and satisfies `isOwnedBy(child.id, parent)`. Durable `parentSession` metadata does not substitute for this relation: a fork or resumed Session may be a runtime root when no live Agent owns it. -The [Agent registration-scope decision](2026-07-08-agent-scope-contexts.md), its [runtime design](2026-07-12-agent-scope-runtime-design.md), and the [initiator-scope decision](2026-07-15-agent-initiator-scope.md) retain their independent registration, lifecycle, and private-chain rationale. This decision supersedes only the reverse Context association and implicit runtime-owner derivation described there. +The [scope reference](../../../../packages/core/scope/README.md) and [Agent loop reference](../../../../packages/core/agent-loop/README.md) document registration and lifecycle behavior. The [initiator-scope decision](2026-07-15-agent-initiator-scope.md) owns private-chain rationale; explicit runtime ownership does not change causal initiation or registration lifetime. ## Verification diff --git a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.zh.md b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.zh.md index 6b6fd2f2ea..3d4131af01 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-31-explicit-agent-runtime-identity.zh.md @@ -10,7 +10,7 @@ Agent 的 Cordis Context 拥有注册及其清理。Agent 身份则为某项操 类型信息被擦除后,这项反向关联还需要补偿机制。Host Remote 转发会从已路由主体检查其 Context,创建流程会从调用方 Context 推断运行时父级,适配器则维护反向身份扫描。这些机制重复类型化请求中已有的身份,也掩盖了哪个调用方在运行时拥有 Agent。 -若没有显式所属方,`SubagentContinuationManager` 会通过私有插件 Context 创建和恢复子级,因此基于 Context 的推断会把每个可续跑子级归类为 runtime root,尽管管理器持有其确切父级。仅限根级的消费方随后可能附加调度工具、授予直接人类输入对应的 Goal 权限,或像处理顶层 Agent 一样路由用户问题。 +若没有显式所属方,`SubagentManager` 会通过私有插件 Context 创建和恢复子级,因此基于 Context 的推断会把每个可续跑子级归类为 runtime root,尽管管理器持有其确切父级。仅限根级的消费方随后可能附加调度工具、授予直接人类输入对应的 Goal 权限,或像处理顶层 Agent 一样路由用户问题。 ## 决策 @@ -18,9 +18,9 @@ Agent 的 Cordis Context 拥有注册及其清理。Agent 身份则为某项操 感知作用域的注册表继续仅使用不透明作用域键判断注册成员关系。tool-subagent 不会分类该键,也不会从 Context 解析 Agent。直接 `AgentSetup` 显式传入尚未发布的 Session,并在发布前通过所给 Context 完成安装。对于由设置控制的常驻 preset,事件 payload 提供 Agent,其 Session 提供策略目标,其 Context 拥有注册项。 -`SubagentContinuationManager` 会把确切父级放进全新创建与冷恢复的 options。因此,存活的可续跑子级不会出现在 `AgentRegistry.roots()` 中,并且满足 `isOwnedBy(child.id, parent)`。持久化 `parentSession` 元数据不能代替这项关系:没有存活 Agent 拥有 fork 或已恢复会话时,它仍可成为 runtime root。 +`SubagentManager` 会把确切父级放进全新创建与冷恢复的 options。因此,存活的可续跑子级不会出现在 `AgentRegistry.roots()` 中,并且满足 `isOwnedBy(child.id, parent)`。持久化 `parentSession` 元数据不能代替这项关系:没有存活 Agent 拥有 fork 或已恢复会话时,它仍可成为 runtime root。 -[Agent 注册作用域决策](2026-07-08-agent-scope-contexts.zh.md)、其[运行时设计](2026-07-12-agent-scope-runtime-design.zh.md)和[发起方作用域决策](2026-07-15-agent-initiator-scope.zh.md)继续拥有各自独立的注册、生命周期及私有调用链理由。本决策只取代其中描述的反向 Context 关联和隐式运行时所属方推导。 +[作用域参考](../../../../packages/core/scope/README.zh.md)与 [Agent loop 参考](../../../../packages/core/agent-loop/README.zh.md)说明注册和生命周期行为。[发起方作用域决策](2026-07-15-agent-initiator-scope.zh.md)负责私有调用链理由;显式运行时归属不改变因果发起关系或注册生命周期。 ## 验证 diff --git a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.i18n.yaml index 38dbc1ec40..e8bd6ad3f9 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.i18n.yaml @@ -36,8 +36,8 @@ en: 65968c18c3d1b7e9 zh: 2e43d7ce1391d554 /agent-note-released-session-formats-migrate-through-stateful-streaming-stages/problem-to-solution-mapping/parent-catalog-prerequisites: - en: 81b40660b49dbfb0 - zh: 67cfa3f43b6eb53e + en: 09f0016cb989fb5a + zh: ae0359c805e226e4 /agent-note-released-session-formats-migrate-through-stateful-streaming-stages/problem-to-solution-mapping/catalog-scan-measurements: en: 6b7a79fb81e58bac zh: 51fc00eaf3a0c3dc diff --git a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md index 00672af920..007620d641 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md +++ b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md @@ -133,7 +133,7 @@ Existing write handles retain the process-local claim and kernel-backed cross-pr ### Parent catalog prerequisites -Runtime child-failure handling in this paragraph is partially superseded by [session-local catalog preparation](../bug-fix/2026-09-19-session-local-subagent-migration.md): opening the parent still completes its catalog from direct-child reads, but unreadable children do not block healthy parent history. V3→V4 completes parent child discovery from retained direct-child headers and own descriptors. Storage supplies compact descriptor evidence to the catalog assembly, which binds it into the V3→V4 stage factory; the Stage waits for the parent's final inherited cut before validating catalog payloads and deriving available own discovery facts. Nested seed markers discard inherited catalog candidates without interpreting their payloads. An existing catalog entry survives an unavailable descriptor, including a child that failed before its first step. Descriptor v1 denotes continuable mode; v2/v3 explicitly record mode. Available unambiguous discovery fields must agree with the parent; new complete entries require one supported descriptor. Missing, unsupported, or multiple descriptors preserve the logs and retain unknown-mode membership without inventing discovery fields, as recorded in [incomplete child evidence](../bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md). JSONL rechecks related membership and source revisions when preparing, reusing, and publishing. Unreadable or unsupported headers are excluded from discovery; failures reading a recognized child preserve its header identity as unknown-mode membership. Source drift invalidates preparation; read open retries once automatically, while write open refuses publication. Diagnostics retain the offending child path and distinguish unsupported migration evidence from malformed child data. The collector reports child decoding and descriptor failures as warnings with the child path; cancellation and source-consistency checks still stop preparation. The V0–V3-only catalog avoids recursive child migration. Current V4 reads skip that scan but apply the same own-catalog field, uniqueness, and current delivery ownership checks as strict restoration. See the [format specification](../../../../packages/session/session-format-v3-to-v4/README.md). +The current [session-local catalog preparation](../../../../packages/session/session-persistence-jsonl/README.md) isolates child failures: opening the parent still completes its catalog from direct-child reads, but unreadable children do not block healthy parent history. V3→V4 completes parent child discovery from retained direct-child headers and own descriptors. Storage supplies compact descriptor evidence to the catalog assembly, which binds it into the V3→V4 stage factory; the Stage waits for the parent's final inherited cut before validating catalog payloads and deriving available own discovery facts. Nested seed markers discard inherited catalog candidates without interpreting their payloads. An existing catalog entry survives an unavailable descriptor, including a child that failed before its first step. Descriptor v1 denotes continuable mode; v2/v3 explicitly record mode. Available unambiguous discovery fields must agree with the parent; new complete entries require one supported descriptor. Missing, unsupported, or multiple descriptors preserve the logs and retain unknown-mode membership without inventing discovery fields, as recorded in [incomplete child evidence](../../../../packages/session/session-format-v3-to-v4/README.md). JSONL rechecks related membership and source revisions when preparing, reusing, and publishing. Unreadable or unsupported headers are excluded from discovery; failures reading a recognized child preserve its header identity as unknown-mode membership. Source drift invalidates preparation; read open retries once automatically, while write open refuses publication. Diagnostics retain the offending child path and distinguish unsupported migration evidence from malformed child data. The collector reports child decoding and descriptor failures as warnings with the child path; cancellation and source-consistency checks still stop preparation. The V0–V3-only catalog avoids recursive child migration. Current V4 reads skip that scan but apply the same own-catalog field, uniqueness, and current delivery ownership checks as strict restoration. See the [format specification](../../../../packages/session/session-format-v3-to-v4/README.md). Raw parent/child migration tests retain 24 historical creation-time conflicts as rejection evidence. Detached controls align only the child creation time and prove event-preserving restoration, including a published child without a descriptor. Headless/ACP and SDK snapshot adapters validate live parent/child clocks before normalization and preserve their equality during refresh; committed predecessor generations remain unchanged. diff --git a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md index 9404d8f454..2984219dd9 100644 --- a/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md @@ -133,7 +133,7 @@ POSIX publication 使用 hard-link creation 加目录 sync;Windows 使用 no-o ### 父目录前置事实 -本段的运行时子会话错误处理部分由[逐会话目录准备](../bug-fix/2026-09-19-session-local-subagent-migration.zh.md)取代:打开父会话仍通过读取直属子会话补齐目录,但不可读子会话不再阻断健康父历史。V3→V4 使用保留的直属子 Session header 和自身 descriptor 补齐父 Session 的子代理发现记录。存储将紧凑的 descriptor 证据提供给 catalog 组装处,由其绑定到 V3→V4 stage 工厂;Stage 等待父 Session 的最终继承截点确定后,才校验目录 payload 并导出可获得的自身发现事实。嵌套的 seed 标记丢弃继承目录候选,不解释其 payload。已有目录记录允许 descriptor 不可用,包括在首次 step 之前失败的子 Session。Descriptor v1 表示 continuable 模式;v2/v3 显式记录模式。可获得且明确的发现字段必须与父目录一致;新增完整记录要求一个受支持的 descriptor。Descriptor 缺失、版本不受支持或存在多个时,保留日志与未知模式成员关系,不编造发现字段,理由见[不完整子目录证据](../bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.zh.md)。JSONL 在准备、复用和发布时重新检查关联成员与来源修订。不可读或不支持的 header 不参与发现;读取已识别子会话失败时,其 header 身份保留为未知模式成员关系。来源变化使准备缓存失效;只读打开自动重试一次,写打开则拒绝发布。诊断保留出错子日志的路径,并区分不支持的迁移证据与畸形子数据。收集器将子解码和 descriptor 失败报告为带子路径的警告;取消与来源一致性检查仍中止准备。仅支持 V0–V3 的目录避免递归迁移子 Session。当前 V4 读取跳过该扫描,但会执行与严格恢复相同的自身目录字段、唯一性及当前投递归属检查。见[格式规范](../../../../packages/session/session-format-v3-to-v4/README.zh.md)。 +当前[逐会话目录准备](../../../../packages/session/session-persistence-jsonl/README.zh.md)隔离子会话失败:打开父会话仍通过读取直属子会话补齐目录,但不可读子会话不再阻断健康父历史。V3→V4 使用保留的直属子 Session header 和自身 descriptor 补齐父 Session 的子代理发现记录。存储将紧凑的 descriptor 证据提供给 catalog 组装处,由其绑定到 V3→V4 stage 工厂;Stage 等待父 Session 的最终继承截点确定后,才校验目录 payload 并导出可获得的自身发现事实。嵌套的 seed 标记丢弃继承目录候选,不解释其 payload。已有目录记录允许 descriptor 不可用,包括在首次 step 之前失败的子 Session。Descriptor v1 表示 continuable 模式;v2/v3 显式记录模式。可获得且明确的发现字段必须与父目录一致;新增完整记录要求一个受支持的 descriptor。Descriptor 缺失、版本不受支持或存在多个时,保留日志与未知模式成员关系,不编造发现字段,理由见[不完整子目录证据](../../../../packages/session/session-format-v3-to-v4/README.zh.md)。JSONL 在准备、复用和发布时重新检查关联成员与来源修订。不可读或不支持的 header 不参与发现;读取已识别子会话失败时,其 header 身份保留为未知模式成员关系。来源变化使准备缓存失效;只读打开自动重试一次,写打开则拒绝发布。诊断保留出错子日志的路径,并区分不支持的迁移证据与畸形子数据。收集器将子解码和 descriptor 失败报告为带子路径的警告;取消与来源一致性检查仍中止准备。仅支持 V0–V3 的目录避免递归迁移子 Session。当前 V4 读取跳过该扫描,但会执行与严格恢复相同的自身目录字段、唯一性及当前投递归属检查。见[格式规范](../../../../packages/session/session-format-v3-to-v4/README.zh.md)。 原始父子迁移测试保留 24 个历史创建时间冲突作为拒绝证据。独立的内存对照只对齐子创建时间,证明恢复保留事件,包括已发布但没有 descriptor 的子 Session。Headless/ACP 与 SDK 快照适配器在规范化之前校验实时父子时钟,并在刷新时保持两者相等;已提交的前代文件保持不变。 diff --git a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml deleted file mode 100644 index dcd4dd469f..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-01-jobs-absorb-activity-record.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-01-jobs-absorb-activity-record.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# 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-jobs-absorb-activity-record.md -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed: - en: f407477a58d20fb6 - zh: b65bc22b9e1e21c3 -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed/problem: - en: a4008f3bcd1e3bd0 - zh: fa8432ddc47003c4 -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed/decision: - en: c7ada6a40d92daf4 - zh: bd16a02f295c4ae2 -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed/alternatives-considered: - en: 00067552f2fcb740 - zh: aa23082899db1a1a -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed/review-corrections: - en: 93db5342c848587a - zh: d4e67a47d573eb39 -/agent-note-the-job-registry-absorbs-the-observation-record-the-standalone-activity-seam-is-removed/consequences: - en: c1df52910a1ee554 - zh: 50fd15ed06f07f76 diff --git a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.i18n.yaml index dbb198ca69..33ebd75e3d 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.i18n.yaml @@ -9,11 +9,11 @@ en: 5a991fec31578cea zh: 13b75aa36eb22907 /agent-note-parent-owned-subagent-catalog-events/decision: - en: 198354f20b5d0a42 - zh: 7a6b4392117a60b5 + en: 4319c502038ab34d + zh: b11825ff9d744822 /agent-note-parent-owned-subagent-catalog-events/alternatives-considered: en: 7a3d4b9f69c243d8 zh: 82b03d16ccc94dd2 /agent-note-parent-owned-subagent-catalog-events/consequences: - en: d582273d5102d617 - zh: 7f05590581b1df88 + en: 6ce20d3b92041805 + zh: 53e35b50c5e8d62a diff --git a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md index 27582937d8..2fac858789 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md +++ b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md @@ -12,15 +12,17 @@ The child descriptor remains necessary for recovery and composition, but it cann ## Decision -The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Each event is one successful creation fact containing `childId`, `childCreatedAt`, mode, and the mode-discriminated label. Remote one-shot runs without a local Session remain outside this catalog. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog. +The shared activation entry point, external execution ownership, and caller-versus-parent result delivery are governed by [Unified subagent activations](../simplification/2026-09-17-unified-subagent-activations.md). This record retains the independent rationale described below. -Creation publishes only successful facts. A one-shot run appends the catalog event after its provider returns a local child and before the run reaches its caller. A continuable run admits the initial prompt, appends the catalog event, then returns the child id. If admission or catalog append fails, creation fails and releases the activation; there is no compensating catalog event or rollback protocol. +The parent Session's required `subagent/catalog` events are the persistent authority for direct-child discovery. Every local child enters the catalog independently of its result recipient; external children enter only with parent delivery. Workflow membership records describe execution progress and do not replace local Session discovery. Each event contains `childId`, `childCreatedAt`, mode, and the mode-discriminated label. Local children use `continuable`; external executions use `external` and cannot be opened as local Sessions. Historical `one-shot` and `unknown` entries remain readable. Invalid own facts, including unsupported payload versions, reject projection restoration because silently dropping a required fact would return an incomplete catalog. + +Creation publishes only successful facts. A local activation admits the initial prompt before appending its catalog event. An external activation appends its event after provider startup and before returning the receipt. Failed admission or publication releases the unpublished execution; there is no compensating catalog event or rollback protocol. The child header and `subagent/descriptor` remain authoritative for recovery and composition. An Activation and the exact parent relationship remain authoritative for authorization and delivery. Mode and label are snapshotted once and the same detached values reach the parent catalog fact and child descriptor. The registered `subagentCatalog` projection materializes the parent facts. It delegates storage, append, iteration, and checkpoint validation to [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.md), which stores facts in a persistent stack of 64-entry chunks, so an append copies at most the head chunk in bounded O(1) work. Materialization visits chunks from oldest to newest and preserves parent catalog event order in O(D) time for D facts. Concurrent creation is ordered by successful catalog append, independent of child timestamps and ids. A projection checkpoint clones the state once in O(D); projection-cache writes remain asynchronous and use the existing mandatory creation, turn-end, and disposal points. -The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 2 stores generic chunk values, so the projection registry rebuilds incompatible caches from Session events. Session event payloads and public catalog rows retain their formats. +The utility owns chunk layout and its shared capacity constant; the catalog owns event validation, fork filtering, and row conversion. Catalog projection state version 6 accepts external membership, so the projection registry rebuilds incompatible caches from Session events. Payload v0 and v1 remain unchanged; v2 records external executions. Fork isolation uses the exact `Session.inheritedEventCount` supplied to projection initialization. The fold ignores `subagent/catalog` events below that offset. The state stores the inherited offset but not each event seq because acceptance is decided during folding. @@ -44,6 +46,6 @@ Current-writer snapshot expectations include catalog facts even when replay inpu ## Consequences -Session observations and client snapshots expose the direct-child list through `projections.values.subagentCatalog`. The projection change feed publishes a complete list when catalog state changes. Each view costs O(D), so D creations can incur O(D²) cumulative view work; this follows the existing projection mechanism. `listChildren()` reads the parent projection through one live-preferred Session observation; descendant listing recursively calls the same reader in catalog order. Reachable catalog facts supply identity and membership without a global Session scan or a separate child-identity cache path. Unreadable branches remain diagnostics; uncataloged Sessions are outside discovery. [Web projection consumption](../simplification/2026-09-08-web-subagent-catalog-projections.md) owns browser loading and synchronization. +Session observations and client snapshots expose the direct-child list through `projections.values.subagentCatalog`. The projection change feed publishes a complete list when catalog state changes. Each view costs O(D), so D creations can incur O(D²) cumulative view work; this follows the existing projection mechanism. `listChildren()` reads the parent projection through one live-preferred Session observation; descendant listing recursively calls the same reader in catalog order. Reachable catalog facts supply identity and membership without a global Session scan or a separate child-identity cache path. Unreadable branches remain diagnostics; uncataloged Sessions are outside discovery. [Web projection consumption](../../../../packages/client/ui-subagent/README.md) owns browser loading and synchronization. -[Creation metadata exclusion](../simplification/2026-09-16-subagent-catalog-membership-only.md) records why model configuration is absent from catalog facts. +[Creation metadata exclusion](../../../../packages/subagent/subagent/README.md) records why model configuration is absent from catalog facts. diff --git a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md index 982dab234e..10088c52a7 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md @@ -12,15 +12,17 @@ child descriptor 对恢复与 composition 仍然必要,但它不能作为 disc ## 决策 -parent Session 的 required `subagent/catalog` 事件是直接 child discovery 的持久化权威。每个事件都是一条成功创建事实,包含 `childId`、`childCreatedAt`、mode 与按 mode 区分的 label。没有本地 Session 的远程 one-shot run 不进入该目录。无效的自身 fact(包括不支持的 payload 版本)会使 projection 恢复失败,因为静默丢弃 required fact 会返回不完整的目录。 +共享 activation 入口、外部执行所有权,以及调用方与父级之间的结果投递选择由[统一 subagent activation](../simplification/2026-09-17-unified-subagent-activations.zh.md) 决策拥有。本记录保留下述独立理由。 -创建只发布成功事实。one-shot run 在 provider 返回本地 child 后、run 到达调用方前追加目录事件。continuable run 先准入初始 prompt,再追加目录事件,最后返回 child id。准入或目录追加失败时,创建失败并释放 activation;不存在补偿目录事件或 rollback 协议。 +父级 Session 的必读 `subagent/catalog` 事件是发现直接子级的持久化权威。每个本地子级都会进入目录,与结果接收方无关;外部子级仅在 parent 投递时进入目录。工作流成员记录描述执行进度,不替代本地 Session 发现。每个事件包含 `childId`、`childCreatedAt`、mode 与按 mode 区分的 label。本地子级使用 `continuable`;外部执行使用 `external`,不能作为本地 Session 打开。历史 `one-shot` 与 `unknown` 条目仍可读取。无效的自身事实(包括不支持的载荷版本)会使投影恢复失败,因为静默丢弃必读事实将返回不完整的 catalog。 + +创建只发布成功事实。本地 activation 先接纳初始提示词,再追加 catalog 事件。外部 activation 在提供方启动后、返回回执前追加事件。准入或发布失败会释放未发布的执行;不存在补偿 catalog 事件或回滚协议。 child header 与 `subagent/descriptor` 继续拥有恢复与 composition 权威。Activation 与精确 parent 关系继续拥有授权与投递权威。mode 与 label 只快照一次,同一份分离值写入 parent catalog fact 与 child descriptor。 注册的 `subagentCatalog` projection 物化 parent fact。它将存储、追加、迭代和检查点校验交给 [`dsh-chunked-list`](../../../../packages/util/chunked-list/README.zh.md),后者以每块 64 项的持久 stack 保存事实,因此 append 最多复制 head chunk,以有界 O(1) 工作完成。materialization 从旧到新访问 chunk,对 D 条事实以 O(D) 时间保留父目录事件顺序。并发创建按目录成功追加的顺序排列,与 child 时间戳和 id 无关。projection checkpoint 以 O(D) 克隆 state;projection-cache 继续异步写入,并使用既有创建、turn-end 与 disposal 强制点。 -工具库拥有分块布局及其共享容量常量;目录拥有事件校验、fork 过滤和目录行转换。目录 projection state 版本 2 保存通用块值,因此 projection registry 从 Session 事件重建不兼容的缓存。Session 事件载荷和公开目录行保持各自格式。 +工具库拥有 chunk 布局与共享容量常量;catalog 拥有事件校验、fork 过滤和行转换。catalog 投影状态版本 6 接受外部成员,因此投影注册表会从 Session 事件重建不兼容缓存。载荷 v0 与 v1 保持不变;v2 记录外部执行。 fork 隔离使用 projection 初始化时提供的精确 `Session.inheritedEventCount`。fold 忽略该 offset 之前的 `subagent/catalog` 事件。state 保存 inherited offset,但不保存每条 event seq,因为接受判定已在 fold 时完成。 @@ -44,6 +46,6 @@ snapshot normalizer 会把 `childCreatedAt` 归零,因为它来自 process clo ## 后果 -Session observation 与 client snapshot 通过 `projections.values.subagentCatalog` 暴露直接子级列表。目录状态变化时,projection change feed 发布完整列表。每次 view 的成本为 O(D),因此 D 次创建可能累计产生 O(D²) 的 view 工作量;这沿用现有 projection 机制。`listChildren()` 通过一次 live-preferred Session observation 读取父 projection;后代枚举按目录顺序递归调用同一读取函数。可达目录事实提供身份和成员关系,无需全局 Session 扫描或独立的子身份缓存路径。不可读分支保留为诊断;未进入目录的 Session 不在发现范围内。[Web projection 消费决策](../simplification/2026-09-08-web-subagent-catalog-projections.zh.md) 说明浏览器加载与同步。 +Session observation 与 client snapshot 通过 `projections.values.subagentCatalog` 暴露直接子级列表。目录状态变化时,projection change feed 发布完整列表。每次 view 的成本为 O(D),因此 D 次创建可能累计产生 O(D²) 的 view 工作量;这沿用现有 projection 机制。`listChildren()` 通过一次 live-preferred Session observation 读取父 projection;后代枚举按目录顺序递归调用同一读取函数。可达目录事实提供身份和成员关系,无需全局 Session 扫描或独立的子身份缓存路径。不可读分支保留为诊断;未进入目录的 Session 不在发现范围内。[Web projection 消费说明](../../../../packages/client/ui-subagent/README.zh.md) 说明浏览器加载与同步。 -[创建元数据排除决策](../simplification/2026-09-16-subagent-catalog-membership-only.zh.md) 说明目录事实为何不包含模型配置。 +[创建元数据排除说明](../../../../packages/subagent/subagent/README.zh.md) 说明目录事实为何不包含模型配置。 diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml index c171926d8d..8a43da160d 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.i18n.yaml @@ -12,8 +12,8 @@ en: 14558bfaf20998db zh: d883b1dd6f024b93 /agent-note-embed-assistant-streams-in-v2-attempt-settlements/decision/live-presentation-and-durable-replay: - en: daf667b45ce34da3 - zh: 25b17f7b3ec14d9e + en: 1dc0ec0236da450b + zh: 0cf2bb3c7e26eb17 /agent-note-embed-assistant-streams-in-v2-attempt-settlements/decision/released-v1-to-v2-migration: en: c1b5a775aa073a9a zh: 45a16998d636aec1 diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md index 89b494959f..38101add90 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md @@ -31,7 +31,7 @@ The migration publication verifier and frozen v2 fixture validator require the e The Web follow adapter opts into these process-local frames and adds the last durable sequence observed at each start. It presents chunks as Client-only `assistant/live-chunk` updates between durable cursors, stages only a later matching settlement until the committed end, and reopens follow on a revision gap. A committed end publishes a named settlement delta that removes the attempt's transient matches, adds the durable entry, and replays only affected Conversation Contexts; an abandoned end publishes the same delta without an entry. A reconnect baseline carries the active attempt's durable start cursor and compact prefix. -The Client event source passes durable settlements through unchanged. Chat and Trajectory fold `assistant/live-chunk` while an attempt is active and build settled output directly from `assistant/message`. Chat does not reconstruct first-token timing after settlement retires the transient chunks. Trajectory reads timing from the [compact stream records](2026-09-06-embedded-stream-record-readers.md) in `assistant/message` and `assistant/attempt`, including when opening history. Neither target expands settled streams into per-delta objects for presentation; other consumers may expand the durable stream when they require its exact evidence. +The Client event source passes durable settlements through unchanged. Chat and Trajectory fold `assistant/live-chunk` while an attempt is active and build settled output directly from `assistant/message`. Chat does not reconstruct first-token timing after settlement retires the transient chunks. Trajectory reads timing from the [compact stream records](../../../../packages/llm/llm/README.md) in `assistant/message` and `assistant/attempt`, including when opening history. Neither target expands settled streams into per-delta objects for presentation; other consumers may expand the durable stream when they require its exact evidence. ### Released v1 to v2 migration diff --git a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md index 63cb425740..718333170a 100644 --- a/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md @@ -31,7 +31,7 @@ Migration publication verifier 与冻结的 v2 fixture validator 要求嵌入式 Web follow adapter 显式选择接收这些进程本地 frame,并为每个 start 补充当时观察到的最后一个持久序号。它把 chunk 呈现为持久 cursor 之间的 Client-only `assistant/live-chunk` update,只暂存 start 之后匹配的 settlement,并在 revision 缺口时重新打开 follow。committed end 会发布具名 settlement delta,删除该 attempt 的 transient match、加入持久 entry,并只重放受影响的 Conversation Context;abandoned end 会发布不含 entry 的同类 delta。重连 baseline 携带活跃 attempt 的持久起始 cursor 与紧凑前缀。 -Client event source 原样传递持久 settlement。Chat 与 Trajectory 在 attempt 活跃时折叠 `assistant/live-chunk`,直接从 `assistant/message` 构造 settled output。结算移除临时 chunk 后,Chat 不会重建首 token 计时。Trajectory 从 `assistant/message` 与 `assistant/attempt` 中的[紧凑流记录](2026-09-06-embedded-stream-record-readers.zh.md)读取计时,包括打开历史时。两个目标都不会为展示将已结算流展开为逐 delta 对象;其他消费方需要精确证据时仍可展开持久 stream。 +Client event source 原样传递持久 settlement。Chat 与 Trajectory 在 attempt 活跃时折叠 `assistant/live-chunk`,直接从 `assistant/message` 构造 settled output。结算移除临时 chunk 后,Chat 不会重建首 token 计时。Trajectory 从 `assistant/message` 与 `assistant/attempt` 中的[紧凑流记录](../../../../packages/llm/llm/README.zh.md)读取计时,包括打开历史时。两个目标都不会为展示将已结算流展开为逐 delta 对象;其他消费方需要精确证据时仍可展开持久 stream。 ### 已发布 v1 到 v2 迁移 diff --git a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml deleted file mode 100644 index 6a8c9879cf..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-02-projcache-cross-version-read-compat.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# 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 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7: - en: 5744de86025f278d - zh: 5ba2198c13ac54f9 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/problem: - en: f8d44a69c7b5fefe - zh: 76a4fb3d87698194 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/the-on-disk-generations: - en: 221b8afd2599e139 - zh: ba1de634b87edc71 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/decision: - en: 264e40c0fb90cdc1 - zh: 2e986615bbd656cf -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/decision/v3-v6-v7-disposition: - en: fe188d164c08a7f5 - zh: 30aa9e9201621f35 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/decision/upgrade-matrix: - en: 5d515bd3cb34b0ee - zh: 0648563a966759c7 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/alternatives-considered: - en: c53e111219c5da0c - zh: 8c69f6fc2e776981 -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/consequences: - en: ac8b2f4a66c926ea - zh: 5a415436c2fbc4ec -/agent-note-projection-cache-predecessor-recovery-and-session-format-binding-session-projcache-v3-v6-v7/testing: - en: 495058fe5708949d - zh: 67ed95abc946d418 diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml index 6a5de04c23..6077e29553 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.i18n.yaml @@ -18,8 +18,8 @@ en: a217a08932938062 zh: 4924d99d55d9c973 /agent-note-the-system-prompt-is-surface-node-0/decision/ownership-in-the-loop: - en: 88945024c2efe271 - zh: 05dca84a4ce5e614 + en: a7d0d3a61d2fef04 + zh: 29a3d5dbed2f987a /agent-note-the-system-prompt-is-surface-node-0/decision/consumers: en: 39f9f4acffcccdc6 zh: 36b23a57e7d28950 @@ -33,5 +33,5 @@ en: bccea8236b4c32b3 zh: a1150784718a6639 /agent-note-the-system-prompt-is-surface-node-0/testing: - en: f5b21c4863cd92bd - zh: 7d1efed827d55161 + en: 9a5a241ae3d0c676 + zh: 8f3e83cc54604bcc diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md index 1500fd2350..2796b9dd52 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md @@ -36,7 +36,7 @@ When the initial rendered prompt is empty, the loop reserves an empty system hea `dsh-agent-loop` owns `SystemPromptProjection` beside `RuntimeContextProjection` in `packages/core/agent-loop/src/runtime-context.ts`. It reads the surviving `system/message` nodes from the current surface on every projection, so a compaction or replacement that ran earlier in the same step is already reflected. `project(rendered, { inHistory, startsSeries })` returns `{ message, intent }` — `intent` is `{ surfaceOp: 'append' }` when no system node survives or when the [in-history rule](../feature/2026-09-02-in-history-system-prompt-replacement.md) applies, otherwise a replacement of exactly the latest surviving system node — or `undefined` when the latest node already holds the rendered text. -In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream. The `dsh-agent-loop/invariant` companion (`packages/core/agent-loop/src/invariant.ts`) asserts that a loop-built request has `system === undefined` and `messages` equal to `deriveMessages()`. +In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream. `dsh-token-meter` anchors usage to the priced surface immediately before the successful `assistant/message`, not to `step/start`. The loop admits the system prompt and user messages after step start, and retry recovery can replace nodes before rebuilding the request. Capturing that current surface includes every admitted input once; the embedded provider output remains separately priced so durable assistant rewrites retain their signed delta. The open step stores only turn and step for lifecycle validation, not a second node snapshot. @@ -90,6 +90,5 @@ The [canonical-envelope specification](../../../../packages/session/session-form - `packages/core/session/tests/surface.spec.ts` (`system/message surface node` block) pins the leading system-role projection, the empty-content `null` projection, `assertSystemHeadRewrite`'s acceptance and rejection paths, the unprotected later system nodes, and the rejection of a seeded `system/message` with a non-system role or non-plugin source. - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` pins the append on first render (including empty), the later non-empty prompt at the derived head in replacement mode, the no-op on an unchanged prompt, the replacement of the latest surviving node on change, the tail append after a replacement shadowed a non-head system node, and the in-history append and re-baseline rules. - `packages/core/agent-loop/tests/request-reconstruction.spec.ts` (`a system-prompt change replaces surface node 0 and starts a new series under the same header`) pins the `series` header that follows a prompt replacement. -- `packages/core/agent-loop/tests/invariant.spec.ts` pins the companion's rejection of a loop request carrying a `system` field and its `messages` equality check against the boundary derivation. - `packages/llm/llm-deepseek/tests/serialize.spec.ts` (`serializes a leading system message byte-for-byte like the same prompt passed as options.system`) pins wire identity. `packages/llm/llm-pi-ai/tests/context.spec.ts` compares both system sources on text and image paths. `packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` pins the derived prefix, routed tools, absent separate `system` option, and protected non-empty or empty head through the region transaction and default summarizer. - The recorded snapshots under `snapshots/` pin the model-visible wire request of every shipped profile; a recorded session that renders a prompt carries the `system/message` event at surface node 0 in its `session.jsonl`, and a session with a mid-session prompt change carries the replacement of node 0 or, on an in-history route, the appended node. diff --git a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md index 306f347092..417bf5fb3e 100644 --- a/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md @@ -36,7 +36,7 @@ Status: implemented `dsh-agent-loop` 在 `packages/core/agent-loop/src/runtime-context.ts` 中与 `RuntimeContextProjection` 并列拥有 `SystemPromptProjection`。它在每次投影时从当前 surface 读取存活的 `system/message` 节点,因此同一步骤中更早运行的压缩或替换已经反映在内。`project(rendered, { inHistory, startsSeries })` 返回 `{ message, intent }`——没有系统节点存活或[历史内规则](../feature/2026-09-02-in-history-system-prompt-replacement.zh.md)适用时 `intent` 为 `{ surfaceOp: 'append' }`,否则是对最新存活系统节点的精确替换——最新节点已持有渲染文本时返回 `undefined`。 -在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。`dsh-agent-loop/invariant` 伴随组件(`packages/core/agent-loop/src/invariant.ts`)断言循环构建的请求满足 `system === undefined` 且 `messages` 等于 `deriveMessages()`。 +在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。 `dsh-token-meter` 把用量锚定到成功的 `assistant/message` 之前的已计价 surface,而不是 `step/start`。循环在步骤开始之后接纳系统提示词与用户消息,重试恢复还可能在重建请求之前替换节点。捕获当前 surface 会让每个已接纳输入恰好计入一次;内嵌的提供方输出仍单独计价,因此持久 assistant 改写保留其带符号增量。开放步骤只保存 turn 与 step 以验证生命周期,不保存第二份节点快照。 @@ -90,6 +90,5 @@ Status: implemented - `packages/core/session/tests/surface.spec.ts`(`system/message surface node` 块)钉住开头 system 角色的投影、空内容的 `null` 投影、`assertSystemHeadRewrite` 的接受与拒绝路径、更后位置系统节点不受保护,以及对 seed 中非 system 角色或非插件 source 的 `system/message` 的拒绝。 - `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` 钉住首次渲染时的追加(包括空提示词)、替换模式下后来非空提示词位于派生历史头部、提示词未变时的无操作、变更时对最新存活节点的替换、替换遮蔽了非头部系统节点之后的尾部追加,以及历史内追加与重新基线规则。 - `packages/core/agent-loop/tests/request-reconstruction.spec.ts`(`a system-prompt change replaces surface node 0 and starts a new series under the same header`)钉住提示词替换之后跟随的 `series` header。 -- `packages/core/agent-loop/tests/invariant.spec.ts` 钉住伴随组件对携带 `system` 字段的循环请求的拒绝,以及其 `messages` 与边界派生结果的相等性检查。 - `packages/llm/llm-deepseek/tests/serialize.spec.ts`(`serializes a leading system message byte-for-byte like the same prompt passed as options.system`)钉住协议一致性。 `packages/llm/llm-pi-ai/tests/context.spec.ts` 在文本与图片路径上比较两种系统提示词来源。`packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` 通过区域事务与默认摘要器钉住派生前缀、已路由工具、不携带单独 `system` 选项,以及非空或空头节点的保护。 - `snapshots/` 下的录制快照钉住每个随发 profile 的模型可见协议请求;渲染了提示词的录制会话在其 `session.jsonl` 中于 surface 第 0 号节点携带 `system/message` 事件,会话中途发生提示词变更的会话则携带对第 0 号节点的替换,或在历史内路由上携带追加的节点。 diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml index 905c80c5a3..b66d66b8e0 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.i18n.yaml @@ -12,8 +12,8 @@ en: a7e2bd18a7e647f0 zh: 1d8f5500a3b0502a /agent-note-one-output-ring-one-projection-one-event-stream-for-background-jobs/alternatives-considered: - en: d5d9d6c2c05e563b - zh: 66b2489d947365fe + en: 09be2a1cf1d52fd5 + zh: 0bf0e18c73542012 /agent-note-one-output-ring-one-projection-one-event-stream-for-background-jobs/consequences: - en: 5c90e63b12c1e9db - zh: ab24e56a9ae34ea0 + en: 3263801e173b6b86 + zh: f6f51bb3f77a817e diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md index 5694c517f5..04f106edf3 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md @@ -23,6 +23,8 @@ The job seam had grown by accretion. Producers declared their output twice — a ## Alternatives considered +**Separate lifecycle and observation registries.** One producer would register the same work twice and coordinate terminal state, correlation IDs, and two client rosters. A job therefore owns both lifecycle and observation. Foreground-only or model-invisible jobs would require separate reporting and visibility rules; foreground workflow narration gains live observation by becoming an ordinary background job instead. + - **Keep three listeners with a shared owner filter.** It preserves the accreted names but not the property reviewers asked for — one place to learn what the registry announces — and `onJobDone`'s exact-owner delivery is what forced the registry to keep an `Agent` in every projection. - **Keep `reported` in the registry.** Only the tool knows which deliveries reach the model (a wait's tool result, a kill's acknowledgement); the registry could only approximate it, and the approximation was the source of the double-notice and missed-notice bugs the review listed. - **Keep the roster on the session control stream.** It saved one Remote stream but coupled the session controller to the job registry and mirrored a `record` flag the ring's byte count now answers directly (`output.total`). @@ -32,6 +34,6 @@ The job seam had grown by accretion. Producers declared their output twice — a The model-visible text is unchanged: status lines, tool descriptions and schemas, and notice wording are byte-identical, and the only new model-visible fact is the kill reason a `killed` status line now carries. A producer that needs to stream output picks pull sources or `append`; it never formats a consuming delta. The Web client renders every live job as expandable, so a job that never writes shows an empty running panel instead of a static row. `JobKindMap` merges are host-side, so a browser program types `JobView.kind` as the kinds it knows and treats the rest as opaque strings, which is all the roster does with it. -Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The `@deepseek-ai/dsh-jobs/invariant` companion checks the announced event protocol per job (registered first, one settlement, removal last) and each announced projection against the registry's own read, which is the cross-observation the [companion rule](../simplification/2026-08-28-omit-unneeded-invariant-companions.md) requires; the earlier single-view field checks are gone. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered. +Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered. The [Session lifecycle decision](../bug-fix/2026-09-30-job-session-lifecycle.md) records registration and settlement for durable SDK consumers while preserving this live event stream and output ownership. diff --git a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md index 2090df84a5..798f9d2b7c 100644 --- a/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md @@ -23,6 +23,8 @@ job seam 是逐步堆出来的。生产者要把输出声明两次——模型 ## 考虑过的替代方案 +**分离生命周期与观测注册表。** 一个生产者需要为同一份工作注册两次,并协调终态、关联 ID 和两份客户端名册。因此,一个 job 同时拥有生命周期与观测。仅前台或对模型不可见的 job 需要另一套报告与可见性规则;前台 workflow 叙述通过成为普通后台 job 获得实时观测。 + - **保留三个监听器并共享一个拥有者过滤。** 它保住了堆出来的名字,却保不住评审要的性质——一个地方就能学会注册表宣布什么——而且 `onJobDone` 的精确拥有者投递正是迫使注册表在每个投影里保留 `Agent` 的原因。 - **把 `reported`留在注册表。** 只有工具知道哪些投递到达了模型(等待的工具结果、kill 的确认);注册表只能近似,而这个近似正是评审列出的重复播报与漏播报 bug 的来源。 - **把名册留在会话控制流上。** 省了一条 Remote 流,却把会话控制器耦合到 job 注册表,还镜像了一个环的字节数如今直接回答的 `record` 标志(`output.total`)。 @@ -32,6 +34,6 @@ job seam 是逐步堆出来的。生产者要把输出声明两次——模型 模型可见文本保持不变:状态行、工具描述与 schema、播报措辞逐字节相同,唯一新增的模型可见事实是 `killed` 状态行现在携带的 kill 原因。需要流式输出的生产者选择拉取源或 `append`,从不格式化消耗式增量。Web 客户端把每个进行中的 job 渲染为可展开,因此从不写输出的 job 显示为空的运行中面板而非静态行。`JobKindMap` 的合并发生在宿主侧,浏览器程序把 `JobView.kind` 视为它认识的 kind 集合,其余当作不透明字符串——名册对它也只做这些。 -合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。`@deepseek-ai/dsh-jobs/invariant` 伴生插件校验每个 job 的事件协议(先 `registered`、恰一次结算、最后 `removed`)以及每个通告的投影与注册表自身读取的一致性,这正是[伴生插件规则](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md)要求的交叉观察;先前只针对单个视图的字段校验已删除。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。 +合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。 [Session 生命周期决策](../bug-fix/2026-09-30-job-session-lifecycle.zh.md)为持久化 SDK 消费方记录注册与结算,同时保留这里的运行期事件流和输出归属。 diff --git a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.i18n.yaml index 76c55ab2c1..61c4f1b4cd 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.i18n.yaml @@ -12,8 +12,8 @@ en: 78afa8698a884b98 zh: b33cfcae7e75660f /agent-note-client-resource-model/decision/addresses: - en: 180e3e39813a93b1 - zh: 845befb7c760f7e1 + en: f369df55e7e0bf77 + zh: 593d4741326dc536 /agent-note-client-resource-model/decision/the-service: en: f42277e961bb7209 zh: 9f3f8a50ad55cf4a @@ -24,8 +24,8 @@ en: bc1c4e9a9e8e9b20 zh: f80a77ee66943ce4 /agent-note-client-resource-model/decision/lifecycle: - en: 4c11bfba97a54646 - zh: a07bda77e918d613 + en: b67e81a19faa5605 + zh: 1244213c3fc7ae4e /agent-note-client-resource-model/alternatives-considered: en: 288b448e7f1de8af zh: 997e10c66decce34 diff --git a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.md b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.md index e6ad8dae33..9fbaf4b816 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.md +++ b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.md @@ -16,7 +16,7 @@ The tab record set the constraint. A tab must survive undo, redo, body remount, ### Addresses -A resource address is a `dsh-resource:///…` URL. The host is the protocol key — the key of `ResourceProtocolMap` — and the path belongs to the protocol's owner. `RESOURCE_SCHEME = 'dsh-resource'` is the one scheme constant; `protocolOf(address)` parses the string with `new URL`, requires `protocol === 'dsh-resource:'`, and returns the lower-cased host, or `undefined` for a string the parser rejects, another scheme, or an empty host. `dsh-resource` is not one of the URL specification's special schemes, so the parser keeps the host's case and treats the path as opaque; the lower-casing is explicit, and each path segment is percent-encoded by the protocol that defines it. A protocol that needs a scope encodes it in the path: `dsh-resource://file/session//`, with `session/` naming the Session whose Host workspace resolves the relative or absolute path ([grammar](../../../../packages/util/workspace-path/README.md)). Any other scheme — `sidebar://guide` — is a navigation address: it names a tab, not data, and the model answers `none` for it ([tab types and navigation](2026-09-05-sidebar-tab-types-and-navigation.md)). +A resource address is a `dsh-resource:///…` URL. The host is the protocol key — the key of `ResourceProtocolMap` — and the path belongs to the protocol's owner. `RESOURCE_SCHEME = 'dsh-resource'` is the one scheme constant; `protocolOf(address)` parses the string with `new URL`, requires `protocol === 'dsh-resource:'`, and returns the lower-cased host, or `undefined` for a string the parser rejects, another scheme, or an empty host. `dsh-resource` is not one of the URL specification's special schemes, so the parser keeps the host's case and treats the path as opaque; the lower-casing is explicit, and each path segment is percent-encoded by the protocol that defines it. A protocol that needs a scope encodes it in the path: `dsh-resource://file/session//`, with `session/` naming the Session whose Host workspace resolves the relative or absolute path ([grammar](../../../../packages/util/workspace-path/README.md)). Any other scheme — `sidebar://guide` — is a navigation address: it names a tab, not data, and the model answers `none` for it ([tab types and navigation](../../../../packages/client/ui-sidebar-right/README.md)). ### The service @@ -55,7 +55,7 @@ A provider yields `RemoteResult` frames: the current state first, one frame per One record exists per address. Holds start and stop observation, not the underlying file or Session. Its holders are the hook's subscribers plus pins; the first holder opens the provider's stream under an `AbortController`, later holders share it and read the latest value at once, and the last release aborts the stream and resets the snapshot to idle — `loading` while a provider is registered, `none` otherwise. A provider that arrives while an address is already held opens that address's stream; one that leaves aborts it and the address reads `none`. Records are kept for the page lifetime so `source(address)` stays reference-stable across React's render-then-subscribe window and a StrictMode remount, where a recreated record would resubscribe and restart the stream on every render. -The right Sidebar's Tab domain pins every open tab record's address for the record's life, so switching tabs unmounts a body without closing its stream and switching back reads the latest value; a record restored by undo is a new pin, and a resource the model already let go is read again ([tab types and navigation](2026-09-05-sidebar-tab-types-and-navigation.md)). `openResource(address)` accepts resource addresses only; pages such as the guide and the file tree are opened by kind and never enter the resource model. +The right Sidebar's Tab domain pins every open tab record's address for the record's life, so switching tabs unmounts a body without closing its stream and switching back reads the latest value; a record restored by undo is a new pin, and a resource the model already let go is read again ([tab types and navigation](../../../../packages/client/ui-sidebar-right/README.md)). `openResource(address)` accepts resource addresses only; pages such as the guide and the file tree are opened by kind and never enter the resource model. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.zh.md b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.zh.md index d716aa8a42..7aeac0d3a5 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-05-client-resource-model.zh.md @@ -16,7 +16,7 @@ Status: implemented ### 地址 -资源地址是 `dsh-resource:///…` 形式的 URL。host 是协议键——`ResourceProtocolMap` 的键——路径归协议拥有者。`RESOURCE_SCHEME = 'dsh-resource'` 是唯一的 scheme 常量;`protocolOf(address)` 用 `new URL` 解析字串,要求 `protocol === 'dsh-resource:'`,返回小写 host;解析器拒绝的字串、其它 scheme 或空 host 返回 `undefined`。`dsh-resource` 不是 URL 规范里的特殊 scheme,解析器会保留 host 的大小写并把路径当作不透明串,所以小写化是显式做的,每段路径由定义它的协议做百分号编码。需要作用域的协议把作用域编进路径:`dsh-resource://file/session//`,`session/` 命名由其 Host 工作区解析相对或绝对路径的 Session([语法](../../../../packages/util/workspace-path/README.zh.md))。其它任何 scheme——`sidebar://guide`——是导航地址:它命名一个 tab 而非数据,模型对它回答 `none`([tab 类型与导航](2026-09-05-sidebar-tab-types-and-navigation.zh.md))。 +资源地址是 `dsh-resource:///…` 形式的 URL。host 是协议键——`ResourceProtocolMap` 的键——路径归协议拥有者。`RESOURCE_SCHEME = 'dsh-resource'` 是唯一的 scheme 常量;`protocolOf(address)` 用 `new URL` 解析字串,要求 `protocol === 'dsh-resource:'`,返回小写 host;解析器拒绝的字串、其它 scheme 或空 host 返回 `undefined`。`dsh-resource` 不是 URL 规范里的特殊 scheme,解析器会保留 host 的大小写并把路径当作不透明串,所以小写化是显式做的,每段路径由定义它的协议做百分号编码。需要作用域的协议把作用域编进路径:`dsh-resource://file/session//`,`session/` 命名由其 Host 工作区解析相对或绝对路径的 Session([语法](../../../../packages/util/workspace-path/README.zh.md))。其它任何 scheme——`sidebar://guide`——是导航地址:它命名一个 tab 而非数据,模型对它回答 `none`([tab 类型与导航](../../../../packages/client/ui-sidebar-right/README.zh.md))。 ### 服务 @@ -55,7 +55,7 @@ type UseResource =

(address: string) => ResourceSnap 每个地址一条记录。持有只控制观察的启停,不控制底层文件或 Session 的生灭。持有者是 hook 的订阅者加 pin;第一个持有者在 `AbortController` 下打开提供方的流,之后的持有者共享它并立刻读到最新值,最后一个释放时中止流并把快照重置为空闲——有提供方注册时为 `loading`,否则为 `none`。地址已被持有时到达的提供方会打开该地址的流;离开的提供方中止它,地址读作 `none`。记录在页面存续期内保留,使 `source(address)` 在 React 渲染到订阅的窗口与 StrictMode 重挂载之间保持引用稳定,否则重建记录会让每次渲染重订阅、重开流。 -右侧 Sidebar 的 Tab 域在每条打开的 tab 记录存续期内钉住其地址,所以切 tab 卸载正文不关流、切回读到最新值;撤销恢复的记录是一次新的钉住,模型已放掉的资源会重新读取([tab 类型与导航](2026-09-05-sidebar-tab-types-and-navigation.zh.md))。`openResource(address)` 只收资源地址;引导页与文件树这类页面按 kind 打开,从不进入资源模型。 +右侧 Sidebar 的 Tab 域在每条打开的 tab 记录存续期内钉住其地址,所以切 tab 卸载正文不关流、切回读到最新值;撤销恢复的记录是一次新的钉住,模型已放掉的资源会重新读取([tab 类型与导航](../../../../packages/client/ui-sidebar-right/README.zh.md))。`openResource(address)` 只收资源地址;引导页与文件树这类页面按 kind 打开,从不进入资源模型。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.i18n.yaml deleted file mode 100644 index 1cbd6713ec..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-05-package-manifest-types.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-package-manifest-types.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-05-package-manifest-types.md -/agent-note-package-manifest-declaration-ownership: - en: 26d50551c7d49cf8 - zh: eac4a93af773c945 -/agent-note-package-manifest-declaration-ownership/problem: - en: bdc0fcaf27081889 - zh: 45eb034edd0bed82 -/agent-note-package-manifest-declaration-ownership/decision: - en: da43234e14fa9bd5 - zh: 0389015e086f4e25 -/agent-note-package-manifest-declaration-ownership/alternatives-considered: - en: bf99e75ffab5cb45 - zh: 398f5934858aaca5 -/agent-note-package-manifest-declaration-ownership/consequences: - en: ce7c3df526622bf2 - zh: 91d51dff77371cb0 diff --git a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.i18n.yaml deleted file mode 100644 index d7f7c0dc3b..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-shared-client-control-primitives.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md -/agent-note-shared-client-control-primitives: - en: 53e5c771078f4383 - zh: 59e4cc45d06adbee -/agent-note-shared-client-control-primitives/problem: - en: 7a711c1cafe0be88 - zh: 7275b36fdd5094f6 -/agent-note-shared-client-control-primitives/decision: - en: cc335d6820b9ab1c - zh: e375bababc0fccdb -/agent-note-shared-client-control-primitives/finding-the-duplicates: - en: f50ed2aea00a34c5 - zh: a8a27c1d1e0177a8 -/agent-note-shared-client-control-primitives/what-stays-local: - en: e4fb0a84b9924d5a - zh: 30545265b2159f04 -/agent-note-shared-client-control-primitives/alternatives-considered: - en: f77deaa327bc5395 - zh: be8450b665be04bf -/agent-note-shared-client-control-primitives/testing: - en: 578f6bad674e4c40 - zh: 0a8f2e72bb4d1f61 -/agent-note-shared-client-control-primitives/consequences: - en: f94dd6f669d00276 - zh: a67de4bd3f242232 diff --git a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md b/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md deleted file mode 100644 index db8cb9a8bc..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md +++ /dev/null @@ -1,73 +0,0 @@ -# Agent Note: Shared client control primitives - -Status: implemented - -English | [中文](2026-09-05-shared-client-control-primitives.zh.md) - -## Problem - -Client feature plugins compose through slots and never import one another's values, so `@deepseek-ai/dsh-client-ui-primitives` is their only channel for sharing a React component. A control that grows inside one feature package is invisible to the next package that needs the same thing, and copying its markup and CSS is the cheapest move available. Three families had diverged that way. A 36×20 toggle switch existed only inside `ui-settings-plugins`. Read-only capsule badges were declared five separate times across `ui-agent-preset`, `ui-settings-plugins`, and `ui-settings-plugin-inventory`, with two different corner radii and separately authored palettes. A plugin-phase status dot was reimplemented in `ui-settings-plugin-inventory` next to the shared `StateDot` it duplicates. - -Neither half of the fix was available to an author. No checklist told them to look in `ui-primitives` before writing a control, and the package gave them nothing to look at: its README named six source files while the package exported more than forty symbols. - -## Decision - -**A control that a second client package needs lives in `ui-primitives`.** The rule is guidance for authors, not a gate: a feature package may still write its own component when its need is genuinely specific, and the [what stays local](#what-stays-local) list below records the cases from this change. What the rule forbids is the copy — when a control already exists, the author either uses it or lifts the deliberate difference into a prop. - -`Tag` is the read-only capsule badge. Its geometry is fixed at the size `ui-agent-preset` established: `999px` radius, `1px 8px` padding, 11px text on a 17px line, 500 weight, `inline-flex`, no wrapping. A single closed `TagTone` union selects the palette, and every member exists because a shipped call site needed it: `outline` and `solid` from the agent-preset section, `neutral` and `quiet` from the plugin settings fields, and `success`, `info`, `warning`, and `danger` from the plugin inventory's enablement tags. The component carries no copy of its own, as every Cordis-free primitive must. - -`Switch` is the two-state toggle, at the 36×20 track and 16px thumb `ui-settings-plugins` established. `label` is required and has no default, so a render site cannot omit the accessible name; `title` carries a lock reason where a deployment disables the control. - -`StateDotState` includes `idle` for a tracked subject with no activity in progress. The plugin inventory's `pending` and `unloading` phases need that semantic member; without it those two phases lose their marker entirely. The five-state union remains safe for consumers because each package produces a `StateDotState` from its own closed status union and none switches over `StateDotState` itself. The current solid-dot palette and rotating `ongoing` loader are owned by the later [StateDot visual language decision](../feature/2026-09-17-state-dot-visual-language.md). - -**The component catalog in the `ui-primitives` README is what makes the rule usable.** It lists every exported component with its purpose and the case it is wrong for, and it names the three pairs that are easy to confuse: `Tag` against `Pill`, `DisclosureRow` against card-shaped disclosure, and the package-internal `FoldToggle` against the exported surface. `Pill` is the selectable capsule button — it takes `active` and `onClick` and drives view switchers and filters; `Tag` is the read-only badge and takes neither. The component comments use the same distinction. - -The rule is stated in [packages/client/AGENTS.md](../../../../packages/client/AGENTS.md) as the first step of the new-component checklist, and [docs/web-styling.md](../../../../docs/web-styling.md) points at the catalog so an author arriving from the styling side reaches it too. - -## Finding the duplicates - -A name-based search undercounts. `.badge`, `.tag`, `.chip`, and `.configTag` miss a capsule named for its role rather than its appearance — `PluginCard`'s unsaved marker is `.pending`. Geometry is the reliable probe: search CSS Modules rules carrying both `border-radius: 999px` and `padding: 1px 8px`. That signature belongs to `Tag` and the deliberately local broken badge below; the unsaved marker keeps only its `flex: none` placement class. - - -## What stays local - -A mechanical search groups these controls with the promoted three. They stay in their own packages, because the grouping is superficial: - -- **`ui-trajectory`'s toolbar toggle** carries `role="switch"` but is an 88px labelled control with an inline track, and it currently renders `hidden`. It is not the same widget as `Switch`. -- **`ui-plan`'s mode chip** and **`ui-conversation`'s `ReferenceChip`** are interactive: the first is a warning-toned button with hover, focus, disabled, and a close affordance; the second is a Lexical atom node with its own truncation. Neither is a read-only badge. -- **`ui-trajectory`'s cell tag** and **`ui-user-questions`'s recommendation badge** use their own geometry — a 6px radius at table density, and a 6px radius at 600 weight on the sidebar accent. Forcing either into the capsule baseline would change a deliberate design, not an accidental one. -- **`TerminalBlock`'s exit-status pill** stays a static `Pill`. It sits on a 24px command line at the pill's own geometry, and `Tag`'s 11px capsule would not fit that row. The read-only/selectable split is the usual guide, but size decides this one, and the catalog says so. -- **`ui-agent-preset`'s broken badge** shares the capsule geometry but carries a solid error fill that no second site uses, and it is the hover anchor for a tooltip element of its own. `Tag` would have to keep a palette override in the feature stylesheet and depend on cross-file CSS ordering to win it. - -## Alternatives considered - -**A `SettingsCard` primitive.** Rejected. `.card` appears in fifteen packages, but only three carry settings-page semantics, and those three differ in behaviour rather than appearance: `ui-settings-plugins`' `PluginCard` stages edits and collapses only after a Host-confirmed save, `ui-agent-preset`'s cards are selectable, and the plugin inventory's are read-only. `PluginCard`'s own header comment already records why it cannot use the shared disclosure row. A single component would have to accept all three behaviours through props that no caller uses more than one of. - -**A gate rejecting `role="switch"` or a `.switch` rule outside `ui-primitives`.** Rejected. The goal is that authors reuse what exists, not that they are prevented from building. A gate would fail a package with a legitimately specific control — `ui-trajectory`'s labelled toolbar toggle is exactly that case — and the cost of the false rejection lands on the author least able to argue with it. The catalog plus the checklist step address the actual failure, which is that authors did not know the control existed. - -**Preserving every current appearance behind extra `Tag` props.** Rejected. The plugin inventory's 5px radius and the agent-preset capsule are the same kind of tag in two shapes, and neither difference was decided. Keeping both would fix an accident into a public union and leave the next author to guess which one to pick. - -**Extending `Pill` instead of adding `Tag`.** Rejected. `Pill` is 24px tall on a 12px radius with 12px text; the badge baseline is denser and rounder. Merging them would produce one component whose size depends on whether `onClick` is present, and would erase the read-only/selectable distinction that the catalog needs in order to answer "which one do I want". - -**A two-axis `variant × tone` API for `Tag`.** Rejected. Three variants against six tones describes eighteen combinations of which eight ship, and it lets a caller request combinations with no defined appearance. The flat eight-member union maps each value to exactly one shipped appearance. - -**Rendering no dot for `pending` and `unloading` instead of adding `idle`.** Rejected. Those phases need a compact inactive or transitional marker; dropping it would remove information from the inventory in a change whose purpose is to consolidate presentation. - -**Putting the reuse rule in the root `AGENTS.md`.** Rejected. The rule governs `packages/client` alone, and the root file sits exactly at its 1950-word ceiling in `scripts/doc-budgets.manifest.json`, so stating it there would have to displace an unrelated repository-wide rule. - -## Testing - -`Tag`, `Switch`, and the extended `StateDot` carry component specs in `packages/client/ui-primitives/tests`, inside the per-file 100% coverage gate. `StateDot`'s palette is pinned by reading its stylesheet: CSS Modules resolve to class-name maps in the component suites, so a state whose color rule is missing renders on the inherited color and no render assertion notices. - -The migrated render sites keep package specs for their role, accessible name, state mapping, and interaction. Web ARIA snapshots cover the assembled presentations — `Switch` keeps `role="switch"` with `aria-checked`, and the inventory's phase dot keeps its `role="img"` name on a wrapper because `StateDot` is `aria-hidden`. - -The component and stylesheet specs pin the capsule geometry, StateDot artwork, animation, tokens, and font weight; Web snapshots pin accessible structure. Manual visual inspection remains responsible for the rendered result across themes. - -## Consequences - -- A new client control now has one place to check and one place to add, and the catalog makes the check a single file read rather than a `grep` over forty exports. -- `TagTone` is eight members wide because eight appearances shipped. Adding a ninth requires a render site that needs it, not a symmetry argument. -- The plugin inventory uses capsule tags; the disabled tag uses the `neutral` tone's visible `--dsw-alias-bg-module-platform` fill. Its phase markers use the shared `StateDot`, including the rotating loader for active transitions. The unconfigured-secret badge in the plugin settings fields uses the baseline 500 weight. -- The plugin inventory palette contains no literal `#b45309`. The conditional tag uses the real `--dsw-alias-state-warn-primary` token rather than the nonexistent `--dsw-alias-state-warning-primary` alias and its hardcoded fallback. -- The rule cannot be checked mechanically. A future author can still copy a control, and only review will catch it. That is the accepted cost of not gating: the alternative rejects legitimate work, and the packages that stay local above are proof that legitimate work exists. -- `Tag` and `Switch` are shared across settings and preset packages; future consumers extend these primitives only when an existing prop cannot express a deliberate difference. diff --git a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md b/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md deleted file mode 100644 index 6d4b04a21b..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md +++ /dev/null @@ -1,73 +0,0 @@ -# Agent Note: Shared client control primitives - -Status: implemented - -[English](2026-09-05-shared-client-control-primitives.md) | 中文 - -## Problem - -客户端功能插件通过 slot 组合,彼此从不 import 对方的值,因此 `@deepseek-ai/dsh-client-ui-primitives` 是它们共享 React 组件的唯一通道。在某个功能包内部长出来的控件,对下一个需要同样东西的包不可见,而复制它的标记与 CSS 是当时最省事的做法。三类控件已经这样分叉:36×20 的开关只存在于 `ui-settings-plugins`;只读胶囊徽章在 `ui-agent-preset`、`ui-settings-plugins`、`ui-settings-plugin-inventory` 里被分别声明了五次,带着两种圆角和各自编写的配色;插件相位状态点在 `ui-settings-plugin-inventory` 里被重新实现,就在它所复制的共享 `StateDot` 旁边。 - -修复所需的两半,作者当时一半也拿不到。没有任何清单要求他们在写控件前先看 `ui-primitives`,而这个包也没给他们可看的东西:README 只列了六个源文件,包却导出了四十多个符号。 - -## Decision - -**第二个客户端包需要的控件,住在 `ui-primitives`。** 这条规则是对作者的引导,不是门禁:功能包在需求确实特殊时仍可以自己写组件,下面的[保留在原包的控件](#what-stays-local)记录了本次改动中的这些例子。规则禁止的是复制——控件已经存在时,作者要么用它,要么把那处有意的差异提升成一个 prop。 - -`Tag` 是只读胶囊徽章。几何锁定在 `ui-agent-preset` 确立的尺寸:`999px` 圆角、`1px 8px` 内边距、11px 字号配 17px 行高、500 字重、`inline-flex`、不换行。单个闭合的 `TagTone` 联合选择配色,每个成员都因为有已发布的调用点需要它而存在:`outline` 与 `solid` 来自 agent preset 区块,`neutral` 与 `quiet` 来自插件设置字段,`success`、`info`、`warning`、`danger` 来自插件清单的启用标签。与所有 Cordis-free 原语一样,这个组件不自带任何文案。 - -`Switch` 是双态开关,采用 `ui-settings-plugins` 确立的 36×20 轨道与 16px 滑块。`label` 必填且没有默认值,渲染点无法省略无障碍名称;`title` 在部署禁用该控件时承载锁定原因。 - -`StateDotState` 包含 `idle`,表示一个被跟踪的对象当前没有进行中的活动。插件清单的 `pending` 与 `unloading` 相位需要这个语义成员;没有 `idle`,这两个相位会彻底失去标记。五态联合对消费方保持安全,因为每个包都从自己的闭合状态联合**产生** `StateDotState`,没有任何一个对 `StateDotState` 本身做 switch。当前的纯色点配色与旋转 `ongoing` loading 由后续的 [StateDot 视觉语言决策](../feature/2026-09-17-state-dot-visual-language.zh.md)负责。 - -**`ui-primitives` README 里的组件目录,是这条规则得以可用的前提。** 它列出每个导出组件的用途,以及它不适用的场合,并点名三组容易混淆的配对:`Tag` 与 `Pill`、`DisclosureRow` 与卡片式折叠、包内的 `FoldToggle` 与对外导出面。`Pill` 是可选中的胶囊按钮——它接受 `active` 与 `onClick`,用于视图切换与筛选器;`Tag` 是只读徽章,两者都不接受。组件注释使用同一套区分。 - -规则写在 [packages/client/AGENTS.md](../../../../packages/client/AGENTS.md) 新组件清单的第一步,[docs/web-styling.md](../../../../docs/web-styling.zh.md) 指向该目录,让从样式一侧进来的作者也能到达。 - -## 如何找出重复 - -按名字搜会漏。`.badge`、`.tag`、`.chip`、`.configTag` 找不到以角色而非外观命名的胶囊——`PluginCard` 的未保存标记叫 `.pending`。几何特征才是可靠探针:搜索同时带有 `border-radius: 999px` 与 `padding: 1px 8px` 的 CSS Modules 规则。这个特征属于 `Tag` 与下面那个有意保留在原包的损坏徽章;未保存标记只保留自己的 `flex: none` 布局类。 - - -## 保留在原包的控件 - -机械搜索会把下面这些控件归到被提升的那三类里。它们留在各自的包中,因为那种归并只是表面相似: - -- **`ui-trajectory` 的工具栏开关**带 `role="switch"`,但它是 88px 宽的带标签控件、轨道内联,而且当前渲染为 `hidden`。它和 `Switch` 不是同一个部件。 -- **`ui-plan` 的模式 chip** 与 **`ui-conversation` 的 `ReferenceChip`** 都是可交互的:前者是警告色调的按钮,带 hover、focus、disabled 与关闭操作;后者是带自有截断逻辑的 Lexical 原子节点。两者都不是只读徽章。 -- **`ui-trajectory` 的单元格 tag** 与 **`ui-user-questions` 的推荐徽章**使用各自的几何——一个是表格密度下的 6px 圆角,一个是侧栏强调色上 600 字重的 6px 圆角。把它们塞进胶囊基准,改掉的是有意的设计,不是意外的分歧。 -- **`TerminalBlock` 的退出状态胶囊**保持为静态 `Pill`。它落在 24px 的命令行上、用的是 pill 自己的几何,`Tag` 的 11px 胶囊放不进那一行。只读与可选中的区分是通常的判据,但这一处由尺寸决定,目录里也是这么写的。 -- **`ui-agent-preset` 的损坏徽章**共用胶囊几何,但带着没有第二处使用的实底错误色填充,而且它是自带提示元素的悬停锚点。改用 `Tag` 就得在功能包样式表里保留一份配色覆盖,并依赖跨文件 CSS 顺序来让它生效。 - -## Alternatives considered - -**做一个 `SettingsCard` 原语。** 否决。`.card` 出现在十五个包里,但只有三处带设置页语义,而这三处的差异在行为而不在外观:`ui-settings-plugins` 的 `PluginCard` 暂存编辑,且只在 Host 确认保存后才折叠;`ui-agent-preset` 的卡片可选中;插件清单的卡片是只读的。`PluginCard` 自己的头注释已经记录了它为何不能使用共享折叠行。单个组件将不得不通过 props 接纳全部三种行为,而没有任何一个调用方会用到其中一种以上。 - -**加门禁,拒绝 `ui-primitives` 之外的 `role="switch"` 或 `.switch` 规则。** 否决。目标是让作者复用已有的东西,不是阻止他们构建。门禁会挂掉一个确实有特殊控件的包——`ui-trajectory` 的带标签工具栏开关正是这种情况——而误拒的代价落在最没法跟它讲道理的作者身上。目录加清单条目针对的是真正的失败原因:作者不知道那个控件存在。 - -**用额外的 `Tag` props 保留现有的每一种外观。** 否决。插件清单的 5px 圆角与 agent preset 的胶囊是同一类标签的两种形状,两处差异都不是决定出来的。两个都留会把一次意外固化进公开联合,并且让下一个作者去猜该挑哪个。 - -**扩展 `Pill` 而不是新增 `Tag`。** 否决。`Pill` 高 24px、圆角 12px、字号 12px;徽章基准更密也更圆。合并会产生一个尺寸取决于是否传了 `onClick` 的组件,并抹掉只读与可选中的区分——而目录正需要这个区分来回答"我该用哪个"。 - -**给 `Tag` 设计 `variant × tone` 双轴 API。** 否决。三个 variant 乘六个 tone 描述十八种组合,其中八种会发布,而且它允许调用方请求没有定义外观的组合。扁平的八成员联合把每个值映射到恰好一种已发布外观。 - -**让 `pending` 与 `unloading` 不渲染点,而不是新增 `idle`。** 否决。这些阶段需要紧凑的未活动或过渡标记;在一个目的是统一呈现的改动里把它删掉,等于从清单中拿走信息。 - -**把复用规则写进根 `AGENTS.md`。** 否决。这条规则只管 `packages/client`,而根文件正好处在 `scripts/doc-budgets.manifest.json` 给它的 1950 词上限上,写在那里就必须挤掉一条不相干的仓库级规则。 - -## Testing - -`Tag`、`Switch` 与扩展后的 `StateDot` 在 `packages/client/ui-primitives/tests` 中各有组件测试,处于每文件 100% 覆盖率门禁之内。`StateDot` 的配色通过读取其样式表来钉住:CSS Modules 在组件测试里解析为类名映射,因此某个状态缺了配色规则时会落到继承色上,而任何渲染断言都不会察觉。 - -迁移后的渲染点保留包级测试,覆盖 role、无障碍名称、状态映射与交互。Web ARIA 快照覆盖装配后的呈现——`Switch` 保留带 `aria-checked` 的 `role="switch"`,而由于 `StateDot` 是 `aria-hidden`,插件清单的相位点把 `role="img"` 名称保留在外层包裹元素上。 - -组件与样式表测试固定胶囊几何、StateDot 图稿、动画、token 与字重;Web 快照固定无障碍结构。明暗主题下的最终渲染仍由人工视觉检查负责。 - -## Consequences - -- 新的客户端控件现在有一处可查、一处可加,而目录把这次查询变成读一个文件,而不是在四十多个导出上 `grep`。 -- `TagTone` 有八个成员,因为发布了八种外观。加第九个需要一个真的需要它的渲染点,而不是一个对称性论证。 -- 插件清单使用胶囊标签;"已停用"标签使用 `neutral` 色调下可见的 `--dsw-alias-bg-module-platform` 填充。相位标记使用共享 `StateDot`,活动转换采用旋转 loading。插件设置字段中的"未配置"徽章使用基准 500 字重。 -- 插件清单调色板不含 `#b45309` 字面量。`conditional` 标签使用真实的 `--dsw-alias-state-warn-primary` token,而不是不存在的 `--dsw-alias-state-warning-primary` 别名及其硬编码兜底色。 -- 这条规则无法被机械检查。将来的作者仍然可以复制一个控件,只有评审能拦住。这是不设门禁所接受的代价:另一条路会拒绝正当的工作,而上面那份保留在原包的清单就是正当工作确实存在的证据。 -- `Tag` 与 `Switch` 由多个设置和预设包共享;只有现有 prop 无法表达明确差异时,后续消费方才扩展这些原语。 diff --git a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml deleted file mode 100644 index e80e658859..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.i18n.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-sidebar-tab-types-and-navigation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-05-sidebar-tab-types-and-navigation.md -/agent-note-right-sidebar-tab-types-and-navigation: - en: 2860d8acc82e4a85 - zh: 7b09ac61a684e36d -/agent-note-right-sidebar-tab-types-and-navigation/problem: - en: 1db34c36cf2dc2bd - zh: 4d4e8d3f09ac18c6 -/agent-note-right-sidebar-tab-types-and-navigation/decision: - en: 905fd6fbedad540f - zh: 5420c0dff38012d8 -/agent-note-right-sidebar-tab-types-and-navigation/decision/the-type-registry-ctx-sidebarrighttabs: - en: 9c0a993b39b2c92f - zh: 0e0c008e425f47e4 -/agent-note-right-sidebar-tab-types-and-navigation/decision/bodies-and-titles-keyed-slot-seats-under-the-definition-s-id: - en: 9e80f2d213b394fe - zh: 79f2e5c504b9863e -/agent-note-right-sidebar-tab-types-and-navigation/decision/tab-occurrence-information: - en: 774c3bf80a48ffbe - zh: c513245789aa8efd -/agent-note-right-sidebar-tab-types-and-navigation/decision/navigation-ctx-sidebarright: - en: c52363f57001b7ac - zh: bbcbdbc038cbf316 -/agent-note-right-sidebar-tab-types-and-navigation/decision/addresses: - en: eeb1e7e7478a2f2c - zh: bddbc5bc78245f63 -/agent-note-right-sidebar-tab-types-and-navigation/decision/entry-points: - en: 6386bde2654f9a0a - zh: 700439be6a01e162 -/agent-note-right-sidebar-tab-types-and-navigation/alternatives-considered: - en: c47eb4e2f3c307f9 - zh: fd4515b8be88506e -/agent-note-right-sidebar-tab-types-and-navigation/consequences: - en: 3cdb1159ee55d851 - zh: dcd068005e678a89 -/agent-note-right-sidebar-tab-types-and-navigation/testing: - en: 49449a6c8465c098 - zh: 1f476cc1226aa9cd -/agent-note-right-sidebar-tab-types-and-navigation/deferred: - en: 5b2416692028bf89 - zh: 65930102aa253bf2 diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml index 561af11c1a..38d42e7d93 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.i18n.yaml @@ -9,14 +9,14 @@ en: 55d969715a48c9b3 zh: 755dab7523cd01c2 /agent-note-workspace-file-service/decision: - en: 6ab2a1f635cfc87c - zh: 369e3ed9b6cdc159 + en: 17137a4e49e56470 + zh: ed08cf3d2d7348c8 /agent-note-workspace-file-service/decision/package-topology: - en: 59c3648960630ff8 - zh: 1891f8659e660e45 + en: 5a8d81c95c031663 + zh: 5bdeebe3b5e759c9 /agent-note-workspace-file-service/decision/the-workspacefiles-file-operations: - en: efdecd9cf80c03e7 - zh: 5ea40e48ab0559a2 + en: 434a2f6b3fab4756 + zh: ea83124d00c6a79c /agent-note-workspace-file-service/decision/paths-on-the-wire: en: 7b5a96adc08177bc zh: ae4692e5e62e7de0 @@ -36,11 +36,11 @@ en: 0d42d4716dde706f zh: 4e95b8af2df232b6 /agent-note-workspace-file-service/decision/related-notes: - en: 6e8cfa03b5222af6 - zh: efdf8f17757fc378 + en: ef015f605f7b00ab + zh: eee9c79faebf3dff /agent-note-workspace-file-service/alternatives-considered: - en: 3d598eb6d03d05e5 - zh: 7dac0b7af471a651 + en: 8eee658526db4465 + zh: 4a655accf6a5c08a /agent-note-workspace-file-service/consequences: en: 246bb7aed62f6c9b zh: debf6d4129ee477b diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md index 7deb0dac14..02937e54e2 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.md @@ -12,11 +12,11 @@ Two constraints frame the service. File reads through `ctx.fs` use the Session's ## Decision -`packages/api/workspace-files` (`@deepseek-ai/dsh-api-workspace-files`) owns the Host `ctx.workspaceFiles` service, the `workspaceFiles` Remote namespace, and the Client `file` provider that turns `stat` and `changes` into live metadata for the [resource model](2026-09-05-client-resource-model.md); [dual-face packaging](2026-09-07-workspace-files-dual-face-package.md) governs their package organization. File methods resolve relative paths from the workspace root but inherit the Session filesystem backend's read authority; `list` and `changes` remain workspace-scoped. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) owns this split and its security consequences. Results name files by their absolute path in the filesystem's execution world, and content is bounded by page, byte window, or complete-file cap. The byte window rides on a new `dsh-fs` seam, `FileSystem.readByteRange`, implemented by every provider. The Session Controller carries no workspace-file code. +`packages/api/workspace-files` (`@deepseek-ai/dsh-api-workspace-files`) owns the Host `ctx.workspaceFiles` service, the `workspaceFiles` Remote namespace, and the Client `file` provider that turns `stat` and `changes` into live metadata for the [resource model](2026-09-05-client-resource-model.md); [dual-face packaging](../../../../packages/api/workspace-files/README.md) governs their package organization. File methods resolve relative paths from the workspace root but inherit the Session filesystem backend's read authority; `list` and `changes` remain workspace-scoped. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) owns this split and its security consequences. Results name files by their absolute path in the filesystem's execution world, and content is bounded by page, byte window, or complete-file cap. The byte window rides on a new `dsh-fs` seam, `FileSystem.readByteRange`, implemented by every provider. The Session Controller carries no workspace-file code. ### Package topology -[dual-face packaging](2026-09-07-workspace-files-dual-face-package.md) supersedes this note's choice of separate Host and Client packages; the file service, paging, and change-feed decisions here remain in force. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) supersedes the original workspace-containment choice for file methods. Host and Client compile in separate leaf configurations, share wire types, and the Client does not import the Host runtime entry. +[dual-face packaging](../../../../packages/api/workspace-files/README.md) supersedes this note's choice of separate Host and Client packages; the file service, paging, and change-feed decisions here remain in force. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) supersedes the original workspace-containment choice for file methods. Host and Client compile in separate leaf configurations, share wire types, and the Client does not import the Host runtime entry. | Face | Package | Files | Depends on | |---|---|---|---| @@ -41,7 +41,7 @@ Every Host method takes `WorkspaceFileScope` first. The `workspaceFileScope` Gat - **`read`** returns one window of lines, `WorkspaceFileText = WorkspaceFileStat & { offset, text, lines, eof }`; `lines` counts the page's lines, so a page holding one empty line (`text: ''`, `lines: 1`) and a page past the end (`lines: 0`) read differently. `range.offset` is the 1-based first line and defaults to 1; `range.limit` is the largest number of lines and defaults to `maxLines`, which it may not exceed. Lines end at `\n` and a final `\n` terminates the last line rather than opening an empty one; `text` joins the page's lines with `\n` and carries no terminator; `eof` is true when the page includes the last line, and an offset past the end returns an empty page with `eof` true. The pager walks `streamText`, counts the lines before the window without keeping them, admits each in-window segment against `maxBytes` before buffering it, and returns at the first character past the window, so a file of any size costs one page of memory. The `version` and `bytes` on a page are the stat's, taken before the stream. - **`readBytes`** returns native bytes with file metadata, offset, and EOF. The required options object selects a complete read under `maxFileBytes` when `range` is absent, or a bounded window under `maxBytes` when present. `range: {}` uses offset zero and the configured window cap; windows can read files above the complete-file cap. The [binary transfer decision](2026-09-17-workspace-file-binary-transfer.md) owns the Remote response encoding. - **`baseFile`** selects target resolution independently of `range`: the target `path` must be relative and resolves from the base file's directory. The Host applies regular-file checks to both files and preserves backend read authority. [Document Preview](2026-09-08-document-preview-operations.md) owns content loading and address semantics. -- **`list`** returns `WorkspaceDirectoryListing { path, entries, truncated }`: the listed directory as a workspace path relative to the root (empty for the root), its direct children in the backend's stable name order as `{ name, type, size? }`, and whether `maxEntries` cut the list. `type` is `file`, `directory`, or `other`; a symlink child reports the type of what it points to and a dangling one is `other`. A child the listing reports as a directory opens through its resolved target, including a Windows junction, and the listing then names that target; [directory link listing](../bug-fix/2026-09-18-windows-directory-junction-listing.md) owns the rule. Dotfiles are listed; nothing is filtered. +- **`list`** returns `WorkspaceDirectoryListing { path, entries, truncated }`: the listed directory as a workspace path relative to the root (empty for the root), its direct children in the backend's stable name order as `{ name, type, size? }`, and whether `maxEntries` cut the list. `type` is `file`, `directory`, or `other`; a symlink child reports the type of what it points to and a dangling one is `other`. A child the listing reports as a directory opens through its resolved target, including a Windows junction, and the listing then names that target; [directory link listing](../../../../packages/api/workspace-files/README.md) owns the rule. Dotfiles are listed; nothing is filtered. - **`changes`** yields `WorkspaceFileWatchFrame`: `{ kind: 'ready' }` after the observation queue is registered and the workspace root resolves, followed by `{ kind: 'change', change }`. The `WorkspaceFileChange` payload is `{ absolutePath, version }` for a present file or `{ absolutePath, absent: true }` for one observed gone. Its source is `fs/observed` inside the workspace root, never an OS watcher. Observations after the first pull are queued, including during root resolution; cancellation or plugin disposal ends the generation. ### Paths on the wire @@ -107,13 +107,13 @@ The Client export registers one `ResourceProvider<'file'>` into `ctx.resources` ### Related notes -The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`, `useResource`, the `dsh-resource:///…` address grammar, and the reasoning for one resource per address; the [text preview and file tree](../feature/2026-09-05-sidebar-text-preview-and-file-tree.md) are the shipped consumers of `read`, `list`, and the `file` provider; the [right Sidebar docking infrastructure](../feature/2026-09-04-right-sidebar-docking-infrastructure.md) is the surface they open into; [workspace file links](../feature/2026-07-31-web-workspace-file-links.md) is where serving files over HTTP was rejected. Anyone extending this system reaches the same five methods through `remote.workspaceFiles` and the same `file` resource through `useResource<'file'>`; the wire types are published as `@deepseek-ai/dsh-api-workspace-files/types`. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) owns Host read access and the HTML security trade-off; [Document Preview](2026-09-08-document-preview-operations.md) owns content loading and per-tab freshness. +The [resource model](2026-09-05-client-resource-model.md) owns `ctx.resources`, `useResource`, the `dsh-resource:///…` address grammar, and the reasoning for one resource per address; the [text preview and file tree](../../../../packages/client/ui-sidebar-documentpreview/README.md) are the shipped consumers of `read`, `list`, and the `file` provider; the [right Sidebar docking infrastructure](../feature/2026-09-04-right-sidebar-docking-infrastructure.md) is the surface they open into; [historical workspace file links](../../archived/feature/2026-07-31-web-workspace-file-links.md) is where serving files over HTTP was rejected. Anyone extending this system reaches the same five methods through `remote.workspaceFiles` and the same `file` resource through `useResource<'file'>`; the wire types are published as `@deepseek-ai/dsh-api-workspace-files/types`. The [workspace file read authority](2026-09-09-workspace-file-read-authority.md) owns Host read access and the HTML security trade-off; [Document Preview](2026-09-08-document-preview-operations.md) owns content loading and per-tab freshness. ## Alternatives considered **Keeping the workspace file endpoint on the Session Controller.** The first form: one `read` under a total byte cap, registered as a sub-plugin of the Session Controller because that is where the wire entry already was. Rejected because a Workspace File service is its own capability — reading and statting files plus listing and observing the workspace — and those queries belong together, while the Session Controller's concern is session lifecycle. The move also let the service grow to five methods without the Controller's file gaining a second purpose. -**A dual-face package with reverse UI dependencies.** The split-package choice followed two project-reference cycles after `api/remotes` referenced the Client leaf: the resource model imported Remote assembly for result types, and the file provider imported Sidebar UI for its parameter map. TypeScript rejected these cycles with `TS6202`. [dual-face packaging](2026-09-07-workspace-files-dual-face-package.md) supersedes that split: result types come directly from the protocol package, and Sidebar parameter registration belongs to the text preview; both root aggregates retain explicit compiler entries. +**A dual-face package with reverse UI dependencies.** The split-package choice followed two project-reference cycles after `api/remotes` referenced the Client leaf: the resource model imported Remote assembly for result types, and the file provider imported Sidebar UI for its parameter map. TypeScript rejected these cycles with `TS6202`. [dual-face packaging](../../../../packages/api/workspace-files/README.md) supersedes that split: result types come directly from the protocol package, and Sidebar parameter registration belongs to the text preview; both root aggregates retain explicit compiler entries. **Serving navigable workspace documents.** Rejected on origin grounds; the [binary transfer](2026-09-17-workspace-file-binary-transfer.md) returns multipart data while HTML remains isolated in a Blob iframe. diff --git a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md index fe1dd00000..60b9597225 100644 --- a/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-05-workspace-files-service.zh.md @@ -12,11 +12,11 @@ Web 客户端需要从一个未必在 Host 机器上的浏览器查看会话工 ## Decision -`packages/api/workspace-files`(`@deepseek-ai/dsh-api-workspace-files`)同时拥有 Host 服务 `ctx.workspaceFiles`、`workspaceFiles` Remote 命名空间,以及将 `stat` 与 `changes` 转成[资源模型](2026-09-05-client-resource-model.zh.md)实时元数据的 Client `file` 提供者;包组织方式由[双面包组织](2026-09-07-workspace-files-dual-face-package.zh.md)规定。文件方法从工作区根解析相对路径,但继承 Session 文件系统后端的读取权限;`list` 与 `changes` 仍限于工作区。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)拥有这一分层及其安全后果。结果以文件在文件系统执行环境中的绝对路径命名文件,内容则受页、字节窗口或整文件上限约束。字节窗口依托 `dsh-fs` 新增的 seam `FileSystem.readByteRange`,由每个提供者实现。Session Controller 不再携带任何工作区文件代码。 +`packages/api/workspace-files`(`@deepseek-ai/dsh-api-workspace-files`)同时拥有 Host 服务 `ctx.workspaceFiles`、`workspaceFiles` Remote 命名空间,以及将 `stat` 与 `changes` 转成[资源模型](2026-09-05-client-resource-model.zh.md)实时元数据的 Client `file` 提供者;包组织方式由[双面包组织](../../../../packages/api/workspace-files/README.zh.md)规定。文件方法从工作区根解析相对路径,但继承 Session 文件系统后端的读取权限;`list` 与 `changes` 仍限于工作区。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)拥有这一分层及其安全后果。结果以文件在文件系统执行环境中的绝对路径命名文件,内容则受页、字节窗口或整文件上限约束。字节窗口依托 `dsh-fs` 新增的 seam `FileSystem.readByteRange`,由每个提供者实现。Session Controller 不再携带任何工作区文件代码。 ### 包拓扑 -[双面包组织](2026-09-07-workspace-files-dual-face-package.zh.md)取代本记录中把 Host 与 Client 分成两个包的组织选择;这里的文件服务、分页和变更流约定保持不变。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)取代文件方法原有的工作区包含选择。Host 与 Client 分别编译在两个叶配置中,共享线路类型,Client 不导入 Host 运行时入口。 +[双面包组织](../../../../packages/api/workspace-files/README.zh.md)取代本记录中把 Host 与 Client 分成两个包的组织选择;这里的文件服务、分页和变更流约定保持不变。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)取代文件方法原有的工作区包含选择。Host 与 Client 分别编译在两个叶配置中,共享线路类型,Client 不导入 Host 运行时入口。 | 面 | 包 | 文件 | 依赖 | |---|---|---|---| @@ -41,7 +41,7 @@ Web 客户端需要从一个未必在 Host 机器上的浏览器查看会话工 - **`read`** 返回一个行窗口 `WorkspaceFileText = WorkspaceFileStat & { offset, text, lines, eof }`;`lines` 计页内行数,使只含一个空行的页(`text: ''`、`lines: 1`)与越过文件末尾的页(`lines: 0`)可区分。`range.offset` 是 1 起算的首行,缺省 1;`range.limit` 是最多行数,缺省 `maxLines` 且不得超过。行以 `\n` 结束,末尾的 `\n` 终止最后一行而不是开启一空行;`text` 以 `\n` 连接本页各行且不带终止符;页含最后一行时 `eof` 为 true,越过末尾的 offset 返回 `eof` 为 true 的空页。切页器沿 `streamText` 前进,数过窗口前的行而不保留,把每个窗内片段先按 `maxBytes` 核准再缓冲,并在越过窗口的第一个字符处返回,因此任意大小的文件只花一页内存。页上的 `version` 与 `bytes` 来自流之前的那次 stat。 - **`readBytes`** 返回原生字节、文件元数据、偏移量和 EOF。必填选项对象未传 `range` 时,在 `maxFileBytes` 上限内完整读取;传入时,在 `maxBytes` 上限内读取有界窗口。`range: {}` 使用零偏移和配置的窗口上限;窗口可读取超过完整文件上限的文件。[二进制传输决议](2026-09-17-workspace-file-binary-transfer.zh.md)负责 Remote 响应编码。 - **`baseFile`** 独立于 `range` 选择目标解析方式:目标 `path` 必须为相对路径,从基准文件所在目录解析。Host 对两个文件执行普通文件检查,并保留后端读取权限。[Document Preview](2026-09-08-document-preview-operations.zh.md)负责内容加载与地址语义。 -- **`list`** 返回 `WorkspaceDirectoryListing { path, entries, truncated }`:被列目录相对根的工作区路径(根为空串)、其直接子项按后端的稳定名序以 `{ name, type, size? }` 给出,以及 `maxEntries` 是否截断了列表。`type` 为 `file`、`directory` 或 `other`;符号链接子项报告其指向目标的类型,悬空者为 `other`。列表报告为目录的子项经其解析目标打开,Windows 目录联接亦然,此时列表以该目标命名;[目录链接列举](../bug-fix/2026-09-18-windows-directory-junction-listing.zh.md)拥有该规则。dotfile 照常列出,不做任何过滤。 +- **`list`** 返回 `WorkspaceDirectoryListing { path, entries, truncated }`:被列目录相对根的工作区路径(根为空串)、其直接子项按后端的稳定名序以 `{ name, type, size? }` 给出,以及 `maxEntries` 是否截断了列表。`type` 为 `file`、`directory` 或 `other`;符号链接子项报告其指向目标的类型,悬空者为 `other`。列表报告为目录的子项经其解析目标打开,Windows 目录联接亦然,此时列表以该目标命名;[目录链接列举](../../../../packages/api/workspace-files/README.zh.md)拥有该规则。dotfile 照常列出,不做任何过滤。 - **`changes`** 产出 `WorkspaceFileWatchFrame`:在观察队列注册且工作区根解析完成后先发 `{ kind: 'ready' }`,随后为 `{ kind: 'change', change }`。载荷 `WorkspaceFileChange` 对存在的文件为 `{ absolutePath, version }`,对消失的文件为 `{ absolutePath, absent: true }`。来源是工作区根内的 `fs/observed`,不监视操作系统。首次拉取后的观察都会排队,包括根解析期间的观察;取消或插件释放会结束该代流。 ### 线路上的路径 @@ -107,13 +107,13 @@ Client 导出向 `ctx.resources` 注册一个 `ResourceProvider<'file'>`,存 ### 相关记录 -[资源模型](2026-09-05-client-resource-model.zh.md)拥有 `ctx.resources`、`useResource`、`dsh-resource:///…` 地址语法以及"每个地址一份资源"的推理;[文本预览与文件树](../feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md)是 `read`、`list` 与 `file` 提供者随包交付的消费方;[右侧 Sidebar 停靠基础设施](../feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md)是它们打开进去的界面;[工作区文件链接](../feature/2026-07-31-web-workspace-file-links.zh.md)是经 HTTP 供文件被否决之处。任何在这套体系上扩展的人都经 `remote.workspaceFiles` 触达同样的五个方法、经 `useResource<'file'>` 触达同样的 `file` 资源;线路类型以 `@deepseek-ai/dsh-api-workspace-files/types` 发布。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)负责 Host 读取权限与 HTML 安全取舍;[Document Preview](2026-09-08-document-preview-operations.zh.md)负责内容加载和逐 tab 新鲜度。 +[资源模型](2026-09-05-client-resource-model.zh.md)拥有 `ctx.resources`、`useResource`、`dsh-resource:///…` 地址语法以及"每个地址一份资源"的推理;[文本预览与文件树](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md)是 `read`、`list` 与 `file` 提供者随包交付的消费方;[右侧 Sidebar 停靠基础设施](../feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md)是它们打开进去的界面;[历史工作区文件链接](../../archived/feature/2026-07-31-web-workspace-file-links.md)是经 HTTP 供文件被否决之处。任何在这套体系上扩展的人都经 `remote.workspaceFiles` 触达同样的五个方法、经 `useResource<'file'>` 触达同样的 `file` 资源;线路类型以 `@deepseek-ai/dsh-api-workspace-files/types` 发布。[工作区文件读取权限](2026-09-09-workspace-file-read-authority.zh.md)负责 Host 读取权限与 HTML 安全取舍;[Document Preview](2026-09-08-document-preview-operations.zh.md)负责内容加载和逐 tab 新鲜度。 ## Alternatives considered **把工作区文件端点留在 Session Controller 上。** 最初形态:总字节上限之下的一个 `read`,作为 Session Controller 的子插件注册,因为线路入口本来就在那里。被否,因为 Workspace File 服务是自己的能力——读取和 stat 文件,并列举和观察工作区——这些查询应归于一处,而 Session Controller 关心的是会话生命周期。搬出也让服务长到五个方法而不给 Controller 的文件添第二重目的。 -**带有反向 UI 依赖的双面包。** 拆包选择源于 `api/remotes` 引用 Client 叶子后形成的两条工程引用环:资源模型为了结果类型引用 Remote 装配,文件提供者为了 Sidebar 参数表引用右栏 UI。TypeScript 以 `TS6202` 拒绝这些环。[双面包组织](2026-09-07-workspace-files-dual-face-package.zh.md)取代拆包选择:结果类型直接取自协议包,Sidebar 参数注册移至文本预览;保留两个根聚合中的显式编译入口。 +**带有反向 UI 依赖的双面包。** 拆包选择源于 `api/remotes` 引用 Client 叶子后形成的两条工程引用环:资源模型为了结果类型引用 Remote 装配,文件提供者为了 Sidebar 参数表引用右栏 UI。TypeScript 以 `TS6202` 拒绝这些环。[双面包组织](../../../../packages/api/workspace-files/README.zh.md)取代拆包选择:结果类型直接取自协议包,Sidebar 参数注册移至文本预览;保留两个根聚合中的显式编译入口。 **提供可导航的工作区文档。** 因源隔离问题不采用;[二进制传输](2026-09-17-workspace-file-binary-transfer.zh.md)返回 multipart 数据,HTML 仍隔离在 Blob iframe 中。 diff --git a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml deleted file mode 100644 index f95de9ad8f..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-embedded-stream-record-readers.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md -/agent-note-embedded-assistant-stream-consumers-read-compact-records: - en: ddc3fed8aa76c1b8 - zh: 409dcb67ef28080e -/agent-note-embedded-assistant-stream-consumers-read-compact-records/problem: - en: 34ceceb99e246ae6 - zh: 0a18314b78557205 -/agent-note-embedded-assistant-stream-consumers-read-compact-records/decision: - en: ad50509c41a7dacf - zh: c8170641203a3184 -/agent-note-embedded-assistant-stream-consumers-read-compact-records/decision/measurements: - en: 2eedf644fa07e35a - zh: cb691c75fbeda518 -/agent-note-embedded-assistant-stream-consumers-read-compact-records/alternatives-considered: - en: 66b6007d582f7521 - zh: a33aa19f7c912fa2 -/agent-note-embedded-assistant-stream-consumers-read-compact-records/consequences: - en: 05cf037a97b4254d - zh: 1a45fae2447678c2 diff --git a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml deleted file mode 100644 index dd4a23fe04..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-sidebar-responsive-tab-info.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-07-sidebar-responsive-tab-info.md -/agent-note-responsive-sidebar-and-injected-tab-information: - en: 63ced560202cf1a3 - zh: 1ec49cd5ae68f909 -/agent-note-responsive-sidebar-and-injected-tab-information/problem: - en: f9824b1d6f93d329 - zh: 36bc474f646a1a25 -/agent-note-responsive-sidebar-and-injected-tab-information/decision: - en: 1a055f79cb0afcde - zh: 78467751791d42d1 -/agent-note-responsive-sidebar-and-injected-tab-information/alternatives-considered: - en: c05a3ca9a201f01a - zh: b1b30e134a92c8ed -/agent-note-responsive-sidebar-and-injected-tab-information/consequences: - en: 852e827be572c626 - zh: 125ac90be15a5187 diff --git a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml deleted file mode 100644 index 2d222100c4..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-workspace-files-dual-face-package.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-07-workspace-files-dual-face-package.md -/agent-note-workspace-files-as-one-dual-face-api-package: - en: fee20bed1c4f69d2 - zh: 8897bf7430250d46 -/agent-note-workspace-files-as-one-dual-face-api-package/problem: - en: 057ac188f6269740 - zh: 4ed1c2f6c3015efb -/agent-note-workspace-files-as-one-dual-face-api-package/decision: - en: 36fdbb0b44b1bb5e - zh: 87a7a9b45231cf28 -/agent-note-workspace-files-as-one-dual-face-api-package/alternatives-considered: - en: 45a93fced31f5980 - zh: 9283fbda48828416 -/agent-note-workspace-files-as-one-dual-face-api-package/consequences: - en: d4558eea4ac3a00b - zh: e625747fd469f146 -/agent-note-workspace-files-as-one-dual-face-api-package/verification: - en: 4c49a2ec778623e8 - zh: 94eacb1ee8e4d36d diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml index 6e302c76a3..72e167ce59 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md /agent-note-bundle-the-desktop-runtime-and-retain-external-plugins: - en: f4c2a14c7f0dbd02 - zh: 5c9b9b68885f3ee0 + en: 6a87387a952b6d28 + zh: 5fd41d2dd5545637 /agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/problem: en: fe97fe6ba33cb23a zh: c83a1f023c4e352d @@ -15,11 +15,11 @@ en: 37cae6b28fd359ef zh: 63118e361fa8f84c /agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/transactions-and-upgrades: - en: 5bdc450be55832fb - zh: 3312fc9e5497e139 + en: 53d48f71fcc1c52e + zh: 7d61923484dbec1c /agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/alternatives-considered: - en: 1558bba473382cbb - zh: b6a6467866ee254d + en: ead5bbd94044c3de + zh: 057cf069413d2a49 /agent-note-bundle-the-desktop-runtime-and-retain-external-plugins/consequences: en: 6dfa581ec6879c5b zh: f6c735ad6a00b2b3 diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md index 61fa9eafb7..f8f6599fe6 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.md @@ -6,7 +6,7 @@ English | [中文](2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md Plugin management and native recovery follow the [shared Web wrapper decision](2026-09-10-desktop-web-wrapper.md). -The [Electron runtime decision](2026-09-11-desktop-electron-node-runtime.md) supersedes the separate upstream Node executable; other decisions in this note remain applicable. +Desktop uses Electron as its Node runtime under the [current runtime reference](../../../../apps/desktop/README.md); other decisions in this note remain applicable. ## Problem @@ -44,11 +44,11 @@ The shared [plugin manager](../../../../packages/boot/plugin-manager/README.md) The shared Web plugin manager owns package mutations and activation; Electron retains profile preparation and native recovery. The [Web wrapper decision](2026-09-10-desktop-web-wrapper.md) owns these responsibilities. -The [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-start.md) owns direct Host startup. The native recovery dialog can disable third-party bundles and back up the profile patch when the Web application cannot start. Installed plugin files remain available for repair. +The [immediate-window reference](../../../../apps/desktop/README.md) owns direct Host startup. The native recovery dialog can disable third-party bundles and back up the profile patch when the Web application cannot start. Installed plugin files remain available for repair. ## Alternatives considered -Full runtime verification belongs to packaging. Startup reads the resource descriptor and checks shared package records and required Host entries. The [release-validation decision](2026-09-09-desktop-build-release-validation.md) assigns release and target compatibility checks to packaging. Startup neither enumerates nor hashes installed runtime files. Reading every file before backend loading adds I/O proportional to distribution size; unusable modules instead fail when loaded. Build-time verification rejects changed, missing, extra, or linked files against the recorded inventory. +Full runtime verification belongs to packaging. Startup reads the resource descriptor and checks shared package records and required Host entries. The [release-validation reference](../../../../apps/desktop/README.md) assigns release and target compatibility checks to packaging. Startup neither enumerates nor hashes installed runtime files. Reading every file before backend loading adds I/O proportional to distribution size; unusable modules instead fail when loaded. Build-time verification rejects changed, missing, extra, or linked files against the recorded inventory. - **Install the bundled offline seed at startup.** This preserves an ordinary pnpm installation procedure but repeats core extraction and installation on every affected machine. Materialized resources remove that work at the cost of more application files and release-builder responsibility. - **Force host dependency versions into plugins.** This unnecessarily couples ordinary plugin dependencies to the host. The runtime resolution supplies missing packages while pnpm-owned entries retain independent versions. diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md index 8d94d58235..6d14494858 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-bundled-runtime-and-external-plugins.zh.md @@ -6,7 +6,7 @@ Status: implemented 插件管理和原生恢复遵循[共享 Web 薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)。 -[Electron 运行时决策](2026-09-11-desktop-electron-node-runtime.zh.md)替代独立上游 Node 可执行文件的选择;本文其他决策仍然适用。 +Desktop 按[当前运行时参考](../../../../apps/desktop/README.zh.md)使用 Electron 作为 Node 运行时;本文其他决策仍然适用。 ## 问题 @@ -44,11 +44,11 @@ profile manifest 分别记录 pnpm 安装的依赖及已启用 bundle 列表。 共享 Web 插件管理器负责包变更和激活;Electron 保留 profile 准备和原生恢复。[Web 薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责这些职责。 -[立即显示窗口决策](2026-09-09-desktop-immediate-window-and-direct-start.zh.md)负责直接启动 Host。Web 应用无法启动时,原生恢复对话框可禁用第三方 bundle 并备份 profile patch。已安装插件文件保留以供修复。 +[立即显示窗口参考](../../../../apps/desktop/README.zh.md)负责直接启动 Host。Web 应用无法启动时,原生恢复对话框可禁用第三方 bundle 并备份 profile patch。已安装插件文件保留以供修复。 ## 考虑过的替代方案 -完整运行时验证属于打包流程。启动读取资源描述文件,并检查共享包记录及必要的 Host 入口。[发布验证决策](2026-09-09-desktop-build-release-validation.zh.md)将发布与目标兼容性检查交给打包流程。启动既不枚举已安装运行时文件,也不计算其哈希。后端加载前读取每个文件会增加与分发体积成正比的 I/O;不可用模块改由加载时失败暴露。构建时验证按记录清单拒绝内容变化、缺失、多余或链接文件。 +完整运行时验证属于打包流程。启动读取资源描述文件,并检查共享包记录及必要的 Host 入口。[发布验证参考](../../../../apps/desktop/README.zh.md)将发布与目标兼容性检查交给打包流程。启动既不枚举已安装运行时文件,也不计算其哈希。后端加载前读取每个文件会增加与分发体积成正比的 I/O;不可用模块改由加载时失败暴露。构建时验证按记录清单拒绝内容变化、缺失、多余或链接文件。 - **启动时安装内置离线 seed。** 这保留普通 pnpm 安装流程,但会在每台受影响机器上重复核心解压与安装。物化资源消除了这部分工作,代价是更多应用文件和发布构建器责任。 - **强制插件使用 Host 依赖版本。** 这会让普通插件依赖不必要地耦合于 Host。runtime resolution 提供缺失的包,pnpm 拥有的条目保留独立版本。 diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.i18n.yaml index 9de126a4e4..98414d1dd2 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.i18n.yaml @@ -9,8 +9,8 @@ en: 8578b584bda3abdc zh: 85dcf44aab2cfd51 /agent-note-desktop-welcome-window-material/decision: - en: df7f9dc0c48769ab - zh: 5e667c736dd58a1c + en: 1affa7f1fccc945f + zh: e5aea959e7886a59 /agent-note-desktop-welcome-window-material/alternatives-considered: en: cf42e200b1d0f00a zh: de96b5647f5ffb23 diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.md index e3961481f8..e285a57699 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.md @@ -12,7 +12,7 @@ The desktop welcome design blurs the desktop behind its entire window. Chromium The [Electron welcome window](../../../../apps/desktop/src/welcome-window.ts) owns native material and window controls. Its independently bundled React renderer supplies the entry, account sign-in states and API-key form. It shares the `StateDot` loading component with the Web UI without booting the Web plugin graph. The Desktop build emits local JavaScript and CSS under `lib/welcome`, included in packaged applications; the document keeps its network-denying content security policy. A narrow preload supplies typed shell copy, a write-only key operation, and a skip operation; IPC rejects other windows and subframes. Each sandboxed preload is bundled independently because Electron’s restricted require cannot load sibling chunks. The Electron main process authenticates through the shared Web launch URL and resolves the official provider's reference through the existing settings and credential RPC methods. The [thin Web wrapper](2026-09-10-desktop-web-wrapper.md) owns the HTTP server; onboarding adds no Host endpoint or child IPC operation. Responses contain metadata or a safe outcome, never a key or private provider diagnostics. -Cold startup opens the entry when neither an account credential nor a model API key is configured. Saving enters the workspace after persistence succeeds; skipping enters without saving a draft or a credential-entry completion flag. The next process launch checks credentials again. The Desktop preload marker suppresses the automatic Web credential step and welcome notice; settings and explicit API-key editing remain available. The [Desktop onboarding decision](../feature/2026-09-16-desktop-onboarding.md) owns the separate device-local introduction after account login. Other native shells can disable only the credential step through the Models Host plugin's `credentialOnboarding` page-injection value. The module graph carries package identities rather than arbitrary Host config, so a Host row alone does not configure its Client half. The [account provider](2026-09-14-deepseek-account-login.md) supplies login and sign-out state while preserving independently stored API keys. +Cold startup opens the entry when neither an account credential nor a model API key is configured. Saving enters the workspace after persistence succeeds; skipping enters without saving a draft or a credential-entry completion flag. The next process launch checks credentials again. The Desktop preload marker suppresses the automatic Web credential step and welcome notice; settings and explicit API-key editing remain available. The [Desktop onboarding reference](../../../../packages/client/ui-settings-account/README.md) owns the separate device-local introduction after account login. Other native shells can disable only the credential step through the Models Host plugin's `credentialOnboarding` page-injection value. The module graph carries package identities rather than arbitrary Host config, so a Host row alone does not configure its Client half. The [account provider](2026-09-14-deepseek-account-login.md) supplies login and sign-out state while preserving independently stored API keys. The generated project follows declared workspace dependencies because pnpm’s hoist index alone can omit configured plugins. diff --git a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.zh.md b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.zh.md index 17504368e8..e708b53447 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-08-desktop-welcome-window-material.zh.md @@ -12,7 +12,7 @@ Status: implemented [Electron 欢迎窗口](../../../../apps/desktop/src/welcome-window.ts)负责原生材质和窗口控件。独立打包的 React 渲染器提供入口、账号登录状态和 API Key 表单。它与 Web UI 共用 `StateDot` 加载组件,不启动 Web 插件图。Desktop 构建将本地 JavaScript 和 CSS 输出到 `lib/welcome`,并纳入应用安装包;文档继续使用禁止网络访问的内容安全策略。窄接口 preload 提供强类型桌面文案、仅写密钥操作和跳过操作;IPC 拒绝其他窗口和子 frame。每个沙箱 preload 都独立打包,因为 Electron 受限的 require 无法加载同目录中的拆分模块。Electron 主进程通过共享 Web 启动 URL 认证,并通过现有设置和凭证 RPC 方法解析官方提供方的引用。[Web 薄壳](2026-09-10-desktop-web-wrapper.zh.md)负责 HTTP 服务;引导不增加 Host 端点或子进程 IPC 操作。响应只包含元数据或安全结果,不包含密钥或提供方的私有诊断。 -冷启动在账号凭证与模型 API Key 均未配置时打开入口。保存会在持久化成功后进入工作区;跳过会直接进入,不保存草稿或凭证入口完成标记。下次进程启动时会重新检查凭证。Desktop preload 标记抑制自动 Web 凭证步骤和欢迎须知;设置与显式 API Key 编辑仍然可用。[Desktop 引导决策](../feature/2026-09-16-desktop-onboarding.zh.md)负责账号登录后独立的设备本地介绍流程。其他原生壳可以通过 Models Host 插件注入页面的 `credentialOnboarding` 值仅禁用凭证步骤。模块图传递的是包标识,而不是任意 Host 配置,因此单独配置 Host 行不会配置其 Client 半部。[账号提供方](2026-09-14-deepseek-account-login.zh.md)提供登录与退出登录状态,同时保留独立存储的 API Key。 +冷启动在账号凭证与模型 API Key 均未配置时打开入口。保存会在持久化成功后进入工作区;跳过会直接进入,不保存草稿或凭证入口完成标记。下次进程启动时会重新检查凭证。Desktop preload 标记抑制自动 Web 凭证步骤和欢迎须知;设置与显式 API Key 编辑仍然可用。[Desktop 引导参考](../../../../packages/client/ui-settings-account/README.zh.md)负责账号登录后独立的设备本地介绍流程。其他原生壳可以通过 Models Host 插件注入页面的 `credentialOnboarding` 值仅禁用凭证步骤。模块图传递的是包标识,而不是任意 Host 配置,因此单独配置 Host 行不会配置其 Client 半部。[账号提供方](2026-09-14-deepseek-account-login.zh.md)提供登录与退出登录状态,同时保留独立存储的 API Key。 生成的开发项目沿已声明的 workspace 依赖建立链接,因为单独依赖 pnpm 的提升索引会漏掉配置插件。 diff --git a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.i18n.yaml index ec7374517f..064ed4f762 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.i18n.yaml @@ -9,8 +9,8 @@ en: a35075e6babe3f7a zh: 3a64ac57761e1009 /agent-note-document-preview-and-file-addresses/decision: - en: d0a2923efefbcfe1 - zh: 34f71d092c0e706b + en: 75b40970e401f2e3 + zh: 970b2cc044ea17f4 /agent-note-document-preview-and-file-addresses/alternatives-considered: en: 8e9616e954df488c zh: 10801341ec53bbe8 diff --git a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.md b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.md index 220db6db04..55845a7718 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.md +++ b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.md @@ -16,9 +16,9 @@ Document Preview separates resource observation from content reads. The [resourc Readable files use `dsh-resource://file/session//`. The path may be workspace-relative or absolute; an encoded absolute path retains its leading slash. `fileAddressFor` always emits this Session-address form. The provider and Preview RPC take the Session only from that address, never from the current selection, first holder, or owning tab. A Session-less `absolute` URI cannot be read; the provider reports `workspace-file/unknown-workspace`. Session authorization is a file-protocol rule, not an additional Resource identity. -[Document Preview](../../../../packages/client/ui-sidebar-documentpreview/README.md) owns format selection and loading policy. Metadata registers with `ctx.documentPreviews`; components register separately into the keyed `sidebar.right.tab.document` Slot. Extension registrations precede builtins, then longer suffixes and registration order decide. The toolbar lists supported alternatives and remembers a manual choice per tab. Plain text remains available for unknown extensions and text-compatible renderers. Registered binary suffixes determine text compatibility independently of loading mode; HTML and SVG remain outside those suffixes and retain source viewing. A single candidate renders no viewer control. Registered binary suffixes omit plain text; known binary suffixes without a renderer show an unsupported state without reading the file ([sidebar preview polish](../feature/2026-09-11-sidebar-document-preview-polish.md)). The child receives accumulated text or complete native bytes, the original resource address, and the standard `useResource` and `useTabInfo` hooks. Preview calls `ctx.remote.workspaceFiles.read` for text and `ctx.remote.workspaceFiles.readBytes` for native bytes, with `baseFile` for related resources. Refresh remains per tab, with no resource reload, shared `changed` acknowledgement, extra resource wrapper, or content Session. +[Document Preview](../../../../packages/client/ui-sidebar-documentpreview/README.md) owns format selection and loading policy. Metadata registers with `ctx.documentPreviews`; components register separately into the keyed `sidebar.right.tab.document` Slot. Extension registrations precede builtins, then longer suffixes and registration order decide. The toolbar lists supported alternatives and remembers a manual choice per tab. Plain text remains available for unknown extensions and text-compatible renderers. Registered binary suffixes determine text compatibility independently of loading mode; HTML and SVG remain outside those suffixes and retain source viewing. A single candidate renders no viewer control. Registered binary suffixes omit plain text; known binary suffixes without a renderer show an unsupported state without reading the file ([sidebar preview polish](../../../../packages/client/ui-sidebar-documentpreview/README.md)). The child receives accumulated text or complete native bytes, the original resource address, and the standard `useResource` and `useTabInfo` hooks. Preview calls `ctx.remote.workspaceFiles.read` for text and `ctx.remote.workspaceFiles.readBytes` for native bytes, with `baseFile` for related resources. Refresh remains per tab, with no resource reload, shared `changed` acknowledgement, extra resource wrapper, or content Session. -Markdown and code reuse the incremental primitives with cumulative paged text. HTML, PDF, and images read complete `Uint8Array` data; Host transport uses the [binary workspace file transfer](2026-09-17-workspace-file-binary-transfer.md). Published buffers are borrowed read-only and never persist into layout or Session JSON. PDF.js runs in an owned Worker with version-matched bundled font and decoder data, and copies input before transfer to preserve Preview's retained buffer. With [Developer tools](../feature/2026-09-17-developer-tools-settings.md) enabled, HTML runs in a Blob iframe with `sandbox="allow-scripts"`, without same-origin, popup, form, download, or top-navigation privileges. The browser retains its normal external-network rules. Bounded static local JS/CSS reads stay in the parent; the opaque frame creates its own asset Blobs, because it cannot load parent-origin Blobs. PNG, JPEG, GIF, WebP, BMP, ICO, and SVG use image-specific Blob URLs in an `` static-image context. An image wider than the pane scales down to its width at its aspect ratio; a smaller image keeps its intrinsic CSS-pixel dimensions centred by auto margins, and a taller image extends the shared scroller's vertical range ([sidebar preview polish](../feature/2026-09-11-sidebar-document-preview-polish.md)). The renderer provides no zoom or drag-to-pan. SVG markup never enters the application DOM or an iframe, so scripts remain inert and cannot reach the parent page. Replacing HTML or an image revokes its root Blob URL. +Markdown and code reuse the incremental primitives with cumulative paged text. HTML, PDF, and images read complete `Uint8Array` data; Host transport uses the [binary workspace file transfer](2026-09-17-workspace-file-binary-transfer.md). Published buffers are borrowed read-only and never persist into layout or Session JSON. PDF.js runs in an owned Worker with version-matched bundled font and decoder data, and copies input before transfer to preserve Preview's retained buffer. With [Developer tools](../../../../packages/client/ui-sidebar-documentpreview/README.md) enabled, HTML runs in a Blob iframe with `sandbox="allow-scripts"`, without same-origin, popup, form, download, or top-navigation privileges. The browser retains its normal external-network rules. Bounded static local JS/CSS reads stay in the parent; the opaque frame creates its own asset Blobs, because it cannot load parent-origin Blobs. PNG, JPEG, GIF, WebP, BMP, ICO, and SVG use image-specific Blob URLs in an `` static-image context. An image wider than the pane scales down to its width at its aspect ratio; a smaller image keeps its intrinsic CSS-pixel dimensions centred by auto margins, and a taller image extends the shared scroller's vertical range ([sidebar preview polish](../../../../packages/client/ui-sidebar-documentpreview/README.md)). The renderer provides no zoom or drag-to-pan. SVG markup never enters the application DOM or an iframe, so scripts remain inert and cannot reach the parent page. Replacing HTML or an image revokes its root Blob URL. PDF.js's official TextLayerBuilder owns selection boundaries and copy normalization over the width-fitted canvas, with shared page cleanup and a component-owned resize observer. Responsive sizing uses the CSS `scale` property independently of PDF.js's page rotation and translation transforms. Its end-of-content marker and stacking rules constrain selection in blank regions; line-break highlighting is suppressed. Per-page cancellation uses the builder's cleanup rather than aborting the first page's signal, because the official selection listeners are shared across pages. diff --git a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md index 7b838711e5..2945cc823d 100644 --- a/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md @@ -16,9 +16,9 @@ Document Preview 将资源观察与内容读取分开。[资源模型](2026-09-0 可读取的文件使用 `dsh-resource://file/session//`。路径可以相对工作区,也可以是绝对路径;编码后的绝对路径保留前导斜杠。`fileAddressFor` 始终生成这种 Session 地址。提供方与 Preview RPC 只从该地址取 Session,不取当前选择、首个持有者或 tab 所属 Session。不带 Session 的 `absolute` URI 无法读取;提供方报告 `workspace-file/unknown-workspace`。Session 授权是文件协议规则,不是额外的 Resource 身份。 -[Document Preview](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md) 负责格式选择和加载策略。元数据通过 `ctx.documentPreviews` 注册;组件单独注册到 keyed `sidebar.right.tab.document` Slot。扩展注册优先于内置注册,其次比较后缀长度和注册顺序。工具栏列出受支持的候选,按 tab 记住手动选择。未知扩展名及文本兼容的渲染器仍可使用纯文本。文本兼容性由注册的二进制后缀决定,与加载方式无关;HTML 和 SVG 不属于二进制后缀,保留源码查看。只有一个候选时不显示查看器控件。注册声明为二进制的后缀不提供纯文本;已知二进制后缀没有注册渲染器时不发起读取,并显示不支持预览的空态([侧边栏预览打磨](../feature/2026-09-11-sidebar-document-preview-polish.zh.md))。子组件收到累积文本或完整原生字节、原始资源地址,以及标准 `useResource` 和 `useTabInfo` 钩子。Preview 通过 `ctx.remote.workspaceFiles.read` 读取文本,通过 `ctx.remote.workspaceFiles.readBytes` 读取原生字节,关联资源使用 `baseFile`。刷新仍按 tab 独立进行,不引入资源 reload、共享 `changed` 确认、额外资源包装层或内容 Session。 +[Document Preview](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md) 负责格式选择和加载策略。元数据通过 `ctx.documentPreviews` 注册;组件单独注册到 keyed `sidebar.right.tab.document` Slot。扩展注册优先于内置注册,其次比较后缀长度和注册顺序。工具栏列出受支持的候选,按 tab 记住手动选择。未知扩展名及文本兼容的渲染器仍可使用纯文本。文本兼容性由注册的二进制后缀决定,与加载方式无关;HTML 和 SVG 不属于二进制后缀,保留源码查看。只有一个候选时不显示查看器控件。注册声明为二进制的后缀不提供纯文本;已知二进制后缀没有注册渲染器时不发起读取,并显示不支持预览的空态([侧边栏预览打磨](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md))。子组件收到累积文本或完整原生字节、原始资源地址,以及标准 `useResource` 和 `useTabInfo` 钩子。Preview 通过 `ctx.remote.workspaceFiles.read` 读取文本,通过 `ctx.remote.workspaceFiles.readBytes` 读取原生字节,关联资源使用 `baseFile`。刷新仍按 tab 独立进行,不引入资源 reload、共享 `changed` 确认、额外资源包装层或内容 Session。 -Markdown 和代码通过累积的分页文本复用增量渲染原语。HTML、PDF 和图片读取完整 `Uint8Array` 数据;Host 传输使用[工作区文件二进制传输](2026-09-17-workspace-file-binary-transfer.zh.md)。发布后的缓冲区只读借用,绝不持久化进布局或 Session JSON。PDF.js 在自有 Worker 中运行,字体和解码数据以相同版本随包发布,转移输入前先复制,以保留 Preview 的缓冲区。开启[开发者工具](../feature/2026-09-17-developer-tools-settings.zh.md)时,HTML 在 Blob iframe 中运行,设置 `sandbox="allow-scripts"`,不授予同源、弹窗、表单、下载或顶层导航权限。浏览器保持正常的外部网络规则。有上限的静态本地 JS/CSS 读取由父页面负责;不透明源 iframe 创建自己的资源 Blob,因为它不能加载父源创建的 Blob。PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 使用图片专用 Blob URL,在 `` 静态图片上下文中渲染。比面板宽的图片按纵横比缩小到面板宽度;较小的图片保留固有 CSS 像素尺寸并由 auto margin 居中,较高的图片扩展共享滚动区的纵向范围([侧边栏预览打磨](../feature/2026-09-11-sidebar-document-preview-polish.zh.md))。渲染器不提供缩放或拖拽平移。SVG 标记绝不进入应用 DOM 或 iframe,因此脚本保持不可执行,也无法访问父页面。替换 HTML 或图片时会撤销其根 Blob URL。 +Markdown 和代码通过累积的分页文本复用增量渲染原语。HTML、PDF 和图片读取完整 `Uint8Array` 数据;Host 传输使用[工作区文件二进制传输](2026-09-17-workspace-file-binary-transfer.zh.md)。发布后的缓冲区只读借用,绝不持久化进布局或 Session JSON。PDF.js 在自有 Worker 中运行,字体和解码数据以相同版本随包发布,转移输入前先复制,以保留 Preview 的缓冲区。开启[开发者工具](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md)时,HTML 在 Blob iframe 中运行,设置 `sandbox="allow-scripts"`,不授予同源、弹窗、表单、下载或顶层导航权限。浏览器保持正常的外部网络规则。有上限的静态本地 JS/CSS 读取由父页面负责;不透明源 iframe 创建自己的资源 Blob,因为它不能加载父源创建的 Blob。PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 使用图片专用 Blob URL,在 `` 静态图片上下文中渲染。比面板宽的图片按纵横比缩小到面板宽度;较小的图片保留固有 CSS 像素尺寸并由 auto margin 居中,较高的图片扩展共享滚动区的纵向范围([侧边栏预览打磨](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md))。渲染器不提供缩放或拖拽平移。SVG 标记绝不进入应用 DOM 或 iframe,因此脚本保持不可执行,也无法访问父页面。替换 HTML 或图片时会撤销其根 Blob URL。 PDF.js 官方 TextLayerBuilder 在适配宽度的 canvas 上负责选择边界和复制规范化,共享页面清理,并使用由组件拥有的 resize observer。响应式尺寸适配使用独立的 CSS `scale` 属性,与 PDF.js 的页面旋转和平移变换组合。其内容结束标记和堆叠规则限制空白区域中的选择;换行高亮被抑制。逐页取消使用 builder 的清理操作,而不 abort 第一页的信号,因为官方选择监听器跨页面共享。 diff --git a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.i18n.yaml deleted file mode 100644 index d16bba3786..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-08-global-main-panels.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-global-main-panels.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-08-global-main-panels.md -/agent-note-global-main-panels-without-default-ui-additions: - en: df950323f9032f3b - zh: d08172be0c9ca71c -/agent-note-global-main-panels-without-default-ui-additions/problem: - en: da6aa479acac3baf - zh: ed6316ea9953ff99 -/agent-note-global-main-panels-without-default-ui-additions/decision: - en: e732d64ac12c0ac5 - zh: e8ff462ae93bf259 -/agent-note-global-main-panels-without-default-ui-additions/alternatives-considered: - en: 18c8461d961490ee - zh: a26b37ff3aa15bbc -/agent-note-global-main-panels-without-default-ui-additions/consequences: - en: 31db82c87448a292 - zh: 1923a60d2f0845f9 diff --git a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.i18n.yaml index a8255aa82a..47d3a65a12 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.i18n.yaml @@ -9,8 +9,8 @@ en: 03e6b75365b29406 zh: cc880a33cf2303d0 /agent-note-awaited-per-agent-initialization/decision: - en: f1db71d1fdb4388e - zh: 6c5a43a770f4cffd + en: 48cdb570d3feb222 + zh: 904a1169540e3111 /agent-note-awaited-per-agent-initialization/alternatives-considered: en: 466a9ebbb13f68c6 zh: ab88fb34786d2c2b diff --git a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.md b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.md index 27353225a8..40dcec53a8 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.md +++ b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.md @@ -16,7 +16,7 @@ AgentLoop holds its existing maintenance activity through setup and creation dis Creation dispatch retains the scope and Session while listeners await. Disposal cancels initialization and joins the dispatch before releasing those resources. A listener must not await its own Agent's idle state or its owner's disposal, because each waits for that listener to finish. Background work on another Agent follows that Agent's own initialization lifecycle. -This decision owns asynchronous creation timing. The [scope runtime decision](2026-07-12-agent-scope-runtime-design.md) retains registry identity and teardown ownership, while the [interception decision](../feature/2026-06-30-interception-extension-points.md) retains policy and tool-event semantics. +This decision owns asynchronous creation timing. The [scope runtime reference](../../../../packages/core/agent-loop/README.md) retains registry identity and teardown ownership, while the [interception reference](../../../../docs/tool-execution-pipeline.md) retains policy and tool-event semantics. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.zh.md b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.zh.md index f664e89da0..43b0bfa7d1 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-09-awaited-agent-creation.zh.md @@ -16,7 +16,7 @@ AgentLoop 在 setup 与创建分发期间保留现有的维护活动。初始化 创建分发在监听器等待期间保留作用域与 Session。Dispose(资源释放)会取消初始化,并在释放这些资源前等待分发结束。监听器不得等待自身 Agent 的空闲状态或自身所有者的 dispose,因为两者都要等待该监听器完成。另一个 Agent 上的后台工作遵循该 Agent 自己的初始化生命周期。 -本决策拥有异步创建时序。[作用域运行时决策](2026-07-12-agent-scope-runtime-design.zh.md)继续拥有注册表身份与 teardown 归属,[拦截决策](../feature/2026-06-30-interception-extension-points.zh.md)继续拥有策略与工具事件语义。 +本决策拥有异步创建时序。[作用域运行时参考](../../../../packages/core/agent-loop/README.zh.md)继续拥有注册表身份与 teardown 归属,[拦截参考](../../../../docs/tool-execution-pipeline.zh.md)继续拥有策略与工具事件语义。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.i18n.yaml index 51149ea2fb..f7082b3940 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.i18n.yaml @@ -9,8 +9,8 @@ en: 4548e7fbdbee5b5a zh: 0078762ea92fa180 /agent-note-consumer-owned-startup-strictness/decision: - en: eb2b83f5f65c6b6a - zh: b85a9aa4645c9a20 + en: 1f11e04162e19ad1 + zh: d102e300eba9a2ea /agent-note-consumer-owned-startup-strictness/alternatives-considered: en: 1b67e062213184cd zh: 91cc8a015de2bfe4 @@ -18,5 +18,5 @@ en: 283dec1b336c540d zh: 2ed358079072cfff /agent-note-consumer-owned-startup-strictness/testing: - en: d8cafae30ea1347b - zh: de47e4e7ba863261 + en: c38221069ce98fd3 + zh: 9a2f91f539eba4d4 diff --git a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.md b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.md index bbe975e13b..cf17823e72 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.md +++ b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.md @@ -18,7 +18,7 @@ The audit treats a throwing `disabled` expression as an entry failure, not a dis The audit runs only during initial application boot. Later config HMR remains best effort and keeps the failed candidate visible for repair. -This policy governs [Web host boot](2026-07-24-web-config-tree-boot-and-transport-layering.md), including its [client plugin roster](2026-07-23-client-plugin-loading-model.md). [Per-session presets](2026-09-18-declarative-agent-presets.md) own a separate strict subtree audit. +This policy governs [Web host boot](../../../../packages/boot/app-boot/README.md), including its [client plugin roster](2026-07-23-client-plugin-loading-model.md). [Per-session presets](2026-09-18-declarative-agent-presets.md) own a separate strict subtree audit. ## Alternatives considered @@ -36,6 +36,6 @@ The compact terminal report keeps the failing plugins visible; a separate file r App-boot unit tests cover absent and disabled required ids, optional import failure, config evaluation failure, synchronous and asynchronous `apply()` failure, pending dependencies, and required failure teardown. Unit expectations pin diagnostic grouping, preservation of original error objects and import logs, exporter cleanup, complete diagnostic values, private file creation, concurrent report names, and failed-write fallback. The built Web-profile acceptance asserts a single port-conflict stack without Node wrapper output, verifies the saved diagnostic file and its stderr fallback, serves the full UI with optional failures and exits nonzero without readiness when the required HTTP port is occupied or `modules` or `connection` cannot activate. -The [Web process matrix](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts) independently exercises optional and required failures at startup and after native patch-file edits. Authenticated HTTP requests and plugin lifecycle files distinguish a usable application from a surviving process. These keyless process checks complement the [controlled-delivery unit tests](../testing/2026-09-09-user-patch-hmr-test-delivery.md): unit tests isolate reconciliation failures, while the process tests also require the shipped launcher, native watcher, and bounded shutdown to work together. +The [Web process matrix](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts) independently exercises optional and required failures at startup and after native patch-file edits. Authenticated HTTP requests and plugin lifecycle files distinguish a usable application from a surviving process. These keyless process checks complement the [controlled-delivery unit tests](../../../../packages/boot/app-boot/tests/user-patches.spec.ts): unit tests isolate reconciliation failures, while the process tests also require the shipped launcher, native watcher, and bounded shutdown to work together. The matrix enables Chokidar's `awaitWriteFinish` to acknowledge stable file contents before each reload; otherwise its short change-event suppression window can discard the next test edit. Native events remain required, and assertions wait for observed activation or failure rather than a fixed settling sleep. This is explicit test configuration, not evidence for the default watcher timing. diff --git a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.zh.md b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.zh.md index 4bd2bfe026..c8bda5cb20 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-09-consumer-owned-startup-strictness.zh.md @@ -18,7 +18,7 @@ Required id 为 `agent-loop`、`webserver`、`modules`、`connection`、`headles 该审计只在应用首次启动时运行。之后的 config HMR 仍采用 best effort,并保留 failed candidate 供后续修复。 -该策略适用于 [Web host 启动](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md),包括其 [client 插件名册](2026-07-23-client-plugin-loading-model.zh.md)。[按会话的 preset](2026-09-18-declarative-agent-presets.zh.md)持有独立的严格子树审计。 +该策略适用于 [Web host 启动](../../../../packages/boot/app-boot/README.zh.md),包括其 [client 插件名册](2026-07-23-client-plugin-loading-model.zh.md)。[按会话的 preset](2026-09-18-declarative-agent-presets.zh.md)持有独立的严格子树审计。 ## 考虑过的替代方案 @@ -36,6 +36,6 @@ Required id 为 `agent-loop`、`webserver`、`modules`、`connection`、`headles App-boot 单元测试覆盖缺失和禁用的 required id、optional import failure、config evaluation failure、同步和异步 `apply()` failure、pending dependency,以及 required failure teardown。单元预期输出固定诊断分组、原始错误对象与导入日志的保留、exporter 清理、完整诊断值、私有文件创建、并发报告命名以及写入失败回退行为。构建后的 Web-profile acceptance 断言端口冲突堆栈只输出一次且不包含 Node 包装输出,验证已保存的诊断文件及其 stderr 回退,并会在 optional failure 存在时继续提供完整 UI,并在 required HTTP port 被占用或 `modules`、`connection` 无法激活时以非零码退出,且不报告就绪。 -[Web 进程矩阵](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts)分别验证启动时和原生补丁文件修改后的 optional 与 required 失败。经过认证的 HTTP 请求和插件生命周期文件区分可用应用与仅存活的进程。这些无需密钥的进程检查与[受控事件投递单元测试](../testing/2026-09-09-user-patch-hmr-test-delivery.zh.md)互补:单元测试隔离配置协调失败,进程测试还要求随附启动器、原生监听器和有界关闭流程协同工作。 +[Web 进程矩阵](../../../../apps/cli/tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts)分别验证启动时和原生补丁文件修改后的 optional 与 required 失败。经过认证的 HTTP 请求和插件生命周期文件区分可用应用与仅存活的进程。这些无需密钥的进程检查与[受控事件投递单元测试](../../../../packages/boot/app-boot/tests/user-patches.spec.ts)互补:单元测试隔离配置协调失败,进程测试还要求随附启动器、原生监听器和有界关闭流程协同工作。 矩阵启用 Chokidar 的 `awaitWriteFinish`,在每次重载前确认文件内容已稳定;否则它的短暂 change 事件抑制窗口可能丢弃下一次测试编辑。测试仍然依赖原生事件,并等待观察到激活或失败,而不是固定时长的休眠。这是显式测试配置,不能证明默认监听器的时序行为。 diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.i18n.yaml deleted file mode 100644 index 2516370b2e..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-desktop-build-release-validation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-09-desktop-build-release-validation.md -/agent-note-verify-desktop-release-compatibility-during-packaging: - en: 4183cb362d89a8fb - zh: ede5e45ba6245b6b -/agent-note-verify-desktop-release-compatibility-during-packaging/problem: - en: ae3f1e31ba929c0e - zh: 1a42684e41531547 -/agent-note-verify-desktop-release-compatibility-during-packaging/decision: - en: 352372f213c43a76 - zh: 54dc9826254e56f2 -/agent-note-verify-desktop-release-compatibility-during-packaging/alternatives-considered: - en: 515cec0d22c8373c - zh: e3a5b3058b6316c2 -/agent-note-verify-desktop-release-compatibility-during-packaging/consequences: - en: 5193e3ce82faa984 - zh: 1bd7a31152e3b71c diff --git a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml deleted file mode 100644 index ecc422ab61..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-desktop-immediate-window-and-direct-start.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-09-desktop-immediate-window-and-direct-start.md -/agent-note-show-the-desktop-window-before-starting-the-host: - en: aed1efe0e21399a1 - zh: dd45a0ed4bfa731e -/agent-note-show-the-desktop-window-before-starting-the-host/problem: - en: bd8f14bc0beaac9e - zh: 20b0521f27ad231f -/agent-note-show-the-desktop-window-before-starting-the-host/decision: - en: 6ac14061a85b3da6 - zh: ec35f11c15b34be8 -/agent-note-show-the-desktop-window-before-starting-the-host/alternatives-considered: - en: 4d476b25f3647e1b - zh: 4a1b0252005c8c22 -/agent-note-show-the-desktop-window-before-starting-the-host/consequences: - en: 98119090f2133d33 - zh: 53ad4539d677ee4e diff --git a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml deleted file mode 100644 index 6c34e86274..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-plugin-management-in-the-web-sidebar.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-09-plugin-management-in-the-web-sidebar.md -/agent-note-plugin-management-moves-to-the-web-sidebar: - en: efdb6c8f366dd2c2 - zh: 5c55e2882753d481 -/agent-note-plugin-management-moves-to-the-web-sidebar/problem: - en: b0d11556425397b0 - zh: 00968088bec13102 -/agent-note-plugin-management-moves-to-the-web-sidebar/decision: - en: eb93a33f64907b80 - zh: 4073f2da219ad798 -/agent-note-plugin-management-moves-to-the-web-sidebar/alternatives-considered: - en: e5cea1a5e97884be - zh: d5544ae935a48f0a -/agent-note-plugin-management-moves-to-the-web-sidebar/consequences: - en: 74c0051937f03444 - zh: 256b38ff34549a4d -/agent-note-plugin-management-moves-to-the-web-sidebar/testing: - en: 9d08eb02abc00595 - zh: 56c7d26016f9deec diff --git a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.i18n.yaml index c1cc88bcdd..5cadfbef6f 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.i18n.yaml @@ -6,8 +6,8 @@ en: 6a4338564c01ad91 zh: 241248cff9bd4cfc /agent-note-add-immutable-profile-resolution-generations/problem: - en: 8e192c176fe48c4c - zh: adb965d650088eb0 + en: 907a40c722a6faff + zh: a471d7034d8c231f /agent-note-add-immutable-profile-resolution-generations/decision: en: a3022a87337058c5 zh: 1d53a08a25ed6eb3 @@ -21,8 +21,8 @@ en: 53a9e92698ce9a5d zh: 6124d446e8d4a532 /agent-note-add-immutable-profile-resolution-generations/decision/immutable-generations: - en: cd4e64bd80f448a2 - zh: 585e1fbd8721ae99 + en: e10b224e0e68b701 + zh: d43fbdf37e6bfc54 /agent-note-add-immutable-profile-resolution-generations/decision/shared-esm-and-commonjs-rule: en: 0e0575a04a081b52 zh: 94c74334939e6d13 @@ -33,8 +33,8 @@ en: 99308bd758859e02 zh: 35f1e7719fe80d2e /agent-note-add-immutable-profile-resolution-generations/decision/additive-package-changes: - en: 718dfa7826783f2a - zh: 21c3d5fdfae9cb99 + en: 0e5e2006caec3c91 + zh: fb3b61dd5692d2e3 /agent-note-add-immutable-profile-resolution-generations/decision/filesystem-and-runtime-carriers: en: b0607713102a093d zh: 2de9c33bfaa0e3e2 @@ -45,8 +45,8 @@ en: 2426173c58a17a48 zh: 0423b3700273accd /agent-note-add-immutable-profile-resolution-generations/verification: - en: fbb17aa8fe62b1e8 - zh: 2ed091fdfd995fc8 + en: 74cb43ea2ccdb334 + zh: b966bbed43a83c82 /agent-note-add-immutable-profile-resolution-generations/consequences: - en: 54cae20b2734a07e - zh: caa334f8f7a40452 + en: 457a7d1e82b45fe0 + zh: 800036e083a7674c diff --git a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md index 649c6c358f..d933a4507e 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md +++ b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md @@ -8,7 +8,7 @@ English | [中文](2026-09-09-profile-resolution-generations.zh.md) A profile loads plugin rows from its own package project, while Harness packages and packages carried by selected bundles can live outside that project's ordinary dependency tree. Bridging the trees through shared symlinks, profile-owned links, or packaged-executable proxy packages persists package selections across processes and installations. Those files require reconciliation and locking, expose generated proxy manifests to metadata readers, and cannot represent a process-local change atomically. -The runtime design keeps installation-first, ordered-bundle, and local-before-fallback precedence. It covers imports performed by plugin modules as well as Loader row imports and works in the main thread and Harness-owned Workers. Generation replacement preserves existing package mappings and local package names, permits linked-root membership changes, and never mutates a live table entry by entry. +The runtime design keeps installation-first, ordered-bundle, and local-before-fallback precedence. It covers imports performed by plugin modules as well as Loader row imports and works in the main thread and Harness-owned Workers. Generation replacement preserves retained package identities, permits removing profile packages and changing linked-root membership, and never mutates a live table entry by entry. ## Decision @@ -75,7 +75,7 @@ Linked-root membership is immutable within a generation. A successor may add or The router retains the real target of each successfully published link name for its lifetime, solely to validate successors. Removing a root does not erase that record or keep its directory intercepted. Re-adding the same name and target is allowed; a different target is rejected because Node caches real paths. Failed publication changes neither the current generation nor the recorded targets. -The launcher constructs one startup generation. The service accepts a complete successor, but no package-manager transaction invokes replacement in this implementation. +The launcher constructs one startup generation. `ProfileRuntimeResolution` retains only the installation anchor, Harness home, and optional profile directory as recomputation inputs; successors reread disk selection and layers rather than inheriting a supplied profile's synthetic layers. A generation without a profile directory can recompute installation packages. The [package refresh decision](2026-09-30-profile-package-refresh-and-manifest-invalidation.md) assigns successor publication to Plugin Manager operations. ### Shared ESM and CommonJS rule @@ -105,9 +105,9 @@ New Workers inherit the latest published generation. Existing Workers keep the g ### Additive package changes -A caller adding a package completes its pnpm transaction before constructing a successor generation. Replacement rejects any generation that changes the directory or version of an existing package. The caller publishes an additive successor before mounting the new Loader row; this implementation does not provide that package transaction. A mount failure may leave the package installed but inactive. +A caller adding a package completes its pnpm transaction before constructing a successor generation. Replacement rejects any generation that changes the directory or version of a retained package. Plugin Manager publishes the successor before mounting the new Loader row. A mount failure may leave the package installed but inactive. -Changing or removing an existing runtime package mapping, or removing a recorded profile-local package name, requires process restart because Node's ESM Module Map, CommonJS cache, existing object references, and running Workers can retain the old module identity. These restrictions do not prohibit removing a linked root from the interception scope. Generation replacement does not claim to unload modules. +A retained package keeps its normalized directory, version, and scope; installation entries also keep their declaring anchor and cannot be removed. Violating these restrictions requires process restart because Node's ESM Module Map, CommonJS cache, existing object references, and running Workers can retain the old module identity. A profile entry may change its declaring anchor: deselecting the first bundle that reaches a shared dependency can leave another bundle selecting the same physical package. Profile-scoped mappings and profile-local package names may be removed; the caller stops their plugins before deleting package files. These restrictions do not prohibit removing a linked root from the interception scope. Generation replacement does not claim to unload modules. ### Filesystem and runtime carriers @@ -142,7 +142,7 @@ Behavior tests exercise root order, transitive and peer dependencies, local and ## Verification - One eager computation supplies the runtime resolution; startup neither writes nor retires module-resolution data. -- [Generation tests](../../../../packages/boot/app-boot/tests/profile-resolution.spec.ts) cover installation and selected-bundle graphs with ordinary directories and recursive symlinks, including different dependency versions beside logical and real anchors. They also cover linked-root removal, same-target restoration, overlapping roots, native misses, new requests from loaded modules, and relink rejection after removal. +- [Generation tests](../../../../packages/boot/app-boot/tests/profile-resolution.spec.ts) cover installation and selected-bundle graphs with ordinary directories and recursive symlinks, including different dependency versions beside logical and real anchors. They also cover same-package profile declarer changes, retained-identity rejection, captured-directory recomputation, linked-root removal, same-target restoration, overlapping roots, native misses, new requests from loaded modules, and relink rejection after removal. - [Source-launch tests](../../../../apps/cli/tests/source-launch.compat.spec.ts) and [built-bin tests](../../../../apps/cli/tests/built-bin.e2e.ts) run both profile layouts through the real CLI. They assert ESM/CJS versions, loaded paths, per-format dependency identity, and consistent Tools/AgentLoop module instances with an accessible scheduler key. - Pkg and Electron carriers select runtime resolution; Electron executes its Host in Node mode from the ASAR-backed dsh tree while native executable entries remain unpacked. - ESM and CommonJS adapters share one router and delegate final resolution to Node without `module.registerHooks` or `_findPath` replacement. @@ -152,4 +152,4 @@ Behavior tests exercise root order, transitive and peer dependencies, local and ## Consequences -Runtime startup avoids disk mutation and proxy manifests while retaining package-precedence rules. Real-directory anchors align dependency discovery with default Node loading and tsx workspace mapping, including cases where a logical symlink path would select another version. The implementation accepts the maintenance cost of Node Internal compatibility tests and an early, self-contained bootstrap in each owned Worker; it provides no disk-only backend or dual comparison mode. Package mappings and local package names remain additive; linked-root membership may change without unloading modules. +Runtime startup avoids disk mutation and proxy manifests while retaining package-precedence rules. Real-directory anchors align dependency discovery with default Node loading and tsx workspace mapping, including cases where a logical symlink path would select another version. The implementation accepts the maintenance cost of Node Internal compatibility tests and an early, self-contained bootstrap in each owned Worker; it provides no disk-only backend or dual comparison mode. Profile package records and linked-root membership may be removed without unloading modules; retained package identities stay unchanged. diff --git a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md index 4e84cc1f1b..24da72defb 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md @@ -8,7 +8,7 @@ Status: implemented profile 从自己的包项目加载插件配置项,而 Harness 包和所选 bundle 携带的包可能位于该项目普通依赖树之外。通过共享 symlink、profile 自有链接或打包可执行文件的代理包连接两棵依赖树,会让选包结果跨进程和安装版本持续存在。这些文件需要协调和锁来维护,并向元数据读取方暴露生成的代理 manifest(元数据清单),也无法原子表示进程内变更。 -运行时设计保留安装优先、有序 bundle 和本地包优先于 fallback 的顺序。它覆盖插件模块内部的 import 以及 Loader 配置项的 import,并在主线程和 Harness 自有 Worker 中工作。generation 替换保留既有包映射和本地包名,允许调整 linked root 集合,不会逐项修改正在使用的表。 +运行时设计保留安装优先、有序 bundle 和本地包优先于 fallback 的顺序。它覆盖插件模块内部的 import 以及 Loader 配置项的 import,并在主线程和 Harness 自有 Worker 中工作。generation 替换保持保留包的身份不变,允许移除 profile 包和调整 linked root 集合,不会逐项修改正在使用的表。 ## Decision @@ -75,7 +75,7 @@ linked root 集合在一个 generation 内不可变。后继 generation 可以 解析器在自身生命周期内保留每个已成功发布的 link 名称对应的真实目标,仅用于校验后继 generation。移除 root 不会抹掉该记录,也不会让其目录继续被拦截。同名、同目标可以重新加入;不同目标会被拒绝,因为 Node 缓存真实路径。发布失败时,当前 generation 和已记录目标均不变。 -launcher 只构造启动 generation。服务接受完整的后继 generation,但本实现没有包管理器事务调用替换操作。 +launcher 只构造启动 generation。`ProfileRuntimeResolution` 仅保留安装锚点、Harness home 和可选的 profile 目录作为重算输入;后继代重新读取磁盘上的选择与配置层,不继承传入 profile 的合成层。没有 profile 目录的 generation 可以重算安装包。[包操作刷新决策](2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md)规定由 Plugin Manager 的操作发布后继代。 ### ESM 与 CommonJS 共用规则 @@ -105,9 +105,9 @@ runtime resolution 列出它提供的包;Loader entries 组成活动插件列 ### 只增加包的变更 -添加包的调用方先完成 pnpm 事务,再构造下一代。替换操作会拒绝改变任何既有 package name 的目录或版本。调用方先发布只增加映射的后继 generation,再挂载新的 Loader 配置项;本实现不提供该包事务。挂载失败可以留下已安装但未启用的包。 +添加包的调用方先完成 pnpm 事务,再构造下一代。替换操作会拒绝改变任何保留包的目录或版本。Plugin Manager 先发布后继 generation,再挂载新的 Loader 配置项。挂载失败可以留下已安装但未启用的包。 -修改或删除既有运行时包映射,或删除已记录的 profile 本地包名,需要重启,因为 Node 的 ESM Module Map、CommonJS cache、现存对象引用和运行中的 Worker 都可能保留旧模块 identity。这些限制不禁止从拦截范围移除 linked root。generation 换代不声称卸载模块。 +保留包的规范化目录、版本和作用域必须不变;安装条目还必须保留声明锚点,且不能移除。违反这些限制需要重启,因为 Node 的 ESM Module Map、CommonJS cache、现存对象引用和运行中的 Worker 都可能保留旧模块 identity。profile 条目可以改变声明锚点:取消选入最先找到共享依赖的组合包后,另一个组合包仍可能选中同一物理包。profile 范围的映射和 profile 本地包名可以移除;调用方在删除包文件之前先停止相关插件。这些限制不禁止从拦截范围移除 linked root。generation 换代不声称卸载模块。 ### 文件系统与运行时载体 @@ -142,7 +142,7 @@ generation 构造发生在启动或显式更新阶段,不属于单次 resolve ## Verification - 一次 eager 计算供应 runtime resolution;启动既不写入也不退休模块解析数据。 -- [Generation 测试](../../../../packages/boot/app-boot/tests/profile-resolution.spec.ts)覆盖普通目录和递归软链接下的安装图与所选 bundle 图,包括逻辑锚点和真实锚点旁存在不同依赖版本的情况。测试还覆盖 linked root 移除、同目标恢复、重叠 root、原生缺包、已加载模块的新请求,以及移除后重新链接不同目标时的拒绝。 +- [Generation 测试](../../../../packages/boot/app-boot/tests/profile-resolution.spec.ts)覆盖普通目录和递归软链接下的安装图与所选 bundle 图,包括逻辑锚点和真实锚点旁存在不同依赖版本的情况。测试还覆盖同包 profile 声明者变更、保留包身份变更拒绝、捕获目录后的重算、linked root 移除、同目标恢复、重叠 root、原生缺包、已加载模块的新请求,以及移除后重新链接不同目标时的拒绝。 - [源码启动测试](../../../../apps/cli/tests/source-launch.compat.spec.ts)与[构建入口测试](../../../../apps/cli/tests/built-bin.e2e.ts)通过真实 CLI 运行两种 profile 布局,断言 ESM/CJS 版本、加载路径、各模块格式内的依赖身份,以及一致的 Tools/AgentLoop 模块实例和可访问的 scheduler 键。 - pkg 与 Electron 载体选择 runtime 解析;Electron 以 Node 模式从 ASAR 承载的 dsh 依赖树执行 Host,原生可执行条目保持 unpacked。 - ESM 与 CommonJS 适配器共享同一个路由器,并把最终解析委托给 Node,不使用 `module.registerHooks` 或替换 `_findPath`。 @@ -152,4 +152,4 @@ generation 构造发生在启动或显式更新阶段,不属于单次 resolve ## Consequences -runtime 启动避免磁盘修改和代理 manifest,同时保留包优先级规则。真实目录锚点让依赖发现对齐 Node 默认加载与 tsx workspace 映射,也覆盖软链接逻辑路径会选中另一版本的情况。实现需要持续维护 Node Internal 兼容测试,并在每个自有 Worker 中尽早执行自包含 bootstrap;它不提供纯磁盘后端或 dual 对比模式。包映射和本地包名仍只允许新增;linked root 集合可以变化,而不卸载模块。 +runtime 启动避免磁盘修改和代理 manifest,同时保留包优先级规则。真实目录锚点让依赖发现对齐 Node 默认加载与 tsx workspace 映射,也覆盖软链接逻辑路径会选中另一版本的情况。实现需要持续维护 Node Internal 兼容测试,并在每个自有 Worker 中尽早执行自包含 bootstrap;它不提供纯磁盘后端或 dual 对比模式。profile 包记录和 linked root 集合可以移除,而不卸载模块;保留包的身份保持不变。 diff --git a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.i18n.yaml index 3d1c6c968a..55defc8dc9 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.i18n.yaml @@ -9,8 +9,8 @@ en: ee5703dcce5d81c5 zh: 6c1aec04723d211b /agent-note-workspace-file-read-authority/decision: - en: 92dbdf6087e00f04 - zh: 2c8720a081d492f9 + en: b4b88d397452d083 + zh: 14069d83e001082d /agent-note-workspace-file-read-authority/alternatives-considered: en: 329e575be02d7392 zh: a78f428b36d40f59 diff --git a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.md b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.md index 140816e8c4..5550cfa003 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.md +++ b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.md @@ -14,7 +14,7 @@ Workspace Files serves both file content and workspace navigation. Applying work `list` and `changes` remain workspace-scoped because they expose workspace navigation and observation rather than a named file read. `list` rejects a directory outside the root, and `changes` filters observations through the backend's workspace-containment predicate. -`readBytes` with `baseFile` resolves a relative path from the base file's directory. A `..` path may therefore read JavaScript or CSS outside the workspace when the Session backend permits it. With [Developer tools](../feature/2026-09-17-developer-tools-settings.md) enabled, Document Preview packages bounded, statically declared local scripts and stylesheets into an HTML Blob iframe with `sandbox="allow-scripts"`; the opaque origin blocks parent access, but the browser retains normal network access. This exposure is an intentional security trade-off for rendering static generated HTML. +`readBytes` with `baseFile` resolves a relative path from the base file's directory. A `..` path may therefore read JavaScript or CSS outside the workspace when the Session backend permits it. With [Developer tools](../../../../packages/client/ui-sidebar-documentpreview/README.md) enabled, Document Preview packages bounded, statically declared local scripts and stylesheets into an HTML Blob iframe with `sandbox="allow-scripts"`; the opaque origin blocks parent access, but the browser retains normal network access. This exposure is an intentional security trade-off for rendering static generated HTML. The [Workspace Files service](2026-09-05-workspace-files-service.md) owns paging, file checks, listing, and observation. [Document Preview](2026-09-08-document-preview-operations.md) owns which related files are packaged and the iframe sandbox. diff --git a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.zh.md b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.zh.md index 75ffee32c4..f12c11ac9b 100644 --- a/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-09-workspace-file-read-authority.zh.md @@ -14,7 +14,7 @@ Workspace Files 同时提供文件内容与工作区导航。对所有操作应 `list` 与 `changes` 仍限于工作区,因为它们暴露工作区导航和观察,而不是读取一个具名文件。`list` 拒绝根外目录,`changes` 通过后端的工作区包含判定过滤观察。 -带 `baseFile` 的 `readBytes` 从基准文件所在目录解析相对路径。因此,只要 Session 后端允许,`..` 路径就可以读取工作区外的 JavaScript 或 CSS。开启[开发者工具](../feature/2026-09-17-developer-tools-settings.zh.md)时,Document Preview 把有界、静态声明的本地脚本与样式表打包进带 `sandbox="allow-scripts"` 的 HTML Blob iframe;不透明源阻止访问父应用,但浏览器保留正常网络访问。这种暴露是为渲染静态生成 HTML 而有意接受的安全取舍。 +带 `baseFile` 的 `readBytes` 从基准文件所在目录解析相对路径。因此,只要 Session 后端允许,`..` 路径就可以读取工作区外的 JavaScript 或 CSS。开启[开发者工具](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md)时,Document Preview 把有界、静态声明的本地脚本与样式表打包进带 `sandbox="allow-scripts"` 的 HTML Blob iframe;不透明源阻止访问父应用,但浏览器保留正常网络访问。这种暴露是为渲染静态生成 HTML 而有意接受的安全取舍。 [Workspace Files 服务](2026-09-05-workspace-files-service.zh.md)负责分页、文件检查、列举和观察。[Document Preview](2026-09-08-document-preview-operations.zh.md)负责选择要打包的关联文件及 iframe sandbox。 diff --git a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml deleted file mode 100644 index 85ab4e3c03..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-command-identities-and-composer-file-action.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-10-command-identities-and-composer-file-action.md -/agent-note-command-identities-and-composer-owned-file-action: - en: 96db2d4653b47858 - zh: b914d79ab95b0e51 -/agent-note-command-identities-and-composer-owned-file-action/problem: - en: ad2c360593eb7a42 - zh: 503a9c31a5eb55e7 -/agent-note-command-identities-and-composer-owned-file-action/decision: - en: c78d13ee6a59c04d - zh: 53fc322d351e437b -/agent-note-command-identities-and-composer-owned-file-action/alternatives-considered: - en: 94ccabd1fe224408 - zh: bfa28806f0218ce8 -/agent-note-command-identities-and-composer-owned-file-action/consequences: - en: 08f25ff5e0d227cf - zh: a26663e5b7d9feeb diff --git a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.i18n.yaml deleted file mode 100644 index 50d4ce7a3d..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-compact-tool-details.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md -/agent-note-compact-tool-details-from-recorded-results: - en: 7495c26039484440 - zh: d1907caf1f139054 -/agent-note-compact-tool-details-from-recorded-results/problem: - en: e4ce66f2cdb08516 - zh: 3f45374f23fa8f3b -/agent-note-compact-tool-details-from-recorded-results/decision: - en: 668e06f5146a78de - zh: 4af1edf74eb8121c -/agent-note-compact-tool-details-from-recorded-results/alternatives-considered: - en: 1f0bf45e4ecc0bfa - zh: c46b173dc7ee180b -/agent-note-compact-tool-details-from-recorded-results/consequences: - en: e02acb07b752c285 - zh: b29f9a5ad44ff10f diff --git a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md b/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md deleted file mode 100644 index 6c1f29c555..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: Compact Tool Details from Recorded Results - -Status: implemented - -English | [中文](2026-09-10-compact-tool-details.zh.md) - -## Problem - -Goal, todo, and schedule results contain a small set of user-facing facts that take more effort to read as JSON. Dedicated expanded bodies need to fit the existing conversation rows without adding a second presentation registry or live state to historical calls. - -## Decision - -The `ui-tool` package registers goal and schedule toolviews through the existing keyed slot and shares one package-internal fields/list component with the todo view. Successful, recognized results display recorded values; incomplete calls, errors, malformed values, and unsupported result formats retain generic input/output. Schedule domain errors, including uncertain persistence and an unknown deletion target, retain their original result. - -Tool rows keep their disclosure and Inspect interaction. Checklist marks are static and have accessible status labels. Schedule times use absolute dates with the viewer's time zone; states come from the recorded result. Goal details distinguish an active goal awaiting continuation from an armed goal. These display values do not subscribe to the current goal, todo, or schedule projection. - -Todo changes compare each root or nested call with its preceding durable `todo/write`, so later writes cannot alter an earlier card. The conversation assembler repairs predecessors when older history loads. Until the predecessor is known, the card shows the submitted list without inferred additions or removals. - -This scoped visual change is an exception to the visual-equivalence requirement in [Client-derived tool presentation](2026-08-23-client-derived-tool-presentation.md). Its raw-event ownership, generic fallback, and single keyed registry remain authoritative. - -## Alternatives considered - -**A shared presenter framework:** the existing keyed slot already dispatches tools. Reusing only the detail body keeps the abstraction local until another package needs it. - -**A dashboard inside each card:** interactive controls, progress graphics, and live states add visual weight and can misrepresent what a historical call returned. Compact read-only fields and lists preserve the conversation's hierarchy. - -## Consequences - -Registered tools gain readable details without changing Host tools, Session events, or public Client exports. Tool-specific adapters interpret recorded JSON and text; unrecognized formats retain generic output. The authored tool-details Session replay covers their assembled rendering. diff --git a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md b/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md deleted file mode 100644 index d0822a48f1..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: Compact Tool Details from Recorded Results - -Status: implemented - -[English](2026-09-10-compact-tool-details.md) | 中文 - -## 问题 - -目标、任务清单和定时任务的结果包含少量面向用户的事实,以 JSON 阅读时较费力。专用展开内容需要融入现有对话行,不能为历史调用新增一套呈现注册表或实时状态。 - -## 决策 - -`ui-tool` 包通过现有按键分发的插槽注册目标和定时任务视图,并与任务清单视图共用一个包内字段/列表组件。成功且可识别的结果显示记录值;未完成调用、错误、格式异常和不支持的结果格式保留通用输入/输出。定时任务领域错误(包括持久化不确定和删除目标不存在)保留原始结果。 - -工具行保留展开和 Inspect 交互。任务清单标记是静态的,并带有可访问的状态标签。定时任务使用查看者时区的绝对日期,状态取自记录的结果。目标详情区分等待继续的活动目标和已启动的目标。这些显示值不订阅当前目标、任务清单或定时任务投影。 - -任务清单的变化将每个根调用或嵌套调用与之前的持久化 `todo/write` 比较,因此后续写入不会改变较早的卡片。对话组装器在加载更早的历史时修复前序记录。前序记录尚不明确时,卡片展示提交的列表,不推断新增或删除。 - -这项有限的视觉调整是[客户端派生工具呈现](2026-08-23-client-derived-tool-presentation.zh.md)中视觉等价要求的例外。该决策中的原始事件所有权、通用回退和唯一按键注册表仍然有效。 - -## 考虑过的替代方案 - -**共享呈现框架:**现有按键插槽已负责工具分发。仅复用详情内容,可以在其他包出现需求前将抽象限制在包内。 - -**在每张卡片中嵌入仪表盘:**交互控件、进度图形和实时状态会增加视觉负担,也可能歪曲历史调用返回的事实。紧凑的只读字段和列表保留了对话的层级。 - -## 影响 - -已注册的工具获得可读详情,无需改变 Host 工具、Session 事件或公开的 Client 导出。工具专用适配器解读记录中的 JSON 和文本;未识别的格式保留通用输出。手写的 tool-details Session 回放覆盖这些工具组装后的渲染。 diff --git a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.i18n.yaml index 2de0be6d61..5f22b758a9 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.md /agent-note-run-desktop-through-the-shared-web-application: - en: 1803d1966797a555 - zh: 6ab64a673a217d34 + en: aa8d39aff5589d4e + zh: f2d72d070f879f15 /agent-note-run-desktop-through-the-shared-web-application/problem: en: 085b445846af76c1 zh: fdddf72401292bbd /agent-note-run-desktop-through-the-shared-web-application/decision: - en: 7767ab2839440970 - zh: 9a998e72b728fd17 + en: 6a51259a49377680 + zh: d18f7010ced738e7 /agent-note-run-desktop-through-the-shared-web-application/alternatives-considered: en: 9c5dfbf9cc788040 zh: 631f851909dd7234 diff --git a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.md b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.md index 4f43cc6507..d2c286c955 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.md +++ b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-09-10-desktop-web-wrapper.zh.md) -The [Electron runtime decision](2026-09-11-desktop-electron-node-runtime.md) supersedes the separate upstream Node executable; other decisions in this note remain applicable. +Desktop uses Electron as its Node runtime under the [current runtime reference](../../../../apps/desktop/README.md); other decisions in this note remain applicable. ## Problem @@ -12,7 +12,7 @@ Separate Desktop composition and request transport require their own configurati ## Decision -The private Desktop Host invokes the CLI's shared profile runner against the independently owned Desktop profile. The complete Web composition owns authentication, HTTP routes, client assets, RPC, and response streaming. Electron loads packaged static Web assets before the child is ready. Child IPC carries readiness, structured boot injections, and shutdown. The [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-start.md) owns local-document HTTP forwarding and authenticated WebSocket access; Web retains application dispatch and stream framing. +The private Desktop Host invokes the CLI's shared profile runner against the independently owned Desktop profile. The complete Web composition owns authentication, HTTP routes, client assets, RPC, and response streaming. Electron loads packaged static Web assets before the child is ready. Child IPC carries readiness, structured boot injections, and shutdown. The [immediate-window reference](../../../../apps/desktop/README.md) owns local-document HTTP forwarding and authenticated WebSocket access; Web retains application dispatch and stream framing. The shared runner owns profile and Harness-home patches, proxy setup, telemetry defaults, the runtime resolution, configuration reload, and application lifecycle. Desktop initializes profiles from the shared Web template and uses its Plugin Manager in the main application. Electron owns windows, menus, native directory selection, recovery, and release updates. diff --git a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.zh.md b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.zh.md index bc41b4f8ef..dfe352d078 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-10-desktop-web-wrapper.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-09-10-desktop-web-wrapper.md) | 中文 -[Electron 运行时决策](2026-09-11-desktop-electron-node-runtime.zh.md)替代独立上游 Node 可执行文件的选择;本文其他决策仍然适用。 +Desktop 按[当前运行时参考](../../../../apps/desktop/README.zh.md)使用 Electron 作为 Node 运行时;本文其他决策仍然适用。 ## Problem @@ -12,7 +12,7 @@ Status: implemented ## Decision -私有 Desktop Host 针对独立归属的 Desktop profile 调用 CLI 的共享 profile runner。完整 Web 组合负责认证、HTTP 路由、客户端资源、RPC 与响应流。Electron 在子进程就绪前加载打包静态 Web 资源。子进程 IPC 承载就绪、结构化启动注入与关闭。[立即显示窗口决策](2026-09-09-desktop-immediate-window-and-direct-start.zh.md)规定本地文档 HTTP 转发与认证 WebSocket 访问;Web 保留应用分派与流帧处理。 +私有 Desktop Host 针对独立归属的 Desktop profile 调用 CLI 的共享 profile runner。完整 Web 组合负责认证、HTTP 路由、客户端资源、RPC 与响应流。Electron 在子进程就绪前加载打包静态 Web 资源。子进程 IPC 承载就绪、结构化启动注入与关闭。[立即显示窗口参考](../../../../apps/desktop/README.zh.md)规定本地文档 HTTP 转发与认证 WebSocket 访问;Web 保留应用分派与流帧处理。 共享 runner 负责 profile 与 Harness-home patch、代理设置、遥测默认值、runtime resolution、配置重载和应用生命周期。Desktop 从共享 Web 模板初始化 profile,并在主应用中使用其插件管理器。Electron 负责窗口、菜单、原生目录选择、恢复和发布更新。 diff --git a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.i18n.yaml index 6a7a9b2152..aadac74410 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.i18n.yaml @@ -6,8 +6,8 @@ en: 13b817438040d321 zh: 490038f3c93551c7 /agent-note-durable-image-offload-events/problem: - en: 93c2241e0f40fca1 - zh: fe3528cff00e07b3 + en: 352f3f7912a08a3e + zh: bf3a16a5bd2c3a84 /agent-note-durable-image-offload-events/decision: en: e1cbaf46e6a6a7be zh: c3640a44c9be69af diff --git a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.md b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.md index e80c4ae17b..1115a66fab 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.md +++ b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.md @@ -6,7 +6,7 @@ English | [中文](2026-09-10-image-offload-events.zh.md) ## Problem -A request can exceed an image-capable route's byte or image-count budget. Recomputing the omitted prefix for each request lets a route switch, inline fallback, or compaction restore previously omitted images. The model-visible image set then depends on unlogged request preparation. The [unified image pipeline](../feature/2026-08-20-unified-image-request-pipeline.md) owns attachment normalization and request versions; durable omission needs its own recorded decision. +A request can exceed an image-capable route's byte or image-count budget. Recomputing the omitted prefix for each request lets a route switch, inline fallback, or compaction restore previously omitted images. The model-visible image set then depends on unlogged request preparation. The [unified image pipeline](../../../../packages/attachment/attachment-local/README.md) owns attachment normalization and request versions; durable omission needs its own recorded decision. ## Decision diff --git a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.zh.md b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.zh.md index 2aa2b8b1fb..a64c898215 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-10-image-offload-events.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -请求可能超过图片路由的字节或图片数量预算。每次请求重新计算省略前缀,会让路由切换、内联回退或压缩恢复先前省略的图片。模型可见的图片集合因此取决于未记录的请求准备过程。[统一图片流水线](../feature/2026-08-20-unified-image-request-pipeline.zh.md)负责附件归一化与请求版本,持久省略需要单独记录决策。 +请求可能超过图片路由的字节或图片数量预算。每次请求重新计算省略前缀,会让路由切换、内联回退或压缩恢复先前省略的图片。模型可见的图片集合因此取决于未记录的请求准备过程。[统一图片流水线](../../../../packages/attachment/attachment-local/README.zh.md)负责附件归一化与请求版本,持久省略需要单独记录决策。 ## 决策 diff --git a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.i18n.yaml index 493f5002ab..4434bd49e6 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.i18n.yaml @@ -9,8 +9,8 @@ en: e4bafc2b26cae6d2 zh: f8226b200b627c55 /agent-note-public-package-manifest-fields/decision: - en: 9351e430c86c51fe - zh: 486496885b77824a + en: 8586a9687da8e327 + zh: 2dcd1c1fdb856412 /agent-note-public-package-manifest-fields/alternatives-considered: en: 656ef2c1e7853d01 zh: 21c525fda71c6def diff --git a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md index 2d35a3ad17..4341d99d4e 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md +++ b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.md @@ -14,7 +14,7 @@ Plugin authors need npm identity, runtime requirements, and DSH declarations fro Runtime requirements live at top-level `engines`: `dsh`, `node`, and `npm` are optional version strings, and other engine names are allowed. `dsh.manifestVersion` identifies declaration format `1`. Format and DSH compatibility declarations are not enforced by current installers or loaders. -The image packer owns `configTrees`, and the workspace catalog generator owns Session migration declarations. Their existing on-disk keys remain readable by those internal tools, but the public manifest types do not expose them. This scope refines the [shared declaration ownership decision](2026-09-05-package-manifest-types.md), whose package placement and dependency rules remain active. +The image packer owns `configTrees`, and the workspace catalog generator owns Session migration declarations. Their existing on-disk keys remain readable by those internal tools, but the public manifest types do not expose them. Each consumer owns JSON parsing, field validation, default resolution, and adaptation to runtime data. Interfaces do not validate parsed JSON. A helper belongs in the shared package only when multiple consumers need the same validation or normalization; getters that repeat property access add no shared policy. diff --git a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md index 7dc03e5160..e4695b1ca7 100644 --- a/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-10-public-package-manifest.zh.md @@ -14,7 +14,7 @@ Status: implemented 运行时要求位于顶层 `engines`:`dsh`、`node` 和 `npm` 均为可选版本字符串,也允许其他 engine 名称。`dsh.manifestVersion` 标识声明格式 `1`。当前安装器和加载器不强制检查格式与 DSH 兼容性声明。 -镜像打包器拥有 `configTrees`,工作区目录生成器拥有 Session 迁移声明。这些内部工具仍可读取既有磁盘字段,但公共 manifest 类型不暴露这些字段。此范围细化了[共享声明归属决策](2026-09-05-package-manifest-types.zh.md),后者的包位置与依赖规则仍然有效。 +镜像打包器拥有 `configTrees`,工作区目录生成器拥有 Session 迁移声明。这些内部工具仍可读取既有磁盘字段,但公共 manifest 类型不暴露这些字段。 各消费方负责 JSON 解析、字段校验、默认值解析和运行时数据适配。接口不会校验已解析的 JSON。只有多个消费方需要相同校验或归一化时,helper 才属于共享包;重复属性访问的 getter 不提供共享策略。 diff --git a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.i18n.yaml deleted file mode 100644 index 30d6654c2d..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-windows-native-installer-pages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.md -/agent-note-native-windows-installer-pages: - en: 2170ba78b248756f - zh: 6e5852f4685fbc81 -/agent-note-native-windows-installer-pages/problem: - en: 5d148a332b151928 - zh: a79cdfd0f1556c58 -/agent-note-native-windows-installer-pages/decision: - en: 3357e9aca4862152 - zh: c2fa86534f9c8737 -/agent-note-native-windows-installer-pages/alternatives-considered: - en: a5b1e11a1b30ab99 - zh: 8495f05dc87b831a -/agent-note-native-windows-installer-pages/consequences: - en: 16243ca0777d1ff8 - zh: e35cb7f33a756b47 diff --git a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml deleted file mode 100644 index fdc33cea63..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-desktop-electron-node-runtime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.md -/agent-note-use-electron-as-the-desktop-node-runtime: - en: 06290013c52b169d - zh: 5e876829cf708f34 -/agent-note-use-electron-as-the-desktop-node-runtime/problem: - en: 67e580ac6d9298d9 - zh: bb2bd7486090bde4 -/agent-note-use-electron-as-the-desktop-node-runtime/decision: - en: 625ed4349cc70849 - zh: b0aa30ab993e858d -/agent-note-use-electron-as-the-desktop-node-runtime/consequences: - en: 3de90a3734d0f405 - zh: 4d26d953e05e0337 -/agent-note-use-electron-as-the-desktop-node-runtime/alternatives-considered: - en: 402978396810dca3 - zh: 5146ec7ad3cabdc1 diff --git a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.i18n.yaml deleted file mode 100644 index 5879ae942a..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-11-node-office-kit.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-node-office-kit.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-node-office-kit.md -/agent-note-node-office-conversion-with-independently-packaged-engines: - en: 04142abd8eef58bc - zh: 1a2fe733f74e88ef -/agent-note-node-office-conversion-with-independently-packaged-engines/problem: - en: ac8ace90fa115f0d - zh: 18b9977031cbadeb -/agent-note-node-office-conversion-with-independently-packaged-engines/decision: - en: 61d2e96b5b3782c5 - zh: 49780b672e681b0e -/agent-note-node-office-conversion-with-independently-packaged-engines/alternatives-considered: - en: 70315a46124cbde7 - zh: ee258ed0ef7effb0 -/agent-note-node-office-conversion-with-independently-packaged-engines/consequences: - en: bb32631217be9743 - zh: 1ea86613ade03732 diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml index 552e27d0a6..90c7dfd954 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.i18n.yaml @@ -9,11 +9,11 @@ en: bd3ad5505204ec0b zh: 770b6089c760d99f /agent-note-plugin-owned-message-projections/decision: - en: 2b33d4a1cfba0f53 - zh: 55347f3ecf234862 + en: 50c02960b1f087c6 + zh: fa3f5c18b897eab9 /agent-note-plugin-owned-message-projections/alternatives-considered: - en: c6e4c770739dca68 - zh: 7bfcb3dda06a22a1 + en: b0da1fbf24bc506a + zh: c4e35896599f824c /agent-note-plugin-owned-message-projections/consequences: en: c2f3ff0b9639bec5 zh: 357f90d9a3423751 diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md index 9e1eca9256..739d27644b 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.md @@ -16,7 +16,7 @@ The `@messageProjection` tag on the owning `SessionEventMap` member generates th The compaction-image-offload plugin registers its definition through a fiber-owned `ctx.sessions.registerMessageProjection()` effect. Each event has one registered owner. Removing a definition invalidates pending committed decisions and cached reads that used it; a replacement definition requires restoring the session. Rejected, uncommitted candidates retain no interpreter dependency. -Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition, while compaction invariants borrow that composition's definitions. No process-global registry or import-time registration is involved. +Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition. No process-global registry or import-time registration is involved. The image event payload and required-on-read semantics remain owned by the image-offload note. Its indexes, selection policy, retry behavior, and SDK recordings are unchanged; this decision partially supersedes that note's core-owned interpretation. @@ -26,7 +26,7 @@ The image event payload and required-on-read semantics remain owned by the image **Use the ordinary session-projection registry.** Its folds expose domain state after commit. They cannot veto an invalid append or supply canonical `deriveMessages()` content without another integration point. -**Transform only the outgoing request.** Compaction, fork, offline replay, and request invariants also derive messages. A send-time transformation does not cover these consumers. +**Transform only the outgoing request.** Compaction, fork, and offline replay also derive messages. A send-time transformation does not cover these consumers. ## Consequences diff --git a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md index b26b5d4549..74c85825bf 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-11-plugin-owned-message-projections.zh.md @@ -16,7 +16,7 @@ Status: implemented compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMessageProjection()` effect 注册处理器。每种事件只有一个注册者。移除处理器后,尚未应用的已提交决策和使用过它的缓存读取都会失效。替换处理器需要重新恢复会话。被拒绝且未提交的候选事件不会保留对处理器的依赖。 -独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合,压缩 invariant 借用该组合的处理器。不使用进程全局注册表,也不在导入模块时注册。 +独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合。不使用进程全局注册表,也不在导入模块时注册。 图片事件载荷和读取时必须识别的语义仍由图片省略记录负责。索引、选图策略、重试行为和 SDK 录制保持不变。本决策部分取代该记录中由核心负责解释事件的职责划分。 @@ -26,7 +26,7 @@ compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMess **使用普通的 session-projection 注册表。** 它的折叠在提交后公开领域状态。没有额外集成点,它无法否决无效追加,也无法提供标准 `deriveMessages()` 内容。 -**只转换发出的请求。** 压缩、分叉、离线回放和请求 invariant 也会派生消息。发送时转换无法覆盖这些消费方。 +**只转换发出的请求。** 压缩、分叉和离线回放也会派生消息。发送时转换无法覆盖这些消费方。 ## 影响 diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml index 834d15576d..d83f853c54 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.i18n.yaml @@ -12,8 +12,8 @@ en: cf072f53de09faca zh: 9edd5c4a2bfaae5a /agent-note-sandboxed-node-execution-for-ptc/decision/resolved-inputs-and-policy: - en: e93df995efab569a - zh: 850daf02d1207028 + en: 3aef37bb4f8a8e53 + zh: a7b477324d486ab3 /agent-note-sandboxed-node-execution-for-ptc/decision/control-and-lifetime: en: 2c7a694b04951772 zh: 133b95b0477b127f diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md index f851775460..2104e58e8e 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md +++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.md @@ -22,7 +22,7 @@ The host preserves `ELECTRON_RUN_AS_NODE` for child startup; the bootstrap remov File mode, observed denial and enforcement completeness travel in `PtcRunResult.sandbox` separately from the program outcome. Restricted execution fails if the required sandbox backend cannot launch. Full access is an explicit policy mode. Program success does not prove full enforcement, and neither the `process` descriptor nor the extra control pipe claims multi-tenant isolation. -The private Python provider keeps its existing execution implementation and configured wall deadline. Its resolver accepts cwd but rejects an explicit file policy or per-call timeout override; a capability descriptor never silently grants unsupported protection. +The [experimental Python provider](../../../../packages/experimental/ptc-runtime-python/README.md) shares the resolved file policy and sandbox diagnostics while retaining its local Unix execution and configured wall deadline. Per-call timeout overrides remain unsupported. ### Control and lifetime diff --git a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md index 94f3a37eb7..429351cca2 100644 --- a/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-11-sandboxed-node-ptc-runtime.zh.md @@ -22,7 +22,7 @@ Host 在子进程启动时保留 `ELECTRON_RUN_AS_NODE`;bootstrap 在求值前 文件模式、观察到的拒绝与强制完整性通过 `PtcRunResult.sandbox` 独立于程序结果传递。所需沙箱后端无法启动时,受限执行失败。完整访问是显式策略模式。程序成功不证明完整强制能力,`process` 描述符与额外控制管道也不声明多租户隔离。 -私有 Python 提供方保留现有执行实现与配置的经过时间截止。其解析器接受 cwd,但拒绝显式文件策略或单次 timeout 覆盖;能力描述符不会静默授予不受支持的保护。 +[实验性 Python 提供方](../../../../packages/experimental/ptc-runtime-python/README.zh.md)共享已解析文件策略与沙箱诊断,同时保留本地 Unix 执行和配置的经过时间截止。仍不支持单次 timeout 覆盖。 ### 控制与生命周期 diff --git a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.i18n.yaml deleted file mode 100644 index 35bfbf6d4b..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-windows-directory-installation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-windows-directory-installation.md -/agent-note-replace-windows-application-directories-after-staging: - en: 7cdd071dd6ad760f - zh: eb8510c6c09f0068 -/agent-note-replace-windows-application-directories-after-staging/problem: - en: fa0015dca8cd9e85 - zh: a826baa59c0a3bd1 -/agent-note-replace-windows-application-directories-after-staging/decision: - en: 5bec9a233d21bb35 - zh: 50e8ba778b14b48e -/agent-note-replace-windows-application-directories-after-staging/consequences: - en: 3562fbc117967fb3 - zh: 6600b28ea6202f68 -/agent-note-replace-windows-application-directories-after-staging/alternatives-considered: - en: 0971baacd045ddef - zh: c908624104e242f9 -/agent-note-replace-windows-application-directories-after-staging/verification: - en: 650fdccdf35ad9d3 - zh: 6069b9acb5f03878 diff --git a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml deleted file mode 100644 index ac5f77829d..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-ptc-runtime-vocabulary.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-12-ptc-runtime-vocabulary.md -/agent-note-ptc-runtime-vocabulary: - en: 4e585bb88c918c48 - zh: 1ee36889df928e84 -/agent-note-ptc-runtime-vocabulary/problem: - en: 1281bbda56bc41e0 - zh: 719dc99572bd7921 -/agent-note-ptc-runtime-vocabulary/decision: - en: b3ca07cb6a92f358 - zh: f3b763124b483563 -/agent-note-ptc-runtime-vocabulary/alternatives-considered: - en: 38af2c476cc408bc - zh: 9f1ebd14829250e3 -/agent-note-ptc-runtime-vocabulary/consequences: - en: bb68774fb0a4bcc3 - zh: b866e8763b3a4f74 diff --git a/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.i18n.yaml new file mode 100644 index 0000000000..49a9f432ee --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-09-13-session-working-directory.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-13-session-working-directory.md +/agent-note-session-working-directory: + en: 4d5b74f6b55a2270 + zh: 8323ea3fbdf8fd8d +/agent-note-session-working-directory/problem: + en: 31fba384d90ce7d2 + zh: f8b814868f3280c3 +/agent-note-session-working-directory/decision: + en: 9ccde1c5e635b9c0 + zh: 4dc76229703d3ef7 +/agent-note-session-working-directory/alternatives-considered: + en: f0995feb9b6bcce8 + zh: 97417085fd17f70f +/agent-note-session-working-directory/consequences: + en: 2dd3a4de72ee6d74 + zh: be5d8a80c8eaac6e diff --git a/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.md b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.md new file mode 100644 index 0000000000..8151c0e85a --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.md @@ -0,0 +1,47 @@ +# Agent Note: Session working directory + +Status: implemented + +English | [中文](2026-09-13-session-working-directory.zh.md) + +## Problem + +A fixed Session creation directory cannot represent a task that changes checkout during its conversation. Independent tools resolving paths from different sources can read and write different projects, while changing the process directory also affects unrelated Sessions. Putting mutable directory information in the system prompt changes the reusable prompt prefix. + +## Decision + +`dsh-working-directory` owns one logged effective directory per Session. Its projection initializes from the immutable original project and folds `working-directory/change`; consumers read that owner instead of deriving execution paths from the header or sandbox root. Directory changes validate through the filesystem provider, serialize within the Session, and publish only after the new directory is available. + +The `dsh-base` composition mounts `working_directory({ cd? })` globally. The tool reads or changes the Session directory, so every Agent preset can select or recover it, including presets without file-editing tools. The Web composition keeps action tools in preset scopes. + +Required user-context snapshots carry the current value; each committed change attempts to queue a notice through the ordinary Agent inbox. Cancellation or disposal may discard unadmitted notices while the directory event survives for subsequent requests. The original project is the recovery destination when the selected directory disappears. Missing recovery destinations produce an error. Write permissions remain independent. + +Subagents capture the parent's current directory or an explicit override and commit their selected directory after inherited history. Existing processes, terminals, and persistent shells keep their process-local directory. Project instructions, skills, and completion follow the effective directory without changing Session identity, grouping, or runtime composition. + +Live Session skill catalogs and file completion validate through the same directory owner. These reads can commit recovery and queue its notice before discovery, keeping discovered files and subsequent operations tied to the committed Session directory. Cold skill catalogs read the recorded projection without activating an Agent. + +Web sidebar terminal environment lookup reads the effective directory without filesystem validation or recovery, so a missing directory does not prevent reconnecting to a retained terminal. New terminal creation validates and recovers through the directory owner before spawning; the process keeps that directory independently of later Session changes. + +Playwright MCP and Chrome DevTools MCP validate the effective directory when acquiring a Session's MCP connection and use it to launch the server. Later directory changes do not restart the retained server or change its process directory. + +File links and command directory labels use result metadata captured when each operation runs, so historical rows remain correct after later directory changes. + +The [prompt-variables note](2026-07-05-prompt-variables-and-tool-guidance-ownership.md) retains strict interpolation, route-variable ownership, and tool-guidance ownership. The [environment-suffix note](../../archived/bug-fix/2026-09-06-environment-prompt-suffix.md) retains ordered prefix/suffix placement for deployment, Harness source, and Web guidance. This decision owns directory state and placement in user context. + +## Alternatives considered + +**Rewrite the Session header.** The header is immutable identity metadata used by storage, grouping, and permission roots. Rewriting it would conflate current execution state with the original project. + +**Let worktree tools own directory state.** Ordinary directory changes, filesystem tools, and subagents need the same value. A Git-specific owner would duplicate selection and recovery. + +**Change the process directory or synchronize every shell.** Concurrent Sessions and existing subprocesses have independent lifetimes. A Session change affects new directory-based operations, while a shell's `cd` remains local. + +**Keep directory text in the system prompt.** Mutable operational context belongs after retained history, using the same user-context mechanism as permission state. + +## Consequences + +The runtime exposes one directory owner and preserves the original project. Consumers migrate together, including filesystem, shell, instructions, skills, subagents, and both SDKs. Directory existence can change outside the harness, so validation and logged recovery occur at use. Required runtime context remains visible even when optional context is suppressed. + +`{{cwd}}` has no loop-provided value; custom personas that depend on that built-in fail strict interpolation. Deployments remove those references using the [system-prompt migration guidance](../../../../packages/core/system-prompt/README.md). When both the current directory and its recovery destination are unavailable, prompt assembly rejects model turns; the [directory service recovery guidance](../../../../packages/session/working-directory/README.md#known-limitations-and-deferred-work) requires restoring the directory or selecting an existing absolute directory through the SDK or host service. + +Verification covers independent Sessions, relative changes, replay, cancellation, recovery, tool disposal, user-context rendering, provider inheritance, and real directory-based operations. diff --git a/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.zh.md b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.zh.md new file mode 100644 index 0000000000..46246a9d67 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-13-session-working-directory.zh.md @@ -0,0 +1,47 @@ +# Agent Note: Session working directory + +Status: implemented + +[English](2026-09-13-session-working-directory.md) | 中文 + +## Problem + +固定的 Session 创建目录无法表示在对话中切换检出目录的任务。各工具从不同来源解析路径时,可能读写不同项目;改变进程目录还会影响其他 Session。把可变目录信息放入系统提示词会改变可复用的提示词前缀。 + +## Decision + +`dsh-working-directory` 拥有每个 Session 的一个日志化有效目录。投影从不可变的原始项目初始化,并折叠 `working-directory/change`;消费者读取该服务,而不是从 header 或沙箱根目录派生执行路径。目录变更通过文件系统 provider 验证,在 Session 内串行执行,并只在新目录可用后发布。 + +`dsh-base` 组合在全局挂载 `working_directory({ cd? })`。该工具读取或改变 Session 目录,使每个 Agent preset 都能选择或恢复它,包括没有文件编辑工具的 preset。Web 组合把操作工具放在 preset 作用域中。 + +必需的用户上下文快照携带当前值;每次已提交变更都会尝试通过普通 Agent 收件箱排入通知。取消或销毁可能丢弃尚未进入请求的通知,但目录事件仍然保留,供后续请求使用。所选目录消失时恢复到原始项目。恢复目录也消失时返回错误。写权限保持独立。 + +子代理捕获父代理当前目录或显式覆盖值,并在继承历史之后提交选定目录。已有进程、终端和持久 shell 保留进程局部目录。项目指令、技能和补全跟随有效目录,不改变 Session 身份、归属或运行时组合。 + +活跃 Session 的技能目录与文件补全通过同一个目录 owner 验证目录。这些读取可在发现操作之前提交恢复并排入通知,使发现的文件与后续操作关联到已提交的 Session 目录。冷态技能目录读取已记录的投影,不激活 Agent。 + +Web 侧栏终端的环境查询读取有效目录,不验证文件系统或执行恢复,因此目录缺失不会阻止重新连接保留的终端。新终端在创建进程前通过目录 owner 验证和恢复目录;后续 Session 目录变更不会改变该进程的目录。 + +Playwright MCP 和 Chrome DevTools MCP 在获取 Session 的 MCP 连接时验证有效目录,并在该目录启动服务器。后续目录变更不会重启保留的服务器,也不会改变其进程目录。 + +文件链接与命令目录标签使用各次操作执行时记录的结果元数据,因此后续目录变更不会使历史记录指向错误的位置。 + +[提示词变量记录](2026-07-05-prompt-variables-and-tool-guidance-ownership.zh.md)保留严格插值、路由变量归属与工具指导归属。[环境后缀记录](../../archived/bug-fix/2026-09-06-environment-prompt-suffix.md)保留部署、Harness 源码与 Web 指导的有序前缀/后缀位置。本决策拥有目录状态及其用户上下文位置。 + +## Alternatives considered + +**改写 Session header。** header 是存储、归属与权限根目录使用的不可变身份元数据。改写它会混淆当前执行状态与原始项目。 + +**让 worktree 工具拥有目录状态。** 普通目录变更、文件系统工具和子代理需要同一个值。Git 专用 owner 会重复实现选择与恢复。 + +**改变进程目录或同步所有 shell。** 并发 Session 与已有子进程具有独立生命周期。Session 变更影响新的目录相关操作,shell 内的 `cd` 则保持局部。 + +**把目录文本留在系统提示词中。** 可变操作上下文应位于保留历史之后,复用权限状态的用户上下文机制。 + +## Consequences + +运行时提供一个目录 owner,并保留原始项目。消费者一起迁移,包括文件系统、shell、指令、技能、子代理和两种 SDK。目录可能在 harness 之外消失,因此在使用时执行验证和日志化恢复。即使可选上下文被禁用,必需运行时上下文也仍然可见。 + +`{{cwd}}` 没有循环提供的值;依赖该内置变量的自定义 persona 会触发严格插值错误。部署方按照[系统提示词迁移说明](../../../../packages/core/system-prompt/README.zh.md)移除这些引用。当前目录与恢复目标均不可用时,提示词组装会拒绝模型轮次;[目录服务恢复说明](../../../../packages/session/working-directory/README.zh.md#known-limitations-and-deferred-work)要求恢复目录,或通过 SDK 或 Host 服务选择现有的绝对目录。 + +验证覆盖 Session 相互独立、相对路径变更、回放、取消、恢复、工具卸载、用户上下文渲染、provider 继承和真实目录操作。 diff --git a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.i18n.yaml index 46f602667d..ccb643fcc4 100644 --- a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.i18n.yaml @@ -9,8 +9,8 @@ en: 42235162e945e015 zh: 4edc5633720cd6cc /agent-note-reuse-the-ptc-node-sandbox-for-workflows/decision: - en: b55a2a59b8039cdd - zh: fd6bbe987a49266f + en: 5112e6adc51b9c40 + zh: 06277d1d753a9b6b /agent-note-reuse-the-ptc-node-sandbox-for-workflows/alternatives-considered: en: 56d9a25713227146 zh: 2cd89bf6ccc3b3bd diff --git a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md index 9454ffef28..297ff20f3e 100644 --- a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md +++ b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md @@ -22,7 +22,7 @@ Progress uses one binding call at a time. The first batch starts synchronously; The Node bootstrap keeps its control pipe open after sending the terminal frame until the host closes it. Unawaited binding replies may still be in flight, so eager child-side close would let an `EPIPE` race an already completed program. -The [dynamic-workflows decision](../feature/2026-07-05-dynamic-workflows.md) retains the script, structured-output, event and tool semantics; this note supersedes only its execution substrate and trust realization. The [sandboxed Node PTC decision](2026-09-11-sandboxed-node-ptc-runtime.md) retains execution and control guarantees; the explicit null deadline extends its service options. The [agent-scope runtime design](2026-07-12-agent-scope-runtime-design.md#workflow-children-are-pending-starts-or-published-records) retains pending-start and child-cleanup ownership. +The [dynamic-workflows reference](../../../../packages/workflow/workflow/README.md) retains the script, structured-output, event and tool semantics; this note supersedes only its execution substrate and trust realization. The [sandboxed Node PTC decision](2026-09-11-sandboxed-node-ptc-runtime.md) retains execution and control guarantees; the explicit null deadline extends its service options. The [agent-scope runtime design](../../../../packages/core/agent-loop/README.md) retains pending-start and child-cleanup ownership. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md index c517060657..48dd4e4262 100644 --- a/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md @@ -22,7 +22,7 @@ VM 定义辅助 API,以及协作式并发、agent 总数和条目上限。它 Node 引导程序发送终态帧后保持控制管道打开,直到 Host 将其关闭。未被等待的绑定回复可能仍在传输,因此子进程提前关闭会让 `EPIPE` 与已经完成的程序结果发生竞争。 -[动态工作流决策](../feature/2026-07-05-dynamic-workflows.zh.md)保留脚本、结构化输出、事件与工具语义;本文只取代其执行基底与信任实现。[沙箱化 Node PTC 决策](2026-09-11-sandboxed-node-ptc-runtime.zh.md)保留执行与控制保证;显式 null 截止扩展其服务选项。[agent 作用域运行时设计](2026-07-12-agent-scope-runtime-design.zh.md#workflow-children-are-pending-starts-or-published-records)保留待启动与子 agent 清理的归属规则。 +[动态工作流参考](../../../../packages/workflow/workflow/README.zh.md)保留脚本、结构化输出、事件与工具语义;本文只取代其执行基底与信任实现。[沙箱化 Node PTC 决策](2026-09-11-sandboxed-node-ptc-runtime.zh.md)保留执行与控制保证;显式 null 截止扩展其服务选项。[agent 作用域运行时设计](../../../../packages/core/agent-loop/README.zh.md)保留待启动与子 agent 清理的归属规则。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml index a0da60252b..175c7d9ac3 100644 --- a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.i18n.yaml @@ -9,8 +9,8 @@ en: 2fc4ac7bdd4a3474 zh: 21ebdd3ac9264132 /agent-note-independent-libreoffice-kit-ownership/decision: - en: 31200a9be3940dc0 - zh: 8e22be1d160ec3e4 + en: 0d8e05bcb940f88f + zh: b5b55f4ee82cf6a3 /agent-note-independent-libreoffice-kit-ownership/alternatives-considered: en: 55175bb30630bf0c zh: 788853ddd5f7a4e9 diff --git a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md index 13efc41e56..50e4ab6e0c 100644 --- a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md +++ b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md @@ -14,7 +14,7 @@ The `deepseek-harness/libreoffice-kit` repository owns the reusable `@deepseek-a Kit releases run independently of Harness releases. The kit repository qualifies and publishes the Node API and engine npm packages at a shared version, starting at `0.0.1`. Harness consumes an exact npm version and commits its dependency resolution in `pnpm-lock.yaml`; Harness releases neither build nor publish kit packages. -The upstream API declares platform engines as optional dependencies. The [platform engine decision](2026-09-15-platform-office-engines.md) supersedes the original required-WASM fallback policy. Desktop installs the kit as an external npm dependency. Python sidecars keep the Worker, selected engine, and their dependency closure on the real filesystem, outside the executable’s virtual filesystem. Conversion requires neither downloads nor GitHub credentials. +The upstream API declares platform engines as optional dependencies. The [platform engine reference](../../../../packages/document/office-to-pdf/README.md) supersedes the original required-WASM fallback policy. Desktop installs the kit as an external npm dependency. Python sidecars keep the Worker, selected engine, and their dependency closure on the real filesystem, outside the executable’s virtual filesystem. Conversion requires neither downloads nor GitHub credentials. The kit repository owns engine qualification and corresponding source materials. MPL-2.0 declarations, source availability, and redistribution notices accompany the API and engines; Harness retains these materials when packaging them. The notices check accepts the exact API, WASM, macOS ARM64/x64, and Windows ARM64/x64 package identities only at MPL-2.0; unrelated packages and changed license terms still reject. diff --git a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md index 720ca2ba3f..222197ab5b 100644 --- a/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md @@ -14,7 +14,7 @@ LibreOffice 编译、源码补丁、平台资格验证和大型二进制发布 kit 发布流程独立于 Harness 发布流程。kit 仓库验证并以统一版本发布 Node API 和引擎 npm 包,起始版本为 `0.0.1`。Harness 消费精确的 npm 版本,并在 `pnpm-lock.yaml` 中提交依赖解析结果;Harness 发布既不构建也不发布 kit 包。 -上游 API 将平台引擎声明为可选依赖。[平台引擎决策](2026-09-15-platform-office-engines.zh.md)取代最初强制携带 WASM 回退引擎的策略。Desktop 将 kit 作为外部 npm 依赖安装。Python sidecar 将 Worker、所选引擎及其依赖闭包保留在真实文件系统中,位于可执行文件的虚拟文件系统之外。转换无需下载或 GitHub 凭据。 +上游 API 将平台引擎声明为可选依赖。[平台引擎参考](../../../../packages/document/office-to-pdf/README.zh.md)取代最初强制携带 WASM 回退引擎的策略。Desktop 将 kit 作为外部 npm 依赖安装。Python sidecar 将 Worker、所选引擎及其依赖闭包保留在真实文件系统中,位于可执行文件的虚拟文件系统之外。转换无需下载或 GitHub 凭据。 kit 仓库负责引擎资格验证和对应源码材料。API 和引擎携带 MPL-2.0 声明、可访问源码及再分发声明;Harness 打包时保留这些材料。再分发声明检查仅在许可为 MPL-2.0 时接受 API、WASM、macOS ARM64/x64 和 Windows ARM64/x64 的精确包标识;无关包和改变后的许可条款仍被拒绝。 diff --git a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml deleted file mode 100644 index 621d099284..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-sidebar-layout-provider-recovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-14-sidebar-layout-provider-recovery.md -/agent-note-sidebar-layout-persistence-and-provider-recovery: - en: 78dee80210908b7d - zh: 01a490734740a523 -/agent-note-sidebar-layout-persistence-and-provider-recovery/problem: - en: 8adb7105e89be803 - zh: 96c4e92e85b6980e -/agent-note-sidebar-layout-persistence-and-provider-recovery/decision: - en: aa09cb90f663dfe8 - zh: edaabb76aa6dc8de -/agent-note-sidebar-layout-persistence-and-provider-recovery/alternatives-considered: - en: 4a9b1234b0289ae7 - zh: 81854e0e9d32b6da -/agent-note-sidebar-layout-persistence-and-provider-recovery/consequences: - en: 4daf813b59205ca3 - zh: 917c2b975c9a67dd diff --git a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.i18n.yaml index d2b3a75106..f1173f2864 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.i18n.yaml @@ -12,8 +12,8 @@ en: a15c7c13f167e5ac zh: 63bcc2ef60c2f344 /agent-note-client-session-references-reference-sources-and-ui-status/decision/scope-and-ownership: - en: d9d52caab1468205 - zh: 1b7665610bb025c6 + en: 3f04f9c2b7b0fc2b + zh: ebfcbac3de8463a3 /agent-note-client-session-references-reference-sources-and-ui-status/decision/addresses-bindings-and-references: en: 3743abc8bca00348 zh: d79e6a9311f0a2fe @@ -30,8 +30,8 @@ en: 04802baa0147fec1 zh: b86037bf2e4bc936 /agent-note-client-session-references-reference-sources-and-ui-status/decision/main-area-navigation-and-presentation: - en: c46c9ca54ea9eaad - zh: 918dc8eebff5edf3 + en: 508268f083a644a9 + zh: 9a3c0931d49fcedc /agent-note-client-session-references-reference-sources-and-ui-status/decision/presets-and-creation-flows: en: 70ef26a3cd399b14 zh: 5c81515ff896f91a diff --git a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.md b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.md index ca4055bd9e..83f2ec688b 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.md +++ b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.md @@ -30,7 +30,7 @@ Client Session objects, Agent-scoped Client Contexts, references, consumer-sourc The [Client layering design](../../implemented/architecture/2026-08-20-client-session-conversation-ownership.md) defines one-way data, adapter, renderer, and presentation dependencies. Reference-source bookkeeping does not give the Controller a dependency on UI packages. -This decision partially supersedes the list-selected scope lifecycle in the [Web Client Session scope and provide-channel decision](2026-07-25-web-client-session-scope-and-provide-channel.md); that note retains the blank-Session and adoption rationale under explicit Provider ownership. +The [blank Session decision](2026-09-17-process-local-blank-sessions.md) owns creation and reuse; this decision owns explicit Provider references and adoption. ### Addresses, bindings, and references @@ -156,7 +156,7 @@ The main view privately persists its target identity and subagent address under | Chat/Trajectory restoration | Restore the view for the explicitly selected main target; independently bound views keep their own state | | Cordis inventory panel | One list without current/other grouping; no public runner getter for main-area selection | -Source metadata does not change when DOM focus moves or when a global panel hides a retained view. The [global main-panel design](../../implemented/architecture/2026-09-08-global-main-panels.md) owns panel selection and layout; Session reference ownership does not replace it. +Source metadata does not change when DOM focus moves or when a global panel hides a retained view. The [global main-panel design](../../../../packages/client/ui-layout/README.md) owns panel selection and layout; Session reference ownership does not replace it. Conversation retains its `hero`, `settling`, and `active` composition and existing history-loading and `openError` handling. Acquisition adds no outer loading/error phase presentation, extra composer-hiding condition, Retry button, or replacement Sidebar recovery panel. Existing error handlers continue to handle their errors; call sites without error presentation gain none. Promise rejection and correct reference release do not imply an additional UI handler. diff --git a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.zh.md b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.zh.md index b8c6c313f6..542def8a76 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-client-session-references.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-client-session-references.zh.md @@ -30,7 +30,7 @@ Client Session 对象、Agent 作用域的 Client Context、引用、使用方 [Client 分层设计](../../implemented/architecture/2026-08-20-client-session-conversation-ownership.zh.md)定义数据、适配器、渲染器与展示层的单向依赖。引用来源统计不会使 Controller 依赖 UI 包。 -本决策部分取代 [Web Client Session scope 与 provide channel 决策](2026-07-25-web-client-session-scope-and-provide-channel.zh.md)中由 list 选择驱动的 scope 生命周期;后者保留显式 Provider 所有权下的 blank Session 与收养语义理由。 +[空白 Session 决策](2026-09-17-process-local-blank-sessions.zh.md)负责创建与复用;本决策负责显式 Provider 引用与收养。 ### 地址、binding 与引用 @@ -156,7 +156,7 @@ Conversation 的引用属于其视图所有者,不属于 Chat、Trajectory 或 | Chat/Trajectory 恢复 | 为显式选中的主目标恢复视图;独立绑定的视图保留自己的状态 | | Cordis inventory 面板 | 使用不区分 current/other 的单一列表;runner 不提供主区域选择的公开 getter | -DOM 焦点移动或全局面板隐藏仍被持有的视图时,来源元数据不变。[全局主面板设计](../../implemented/architecture/2026-09-08-global-main-panels.zh.md)拥有面板选择与布局;Session 引用所有权不替代它。 +DOM 焦点移动或全局面板隐藏仍被持有的视图时,来源元数据不变。[全局主面板设计](../../../../packages/client/ui-layout/README.zh.md)拥有面板选择与布局;Session 引用所有权不替代它。 Conversation 保留 `hero`、`settling`、`active` 组合与既有历史加载和 `openError` 处理。获取引用不增加外层 loading/error 阶段展示、额外隐藏 composer 的条件、Retry 按钮或替换 Sidebar 内容的恢复面板。已有错误处理方继续处理自己的错误;没有错误展示的调用点不增加展示。Promise 拒绝与正确释放引用不意味着额外增加 UI 处理方。 diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml index b5ebc209af..8d942f2a1d 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.i18n.yaml @@ -9,8 +9,8 @@ en: 9cf7ba7dfd36aee0 zh: 356fdbb01870981e /agent-note-native-desktop-fatal-recovery/decision: - en: a3b9aa3659117524 - zh: 6f25cb295c37cddd + en: 02d07a5458e01321 + zh: de9f480b7196f2c0 /agent-note-native-desktop-fatal-recovery/alternatives-considered: en: 624d62bc0b73c37a zh: 81c3d7cd66782f73 diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md index fc1564b02e..75364ae8ee 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.md @@ -16,7 +16,7 @@ The first report claims presentation before awaiting the dialog. Later reports r The Web document stays in place. A carrier callback owns startup failure presentation while the shared boot page retains its spinner; ordinary browser boot still renders its own failure report. Only the primary application frame may report a Web boot failure. Backend state remains in the main process, and native recovery directly owns disabling all third-party bundles. Desktop has no profile reset or emergency recovery document. A fatal backend failure requires one of the native recovery actions rather than an in-process retry. -This supersedes recovery-page and reset behavior in the [immediate-window decision](2026-09-09-desktop-immediate-window-and-direct-start.md), whose immediate visibility and direct Host startup rationale remain active. The [Web wrapper decision](2026-09-10-desktop-web-wrapper.md) owns shared plugin management and native profile preparation. +The [Desktop reference](../../../../apps/desktop/README.md) defines immediate window visibility and direct Host startup; this decision owns native fatal recovery. The [Web wrapper decision](2026-09-10-desktop-web-wrapper.md) owns shared plugin management and native profile preparation. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md index ceda54b414..f5c1775278 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-desktop-native-fatal-recovery.zh.md @@ -16,7 +16,7 @@ Electron 在每个应用进程中提供一次原生致命错误对话框。明 Web 文档保留在原位。宿主回调负责启动失败展示,共享启动页保留加载动画;普通浏览器启动仍显示自身的失败报告。只有主应用框架可以上报 Web 启动失败。后端状态保留在主进程中,原生恢复直接负责禁用全部第三方 bundle。Desktop 不提供 profile 重置或应急恢复文档。后端致命故障必须通过原生恢复操作处理,不在当前进程中重试。 -本决策取代[立即显示窗口决策](2026-09-09-desktop-immediate-window-and-direct-start.zh.md)中的恢复页面和重置行为;其立即显示与直接启动 Host 的理由仍有效。[Web 薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责共享插件管理和原生 profile 准备。 +[Desktop 参考](../../../../apps/desktop/README.zh.md)定义立即显示窗口与直接启动 Host;本决策负责原生致命故障恢复。[Web 薄壳决策](2026-09-10-desktop-web-wrapper.zh.md)负责共享插件管理和原生 profile 准备。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml index 9283aaa9e4..d1e00c2cd5 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.i18n.yaml @@ -9,14 +9,14 @@ en: edefff7cd986a509 zh: a270839fa86976a2 /agent-note-guided-plugin-installation/decision: - en: b661fb786e7182c5 - zh: 4cdeb584639fda23 + en: 30f1e636973df06a + zh: 0f874b88a5d4d7f5 /agent-note-guided-plugin-installation/alternatives-considered: en: be64b541f4d81e7c zh: 8600531c08ab4288 /agent-note-guided-plugin-installation/consequences: - en: e4536b20f3587497 - zh: a8eeb1d3c578b785 + en: bc1b116dd15ff420 + zh: e886e84cc3081feb /agent-note-guided-plugin-installation/testing: en: 1e121dca5f7dc572 zh: bcdb1b6482cc1af6 diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md index 68bd519b7c..c0e9e68325 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md @@ -14,7 +14,7 @@ The install dialog put a spec straight into `pnpm add` and showed pnpm's termina **A failed or cancelled installation restores the profile files.** `installBundle` snapshots `package.json` and `pnpm-lock.yaml` before pnpm runs and puts them back when pnpm fails, when the run is cancelled, or when the package pnpm added declares no bundle patch. This reverses, for installations only, the [manager's decision](2026-09-14-current-profile-plugin-management.md) to retain partial changes: an installation that did not produce a usable bundle must not leave a dependency the page cannot show. Removals keep that decision. Downloaded files can stay under `node_modules` and the pnpm store. -**A failed run is classified where the facts are.** `classifyInstallFailure` reads how the run ended and what pnpm printed — its `ERR_PNPM_*` codes and Node's errno names — into `packageResult.kind`. A run the manager terminated is classified `timeout` whatever exit status the signal left behind, so installation restores the files and removal reports failure instead of reading it as a success ([run bounds](../bug-fix/2026-09-23-bounded-pnpm-runs.md)). The client shows the kind as one line and folds pnpm's output behind the details. Parsing the log is confined to this one function with a fixture-driven test. +**A failed run is classified where the facts are.** `classifyInstallFailure` reads how the run ended and what pnpm printed — its `ERR_PNPM_*` codes and Node's errno names — into `packageResult.kind`. A run the manager terminated is classified `timeout` whatever exit status the signal left behind, so installation restores the files and removal reports failure instead of reading it as a success ([run bounds](../../../../packages/boot/plugin-manager/README.md)). The client shows the kind as one line and folds pnpm's output behind the details. Parsing the log is confined to this one function with a fixture-driven test. **GitHub connection failures have a short budget before installation.** The Host runs a read-only `git ls-remote` check with the profile's Git and proxy configuration and a default `githubConnectionTimeoutMs` of 5000 ms. Credential helpers and prompts are disabled for this check, so authentication cannot consume the connection budget. Only network failures and timeouts prevent pnpm from starting; pnpm still owns authentication, HTTPS-to-SSH fallback, ref resolution and the full installation. The budget does not limit downloads or builds. The existing failure kind and `failedAt: 'spec-host'` let the Client show a connection error or timeout and offer a domestic registry for a replacement package spec. Cancellation and manager disposal terminate the check and its descendants before settling. @@ -38,7 +38,7 @@ The install dialog put a spec straight into `pnpm add` and showed pnpm's termina ## Consequences -`inspect`, `cancelInstall`, and the `plugin-manager/changed`, `plugin-manager/install-log`, and `plugin-manager/install-state` events join the manager's Remote; `listBundles` carries titles, rows, and overrides; `ChangeResult` gains `cancelled`, `bundle`, and `packageResult.kind`; the config gains `pnpmCommand`, `inspectTimeoutMs` and `githubConnectionTimeoutMs`. The dialog is four screens over one subject card. +`inspect`, `cancelInstall`, and the `plugin-manager/changed`, `plugin-manager/install-log`, and `plugin-manager/install-state` events join the manager's Remote; `listBundles` carries titles, rows, and overrides; `ChangeResult` gains `cancelled`, `bundle`, `version`, and `packageResult.kind`; the config gains `pnpmCommand`, `inspectTimeoutMs` and `githubConnectionTimeoutMs`. The dialog is four screens over one subject card. ## Testing diff --git a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md index f3cbefedeb..0d914869a3 100644 --- a/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md @@ -14,7 +14,7 @@ Status: implemented **失败或被取消的安装恢复 profile 文件。** `installBundle` 在 pnpm 运行前快照 `package.json` 与 `pnpm-lock.yaml`,在 pnpm 失败、运行被取消、或 pnpm 装入的包没有声明组合包 patch 时把它们放回去。这只针对安装反转了[管理器的决定](2026-09-14-current-profile-plugin-management.zh.md)中"保留部分改动"的部分:没有产出可用组合包的安装,不能留下一个页面无法显示的依赖。删除仍沿用那个决定。已下载文件可能留在 `node_modules` 与 pnpm 缓存中。 -**失败在事实所在处分类。** `classifyInstallFailure` 依据运行的结束方式和 pnpm 的输出——它的 `ERR_PNPM_*` 码和 Node 的 errno 名——给出 `packageResult.kind`。被管理器终止的运行,不论信号留下什么退出状态都归类为 `timeout`,因此安装会还原文件,删除则报告失败而不会读成成功([运行上界](../bug-fix/2026-09-23-bounded-pnpm-runs.zh.md))。客户端把 kind 显示成一句话,把 pnpm 输出折叠进详情。对日志的解析只存在于这一个函数,用夹具驱动的测试钉住。 +**失败在事实所在处分类。** `classifyInstallFailure` 依据运行的结束方式和 pnpm 的输出——它的 `ERR_PNPM_*` 码和 Node 的 errno 名——给出 `packageResult.kind`。被管理器终止的运行,不论信号留下什么退出状态都归类为 `timeout`,因此安装会还原文件,删除则报告失败而不会读成成功([运行上界](../../../../packages/boot/plugin-manager/README.zh.md))。客户端把 kind 显示成一句话,把 pnpm 输出折叠进详情。对日志的解析只存在于这一个函数,用夹具驱动的测试钉住。 **GitHub 连接失败在安装前有单独的短时限。** 宿主通过只读的 `git ls-remote` 检查连接,沿用 profile 的 Git 与代理配置,`githubConnectionTimeoutMs` 默认为 5000 毫秒。这次检查禁用凭据助手和认证提示,避免认证过程占用连接预算。只有网络失败与超时会阻止 pnpm 启动;认证、HTTPS 到 SSH 的回退、ref 解析及完整安装仍由 pnpm 负责。时限不约束下载或构建。已有的失败类型与 `failedAt: 'spec-host'` 让客户端显示连接失败或超时,并提供国内镜像供用户输入另一个包名。取消安装或销毁管理器会先终止检查及其子进程,再结束操作。 @@ -38,7 +38,7 @@ Status: implemented ## 后果 -`inspect`、`cancelInstall` 以及 `plugin-manager/changed`、`plugin-manager/install-log`、`plugin-manager/install-state` 事件加入管理器的 Remote;`listBundles` 携带标题、行与覆盖项;`ChangeResult` 增加 `cancelled`、`bundle` 与 `packageResult.kind`;配置增加 `pnpmCommand`、`inspectTimeoutMs` 与 `githubConnectionTimeoutMs`。对话框是围绕同一张主题卡的四个画面。 +`inspect`、`cancelInstall` 以及 `plugin-manager/changed`、`plugin-manager/install-log`、`plugin-manager/install-state` 事件加入管理器的 Remote;`listBundles` 携带标题、行与覆盖项;`ChangeResult` 增加 `cancelled`、`bundle`、`version` 与 `packageResult.kind`;配置增加 `pnpmCommand`、`inspectTimeoutMs` 与 `githubConnectionTimeoutMs`。对话框是围绕同一张主题卡的四个画面。 ## 测试 diff --git a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml deleted file mode 100644 index 7e6309f2da..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-narrow-pi-ai-runtime-imports.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-narrow-pi-ai-runtime-imports.md -/agent-note-narrow-pi-ai-runtime-imports: - en: d29340bbc4bf0e5b - zh: 12a39c4efe53b580 -/agent-note-narrow-pi-ai-runtime-imports/problem: - en: 4b2048b356064ada - zh: 9926d7c54da73852 -/agent-note-narrow-pi-ai-runtime-imports/decision: - en: d425c01ec05ee7f2 - zh: 8d58d6db913720c4 -/agent-note-narrow-pi-ai-runtime-imports/alternatives-considered: - en: c663ce67f3831d3f - zh: de280dc149d5a6a0 -/agent-note-narrow-pi-ai-runtime-imports/consequences: - en: 9707284a83c1294a - zh: c53be621aac00fc9 diff --git a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml deleted file mode 100644 index c20b10b6fe..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-platform-office-engines.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md -/agent-note-one-office-engine-per-platform: - en: 5ba8eea6deb11824 - zh: 7d1cb289fa278c74 -/agent-note-one-office-engine-per-platform/problem: - en: 803030fa18966fef - zh: bb1bbd185860f533 -/agent-note-one-office-engine-per-platform/decision: - en: 8cea2cb889666af7 - zh: 346adb1e99787c78 -/agent-note-one-office-engine-per-platform/alternatives-considered: - en: d406d0a9f4dd503a - zh: b2685e11d706799e -/agent-note-one-office-engine-per-platform/consequences: - en: 7e236c479975eee6 - zh: 94a8e0e8883bd324 diff --git a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.i18n.yaml index 0c59a93381..e2da7be945 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.i18n.yaml @@ -9,8 +9,8 @@ en: 44615a7a0939054e zh: b8efa721321ba0d7 /agent-note-creator-mode-installs-persistent-plugin-bundles/decision: - en: 3530a89a72e35bdd - zh: 3601941563747b5d + en: c0d74db1b2b16576 + zh: b8713a6aadf007f9 /agent-note-creator-mode-installs-persistent-plugin-bundles/alternatives-considered: en: af527d8aabdfd59c zh: 34388da992d2ab56 diff --git a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.md b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.md index 04ccbb83fe..5723eba63e 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.md +++ b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.md @@ -12,7 +12,7 @@ Agents need to install capabilities and use them during the same conversation. G Creator mode enables the existing `plugin_manager` tool. Agents author packages and Loader YAML patches as workspace files, then install them through `install_bundle`. An MCP connection is a configuration-only bundle inserting the installed `dsh-mcp-client`; a UI bundle includes a Host entry and a Client artifact. Profile locking, package installation, enablement and HMR remain owned by the existing manager. The entire management tool requires `danger-full-access` or approval for one call: profile changes can load code with host permissions and affect other sessions. Each execution, including inventory reads, uses the shared sandbox escalation helper and approval service before accessing the manager. Under lower sandbox modes, `ask` requests approval and `never` denies; a full-access session needs no additional approval. A grant does not change session permissions, but its profile changes persist. This keeps shell-equivalent Host access explicit without requiring a permanent session-wide elevation. Human-operated Web and CLI controls retain their existing behavior. -The model sees two read-only Cordis inspection tools. Generated-code define/run/stop/undefine and dynamic self-inspection tool APIs are absent. Existing runtime and Client consumers retain their services; historical session cards remain readable. This supersedes only the model-facing mutation workflow in the [self-referential toolset decision](../feature/2026-07-08-self-referential-cordis-toolset.md); its runtime ownership and sandbox rationale remain independently relevant. The [profile transaction decision](2026-09-14-current-profile-plugin-management.md) continues to govern locking, package installation, and partial failures. +The model sees two read-only Cordis inspection tools. Generated-code define/run/stop/undefine and dynamic self-inspection tool APIs are absent. Existing runtime and Client consumers retain their services; historical session cards remain readable. This supersedes only the model-facing mutation workflow in the [self-referential toolset reference](../../../../packages/extensions/cordis-host-runner/README.md); its runtime ownership and sandbox rationale remain independently relevant. The [profile transaction decision](2026-09-14-current-profile-plugin-management.md) continues to govern locking, package installation, and partial failures. Visual creation requests default to an installed Client plugin rendered in the current Web page unless the user names another destination. The development skill supplies a minimal package and effect-owned Client registration. Discovery ends when the required APIs are known; a working first version is installed before optional visual refinement. Verification uses the connected page where available. Browser authentication or operating-system setup is not a prerequisite for plugin installation, and a mock preview cannot establish an in-app result. diff --git a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.zh.md b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.zh.md index 91afa00cd3..33c1fd867f 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-16-creator-persistent-plugin-management.zh.md @@ -12,7 +12,7 @@ agent 需要安装能力并在同一段对话中使用。生成代码工具在 创造模式启用现有的 `plugin_manager` 工具。agent 将包和 Loader YAML patch 写入工作区文件,再通过 `install_bundle` 安装。MCP 连接是插入已安装 `dsh-mcp-client` 的纯配置组合包;UI 组合包包含 Host 入口和 Client 产物。profile 锁、包安装、启停和 HMR 继续由现有管理器负责。 整个管理工具要求 `danger-full-access` 或单次调用的批准:profile 变更能以宿主权限加载代码,并影响其他会话。每次执行(包括查询列表)都会先使用共享沙箱提权函数和审批服务,再访问管理器。在较低沙箱模式下,`ask` 请求审批,`never` 拒绝;完整权限会话无需额外审批。批准不改变会话权限,但本次操作的 profile 变更会持久化。这让与 shell 等价的宿主访问需要明确授权,同时不必永久提升整个会话的权限。由用户直接操作的 Web 和 CLI 控件保持原有行为。 -模型可见两个只读 Cordis 检查工具,不再提供生成代码的 define/run/stop/undefine 和动态自省工具 API。现有运行时和 Client 消费者保留其服务;历史会话卡片仍然可读。这仅取代[自引用工具集决策](../feature/2026-07-08-self-referential-cordis-toolset.zh.md)中的模型侧变更流程;其运行时所有权和沙箱依据仍有独立价值。[profile 事务决策](2026-09-14-current-profile-plugin-management.zh.md)继续规定锁、包安装和部分失败行为。 +模型可见两个只读 Cordis 检查工具,不再提供生成代码的 define/run/stop/undefine 和动态自省工具 API。现有运行时和 Client 消费者保留其服务;历史会话卡片仍然可读。这仅取代[自引用工具集参考](../../../../packages/extensions/cordis-host-runner/README.zh.md)中的模型侧变更流程;其运行时所有权和沙箱依据仍有独立价值。[profile 事务决策](2026-09-14-current-profile-plugin-management.zh.md)继续规定锁、包安装和部分失败行为。 除非用户指定其他目标,视觉创建请求默认通过已安装的 Client 插件显示在当前 Web 页面。开发 skill 提供最小包和由 effect 管理的 Client 注册示例。已知所需 API 后结束探查,在可选视觉优化之前先安装能工作的初版。有条件时使用已连接页面验证。浏览器认证或操作系统设置不是安装插件的前提,mock 预览不能证明应用内结果。 diff --git a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml deleted file mode 100644 index 8a90598c63..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-desktop-cos-upload-transport.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-16-desktop-cos-upload-transport.md -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk: - en: acead237faaa9390 - zh: ca5d23451f3b4577 -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk/problem: - en: 74f8515cdbb2a0aa - zh: a00d1b08db6d6158 -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk/decision: - en: 54cef8eacccce2e8 - zh: 8bad9ce5c932e0dd -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk/testing: - en: c15a405f365ca499 - zh: ee59d1b593473ee7 -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk/alternatives-considered: - en: faae1f132d6cd404 - zh: 1683143fbe6b07ce -/agent-note-upload-desktop-releases-through-the-tencent-cos-sdk/consequences: - en: 2453f558b12db1ec - zh: 15d77bfb842f9b31 diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml index 3be199a3df..a59c5e9371 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.i18n.yaml @@ -9,8 +9,8 @@ en: 2c774d05ae75784f zh: 619ba7f281b13233 /agent-note-host-owned-scheduled-messages/decision: - en: de81925284f13c12 - zh: d0ef35c8b58813f2 + en: 1210b3975fd26bb3 + zh: 3322e6d0aa63fdb0 /agent-note-host-owned-scheduled-messages/alternatives-considered: en: 21d7ec9a9ff3cbf2 zh: 93d03b8b3012aa7e diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md index 89d5952c9f..cd295da249 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.md @@ -14,7 +14,7 @@ The existing Schedule package owns one Host storage domain containing each task' The task domain declares the whole-unit layout. Tasks are authoritative, so the JSON backend rejects an unreadable file, malformed document, unsupported version, or invalid task as a complete load failure instead of interpreting it as deletion. Failed recovery leaves `schedule.json` unchanged, while an initially absent file opens as an empty domain; repairing the file preserves task identity. `Service.init` waits for both storage validation and runtime setup, management methods wait for the same initialization result, and an unloading owner releases late-acquired storage without starting dispatch. Dispatch admission is separate from initialization: an ancestor can close Agent admission after a task mutation commits but before runtime cleanup starts. That refusal is logged without automatic retry and does not turn the persisted mutation into a failed management result. Invalid storage and failed cleanup registration still reject startup. -This supersedes the Session-log authority, live-only activation, and read-only catalog decisions in the [Session-local reminders note](../feature/2026-08-05-durable-web-schedule.md). Its explicit absolute-time validation, fixed-rate arithmetic, and distinction between queue admission and model completion remain applicable. Historical `schedule/change` creation, fold, and invariant types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated. +This replaces the earlier Session-log authority, live-only activation, and read-only catalog decisions. The [Schedule reference](../../../../packages/schedule/schedule/README.md) owns explicit absolute-time validation, fixed-rate arithmetic, and the distinction between queue admission and model completion. Historical `schedule/change` creation and fold types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated. A plugin-sourced `followup()` synchronously appends the message to the Session inbox. Successful Session flush acknowledges durable delivery. One task-record put stores `lastDelivery`, appends a delivery record with the occurrence target, acknowledgment time, actual message id, and instruction snapshot, and updates the one-shot's `inactive` status or the recurring task's next target and status. Task state and its saved history therefore share one commit. Inactive tasks remain stored and never schedule another delivery; explicit deletion stops future delivery and removes the row together with its saved records. These records acknowledge inbox delivery, not user receipt or model execution completion. diff --git a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md index ecd15d790b..c5ee3ac455 100644 --- a/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-16-host-schedule-storage.zh.md @@ -14,7 +14,7 @@ Status: implemented 任务 domain 声明整 unit 布局。任务是权威数据,因此 JSON 后端会把文件不可读、文档损坏、版本不受支持或任务非法作为整批加载失败,而不是解释为删除。恢复失败不会改写 `schedule.json`,初始不存在的文件则作为空 domain 打开;修复该文件后会保留任务身份。`Service.init` 等待存储校验和运行时初始化,管理方法等待同一初始化结果;拥有方卸载时释放迟到的存储资源,而不开始投递。投递调度准入与初始化分开:任务变更提交后、运行时清理开始前,祖先可能关闭 Agent 准入。此类拒绝会记录日志而不自动重试,不会将已持久化的变更变成失败的管理结果。存储非法和清理注册失败仍拒绝启动。 -此决策替代[会话内提醒记录](../feature/2026-08-05-durable-web-schedule.zh.md)中的 Session 日志权威来源、仅 live 时激活和只读目录决策。其中显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成的决策仍然适用。历史 `schedule/change` 的创建、fold 和 invariant 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。 +本决策替代先前的 Session 日志权威来源、仅 live 时激活和只读目录决策。[Schedule 参考](../../../../packages/schedule/schedule/README.zh.md)负责显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成。历史 `schedule/change` 的创建与 fold 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。 插件来源的 `followup()` 同步将消息追加到 Session 收件箱。Session flush 成功后确认持久投递。一次任务记录 put 存储 `lastDelivery`,追加包含发生目标、确认时间、真实消息 id 和指令快照的发送记录,并更新一次性任务的 `inactive` 状态或重复任务的下一目标和状态。因此,任务状态与其保存的历史共用一次提交。未运行任务仍保留在存储中,且不会再次安排投递;显式删除会阻止后续投递,并把该行连同其已保存记录一并移除。这些记录确认收件箱投递,不表示用户收到消息或模型执行完成。 diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml deleted file mode 100644 index 209838a431..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-plugin-configuration-on-the-plugins-page.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md -/agent-note-plugin-configuration-on-the-plugins-page: - en: e3cb8f2cfd31ee99 - zh: 75c7c498bb5b29e4 -/agent-note-plugin-configuration-on-the-plugins-page/problem: - en: 9ca845902f1df3a2 - zh: 41e3157a843a511e -/agent-note-plugin-configuration-on-the-plugins-page/decision: - en: ccee8068ea810fcb - zh: c87c4e6c44b33a67 -/agent-note-plugin-configuration-on-the-plugins-page/consequences: - en: d3d131d6c46bc32e - zh: eb7c06f119dfbe8d -/agent-note-plugin-configuration-on-the-plugins-page/alternatives-considered: - en: 14c6db09f23c2005 - zh: 979730121cd837c3 -/agent-note-plugin-configuration-on-the-plugins-page/testing: - en: b58fdf0d5922f52b - zh: 0b999255d2b9b832 diff --git a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.i18n.yaml deleted file mode 100644 index de00746f9a..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-size-neutral-product-icon-weights.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md -/agent-note-size-neutral-product-icon-weights: - en: f0d742af33e3297e - zh: 5db6cdc26635d843 -/agent-note-size-neutral-product-icon-weights/problem: - en: aae2bf56f8695ee8 - zh: 491fa683d7cc580f -/agent-note-size-neutral-product-icon-weights/decision: - en: c0680b384d4198c8 - zh: a34f4481b7784b4f -/agent-note-size-neutral-product-icon-weights/alternatives-considered: - en: bda83cdc7a7c59a9 - zh: d3592cdf068556ea -/agent-note-size-neutral-product-icon-weights/consequences: - en: bceee31f65a32e69 - zh: d8fad0a01f1a7e7a diff --git a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md b/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md deleted file mode 100644 index 3850fcbddf..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: Size-neutral product icon weights - -Status: implemented - -English | [中文](2026-09-16-size-neutral-product-icon-weights.zh.md) - -## Problem - -Shared product-icon exports encoded one historical render size in each symbol name even though every component accepted a `size` prop. Consumers also thickened selected icons through local CSS or separate drawings, so emphasis had no named library choice and could drift between features. - -## Decision - -Every shared product glyph exports a size-neutral `Regular` and `Medium` component. `Regular` renders the supplied one-pixel artwork, while `Medium` renders the same paths with a 1.3px inherited stroke; filled regions remain unchanged. The `size` prop controls rendered dimensions, and the former numeric suffix survives only as the default size when a glyph has no 16px source. - -`LinkIconRegular` and `LinkIconMedium`, `ReferenceIconRegular` and `ReferenceIconMedium`, and the three `PermissionIcon*Regular`/`PermissionIcon*Medium` pairs follow the same rule. Repository consumers use a named weight instead of a numeric export. - -Medium weight is reserved for deliberate emphasis: new-Session controls, Settings trigger and navigation icons, Appearance choices, the composer add button, and clickable artifact-link glyphs. Other existing consumers use Regular. The composer command menu uses `PermissionIconFullAccessRegular` for the permission command because that row names permission elevation rather than the current mode. - -## Alternatives considered - -**Keep pixel sizes in export names.** Rejected because the suffix duplicated the runtime `size` prop and created separate names for identical geometry. - -**Expose one component with a `weight` prop.** Rejected because call sites would hide the chosen weight inside props; explicit component names keep visual emphasis searchable and reviewable. - -**Override stroke width in consumer CSS.** Rejected because source SVG elements can carry their own stroke widths, and local overrides would recreate inconsistent weighting outside the library. - -## Consequences - -The icon API is intentionally breaking while pre-stable: every consumer names `Regular` or `Medium`, and former 14px call sites pass an explicit size when they share geometry with a 16px export. New product glyphs add both weights from one geometry definition. Fill-only artwork exposes both names for API consistency even when stroke weight does not change its appearance. The hierarchy choices above remain explicit at their render sites rather than becoming global size or opacity overrides. diff --git a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.zh.md b/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.zh.md deleted file mode 100644 index 84031677e9..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.zh.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: 尺寸无关的产品图标线重 - -Status: implemented - -[English](2026-09-16-size-neutral-product-icon-weights.md) | 中文 - -## 问题 - -共享产品图标的导出名称包含一个历史渲染尺寸,但每个组件已经接受 `size` prop。消费方还会通过本地 CSS 或独立图稿加粗部分图标,视觉强调没有具名的图标库选项,因而可能在不同功能间漂移。 - -## 决策 - -每个共享产品图形都导出尺寸无关的 `Regular` 和 `Medium` 组件。`Regular` 渲染提供的 1px 图稿,`Medium` 使用相同路径并继承 1.3px 描边;填充区域保持不变。`size` prop 控制渲染尺寸;仅当图稿没有 16px 来源时,原数字后缀才作为默认尺寸保留。 - -`LinkIconRegular` 与 `LinkIconMedium`、`ReferenceIconRegular` 与 `ReferenceIconMedium`,以及三组 `PermissionIcon*Regular`/`PermissionIcon*Medium` 遵循同一规则。仓库内消费方使用具名线重,不再使用带数字的导出名。 - -Medium 线重只用于明确强调的场景:新建会话控件、设置入口与导航图标、外观选项、输入框加号按钮,以及可点击产物链接的图标。其他既有消费方使用 Regular。编辑器加号菜单中的权限命令使用 `PermissionIconFullAccessRegular`,因为该行表示权限提升,而不是当前权限模式。 - -## 考虑过的替代方案 - -**在导出名中保留像素尺寸。** 否决,因为后缀重复了运行时 `size` prop,并为相同几何创建了多个名称。 - -**只导出一个带 `weight` prop 的组件。** 否决,因为调用点会把线重选择隐藏在 prop 中;显式组件名便于搜索和审查视觉强调。 - -**在消费方 CSS 中覆盖描边宽度。** 否决,因为源 SVG 元素可能自带描边宽度,本地覆盖会在图标库外重新产生不一致线重。 - -## 后果 - -图标 API 在预稳定阶段有意进行破坏性调整:每个消费方明确选择 `Regular` 或 `Medium`;原 14px 调用点与 16px 导出共享几何时会显式传入尺寸。新增产品图形从同一份几何定义同时提供两种线重。仅填充的图稿也为 API 一致性导出两个名称,即使线重不会改变其外观。上述层级选择继续由对应渲染点显式声明,不会变成全局尺寸或透明度覆盖。 diff --git a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.i18n.yaml deleted file mode 100644 index 16d3ced836..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-frame-shell-leading-window-chrome-seat.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.md -/agent-note-frame-owned-shell-leading-window-chrome-seat: - en: 3e2cfa57af6b53e7 - zh: 78fe101ad11a6330 -/agent-note-frame-owned-shell-leading-window-chrome-seat/problem: - en: 09b5defb78287f7e - zh: b7333ce5077914a2 -/agent-note-frame-owned-shell-leading-window-chrome-seat/decision: - en: 04853eeed03a00c5 - zh: 9542acfaa6258842 -/agent-note-frame-owned-shell-leading-window-chrome-seat/alternatives-considered: - en: 81e997e43d185834 - zh: 38eda1f9a4bac318 -/agent-note-frame-owned-shell-leading-window-chrome-seat/consequences: - en: 7d95c4c155f8e3c3 - zh: b3f88fe2fb7a77f0 -/agent-note-frame-owned-shell-leading-window-chrome-seat/testing: - en: 2fcc157c9c4f0b81 - zh: 3b0e6135b1c10db1 diff --git a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.md b/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.md deleted file mode 100644 index cc3ef57690..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.md +++ /dev/null @@ -1,38 +0,0 @@ -# Agent Note: Frame-owned shell.leading window-chrome seat - -Status: implemented - -English | [中文](2026-09-17-frame-shell-leading-window-chrome-seat.zh.md) - -## Problem - -The [macOS hidden-titlebar work](../feature/2026-09-13-macos-hidden-titlebar-vibrancy.md) hid the collapsed sidebar column entirely on darwin and moved the reopen and New Session controls into a conversation-header slot (`conversation.session.header.leading`), shown via CSS while `data-sidebar-collapsed` was published. Only the Conversation had that seat: with any other main panel selected (the plugin manager, or any future global panel), a collapsed window kept the floating traffic lights over the panel's content with no reopen control anywhere on screen. Each new panel would have had to grow its own leading seat and repeat the same clearance geometry. - -## Decision - -The window chrome belongs to the frame, not to one panel. ui-layout declares a fifth root-scoped child slot, `shell.leading` (single), and AppFrame renders its seat as a frame-level box over the columns — mounted only on the darwin desktop while `sidebarCollapsed`, the full-hide state that leaves window chrome without a home (Windows' zero-width collapse keeps its fixed caption controls, so the seat must not mount a second set there). The seat sits at the frame's top-left (left 88px clears the hiddenInset traffic lights, top 11px centers the 28px controls on the conversation title row, z-index 15 above column content and below frame overlays) and carries `-webkit-app-region: no-drag`, subtracting itself from any drag band beneath it. - -Under the same collapsed condition the frame publishes `--dsh-frame-leading-clearance: 160px` — the inline band the traffic lights plus the seat's two controls occupy, measured from the frame's left edge. Window fullscreen hides the traffic lights (the desktop preload mirrors the state onto `html[data-fullscreen]`): the seat moves to `left: 12px` into their vacated band and the clearance drops to `84px`. The conversation title row is the one consumer: it pads `max(0px, clearance - 20px)` (its header already pads 20px). Entry pages that start below the window strip in both sidebar states (the plugin manager's page head) instead pad their top by `--dsh-frame-top-clearance` (48px, published on the darwin frame unconditionally), which clears the strip vertically rather than indenting inline. - -ui-sidebar registers `HeaderLeadingControls` into `shell.leading` instead of the conversation seat, reusing the shell's inject face and locale; the component renders unconditionally because the frame owns the mount condition, and its platform check and CSS collapse-gating are deleted. This also drops ui-sidebar's dependency on ui-conversation. The original `conversation.session.header.leading` slot is gone per the pre-stable API rule (every consumer updated, no compatibility shim); the conversation header now exposes its own sessionless `conversation.header.leading` seat (root-scoped, rendered without a Session) as a public extension point, empty in the shipped composition because the window-chrome controls live in `shell.leading`. - -## Alternatives considered - -**Keeping the conversation seat and adding one per panel.** Every main panel would repeat the seat markup, the drag-region subtraction, and the traffic-light geometry, and a panel that forgot would strand a collapsed window without a reopen control. One frame-owned seat makes the guarantee structural. - -**Mounting the occupant always and gating visibility in CSS (the prior mechanism).** This kept dead DOM and a no-drag subtraction in the header on every platform and every collapse state, and it could not help non-conversation panels at all. A frame-level TSX mount keeps the condition, the seat geometry, and the clearance publication in one file. - -**Gating the seat on rightbar fullscreen as well.** Unnecessary: the fullscreen right panel (z-index 40) covers the seat (15) visually, and it already opts its whole box out of window dragging, so the seat's geometry cannot puncture it. - -**Publishing clearance as a fixed padding each consumer hardcodes.** A raw `160px` in each panel would silently desynchronize from the seat's real band; one custom property published under exactly the mount condition keeps consumers correct when the geometry changes. - -## Consequences - -- Every current and future main panel keeps the reopen and New Session controls while the sidebar is hidden, at zero per-panel cost; the conversation opts into `--dsh-frame-leading-clearance`, and entry pages take the unconditional `--dsh-frame-top-clearance` instead. -- `conversation.session.header.leading` no longer exists in the client catalog; `shell.leading` carries the window chrome as a public root-scoped seat, while the conversation header's sessionless `conversation.header.leading` remains a separate public seat with no shipped occupant. -- ui-sidebar no longer depends on ui-conversation (inject list, devDependency, and tsconfig reference removed). -- The mount condition names darwin explicitly: Windows' `data-windows-titlebar` collapse also yields a zero-width column but keeps its own fixed caption controls, which a frame seat would duplicate. A platform that later hides its column without leaving its own chrome opts in by widening the condition. - -## Testing - -The ui-layout app-frame spec pins the seat's mount and unmount across the darwin collapse and its absence beside the 56px rail; the apply spec pins the five-slot declaration and teardown. The ui-sidebar apply spec pins the `shell.leading` registration (component, locale, shared inject face) and its removal on dispose; the sidebar-root spec pins the two controls' actions. diff --git a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.zh.md b/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.zh.md deleted file mode 100644 index c652f313d4..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-frame-shell-leading-window-chrome-seat.zh.md +++ /dev/null @@ -1,38 +0,0 @@ -# Agent Note: 框架持有的 shell.leading 窗口 chrome 座 - -Status: implemented - -[English](2026-09-17-frame-shell-leading-window-chrome-seat.md) | 中文 - -## Problem - -[macOS 隐藏标题栏工作](../feature/2026-09-13-macos-hidden-titlebar-vibrancy.zh.md)在 darwin 上把收起的侧边栏整列隐藏,并把重新打开与 New Session 控件移入会话头部的 slot(`conversation.session.header.leading`),在 `data-sidebar-collapsed` 发布期间由 CSS 显示。但只有会话界面有这个座:选中任何其他主面板(插件管理器或未来的全局面板)时,收起的窗口只剩悬浮的红绿灯压在面板内容上,屏幕上没有任何重新打开控件。每个新面板都得自建 leading 座并重复同一套避让几何。 - -## Decision - -窗口 chrome 属于框架,不属于某个面板。ui-layout 声明第五个 root 作用域子 slot `shell.leading`(single),AppFrame 把它的座渲染为覆盖各列的框架级盒子——仅在 darwin 桌面且 `sidebarCollapsed` 时挂载,即让窗口 chrome 无处安放的全隐藏状态(Windows 的零宽收起保留其固定 caption 控件,该座在那里挂载会出现第二组控件)。座位于框架左上角(left 88px 避开 hiddenInset 红绿灯,top 11px 让 28px 控件对齐会话标题行中线,z-index 15 高于列内容、低于框架浮层),并带 `-webkit-app-region: no-drag`,把自己从下方任何拖拽带中减去。 - -在同一收起条件下框架发布 `--dsh-frame-leading-clearance: 160px`——红绿灯加座内两个控件占据的行内带宽,自框架左边缘起量。窗口全屏隐藏红绿灯(桌面 preload 把状态镜像到 `html[data-fullscreen]`):座移入其空出的带宽(`left: 12px`),避让量降到 `84px`。会话标题行是唯一消费者:它 pad `max(0px, clearance - 20px)`(其头部已 pad 20px)。两种侧栏状态下都始于窗口顶带之下的入口页(插件管理器页头)改用 `--dsh-frame-top-clearance`(48px,darwin 框架上无条件发布)做顶部内边距,纵向避开顶带而非行内缩进。 - -ui-sidebar 把 `HeaderLeadingControls` 改注册到 `shell.leading` 而非会话座,复用 shell 的 inject face 与 locale;因挂载条件归框架所有,组件无条件渲染,其平台判断与 CSS 收起门控被删除,同时解除 ui-sidebar 对 ui-conversation 的依赖。原 `conversation.session.header.leading` slot 按 pre-stable API 规则不复存在(更新所有消费者,不留兼容垫层);会话头现在自带无会话可用的 `conversation.header.leading` 座(root 作用域、无 Session 也渲染)作为公开扩展点,因窗口 chrome 控件已住进 `shell.leading`,出厂组合中它保持空置。 - -## Alternatives considered - -**保留会话座并为每个面板各加一个。** 每个主面板都要重复座标记、拖拽区减除与红绿灯几何,漏掉的面板会让收起的窗口失去重新打开控件。框架持有的单一座使该保证成为结构性的。 - -**始终挂载占用方、由 CSS 控制可见性(先前机制)。** 它在所有平台与所有收起状态下都保留死 DOM 和头部的 no-drag 减除,且完全帮不到非会话面板。框架级 TSX 挂载把条件、座几何与避让量发布集中在一个文件。 - -**同时以右栏全屏为座的门控条件。** 不必要:全屏右面板(z-index 40)在视觉上盖住座(15),且它已让整个盒子退出窗口拖拽,座的几何无法穿透它。 - -**把避让量作为各消费者硬编码的固定内边距。** 各面板写死 `160px` 会与座的真实带宽悄然失同步;在恰为挂载条件下发布一个自定义属性,几何变化时消费者仍保持正确。 - -## Consequences - -- 侧边栏隐藏时,现有与未来的每个主面板都免费获得重新打开与 New Session 控件;会话接入 `--dsh-frame-leading-clearance`,入口页改用无条件发布的 `--dsh-frame-top-clearance`。 -- `conversation.session.header.leading` 从客户端目录中消失;`shell.leading` 作为公开 root 作用域座承载窗口 chrome,会话头无会话可用的 `conversation.header.leading` 则是另一个公开座,出厂组合中无占用者。 -- ui-sidebar 不再依赖 ui-conversation(inject 列表、devDependency 与 tsconfig 引用均已移除)。 -- 挂载条件显式点名 darwin:Windows 的 `data-windows-titlebar` 收起同样得到零宽列,但保留自己的固定 caption 控件,框架座会与之重复。未来某平台若隐藏整列且不留自己的 chrome,通过放宽该条件接入。 - -## Testing - -ui-layout 的 app-frame spec 钉住 darwin 收起下座的挂载/卸载及 56px 控制栏旁座的缺席;apply spec 钉住五 slot 声明与拆除。ui-sidebar 的 apply spec 钉住 `shell.leading` 注册(组件、locale、共享 inject face)及 dispose 时的移除;sidebar-root spec 钉住两个控件的动作。 diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml index cfe428cf55..ecb3c391ff 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.i18n.yaml @@ -9,11 +9,11 @@ en: cfb527c1c2ca661a zh: b61a906330cdaf97 /agent-note-mandatory-native-v4-relationship-validation/decision: - en: 14feaf74a58d5609 - zh: 8aea80c89110e638 + en: 534efaae5eb9beb0 + zh: ccb3734458248383 /agent-note-mandatory-native-v4-relationship-validation/alternatives-considered: - en: d6296e00a6b30d48 - zh: 9f7074c69fb42e5f + en: 96a37d9ba0252630 + zh: fdf2d085805f0010 /agent-note-mandatory-native-v4-relationship-validation/consequences: en: b76aaca1c35dcc6c zh: b933c2518c64c60c diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md index b111e9f1dd..385377db23 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.md @@ -16,7 +16,7 @@ The V4 format restorer and current JSONL scanner share one mandatory cross-event An unfinished tail may retain an open turn, step, tool call, or compaction. A closing event must settle the relationships it closes. An inherited unfinished compaction ends at its `session/end-seed` marker; it does not constrain the child's lifecycle. Repair message identities retain their historical numeric suffix after earlier migrations change sequence coordinates. Fork repairs retain their separately validated native identities. -Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Optional runtime invariants provide diagnostics; their installation is not required for durable admission. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity. +Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity. A retired tool-result wrapper outside its owning `tool/result` row would require changing the event, role, or source to represent it in V4. The adjacent migration lifts only the canonical owned wrapper; target restoration refuses wrappers remaining in interpreted content. Native row admission applies the same refusal before recoverable tail suppression. The check follows declared message, content, and Assistant-stream slots instead of recursively inspecting JSON, so tool arguments, replay state, schema parameters, nested extensions, and unknown ignorable payloads retain their recorded values. @@ -26,8 +26,6 @@ A retired tool-result wrapper outside its owning `tool/result` row would require **Project V4 back into a frozen V3 validation view.** This couples current acceptance to retired representations and invites new fields to disappear in the projection. Frozen generations remain independently readable, while the current restorer interprets its own fields. -**Rely on optional runtime invariants.** Detached readers and restoration paths may not install diagnostic companions. A required relationship must be checked before publication or consumption of the restored Session. - ## Consequences The native restorer maintains one bounded state machine per read and rejects contradictions before successor publication. Frozen V0–V3 validators and committed Session generations remain unchanged. Public catalog regressions cover invalid relationships alongside partial tails, inherited cuts, immutable unknown attribution, and complete tool exchanges; these checks enforce the declared V4 representation without changing its schemas. Breaking changes require a successor; compatible additions use new acknowledgements, and implementation fixes preserve the declared meaning. diff --git a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md index 20f1dc53b6..169a80ecf7 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-17-native-v4-read-validation.zh.md @@ -16,7 +16,7 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校 未完成的尾部可以保留打开的 turn、step、工具调用或压缩事务。关闭事件必须结清它关闭的关系。继承的未完成压缩事务在其 `session/end-seed` 标记处结束,不约束子 Session 的生命周期。较早迁移改变序号坐标后,修复消息身份仍保留历史数字后缀。fork 修复保留经过独立校验的原生身份。 -已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。可选运行时不变式提供诊断;持久化准入不要求安装它们。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。 +已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。 退役的 tool-result 包装若不属于对应的 `tool/result` 行,就需要改变事件、角色或来源才能在 V4 中表示。相邻迁移只提升规范的自有包装;目标恢复器拒绝解释内容中剩余的包装。原生行准入在可恢复尾部被忽略之前执行同样的拒绝。检查沿声明的消息、内容和 Assistant stream 字段进行,不递归扫描 JSON,因此工具参数、replay state、schema 参数、嵌套扩展和未知可忽略载荷保留其记录值。 @@ -26,8 +26,6 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校 **将 V4 投影回冻结的 V3 校验视图。** 这会将当前准入与退役表示绑定,并容易让新增字段在投影中消失。冻结代际仍可独立读取,当前恢复器解释自己的字段。 -**依赖可选运行时不变式。** 分离读取器和恢复路径可能不安装诊断组件。必需关系必须在发布或消费恢复后的 Session 之前完成校验。 - ## Consequences 原生恢复器为每次读取维护独立状态机,在发布后继文件之前拒绝矛盾。冻结的 V0–V3 校验器及已提交的 Session 代际保持不变。公开目录回归覆盖无效关系、部分尾部、继承切分点、未知归属的不可变保存,以及完整工具交互;这些检查落实已声明的 V4 表示,不改变其 schema。破坏性变更要求后继版本;兼容新增使用新的确认记录,实现修复须保留声明的含义。 diff --git a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml deleted file mode 100644 index 5b7f7e9b6b..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-plugin-detail-page-extension-slots.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-plugin-detail-page-extension-slots.md -/agent-note-plugin-detail-pages-as-an-extension-point: - en: 71b432dfc4475c08 - zh: a9f9a7e272c32093 -/agent-note-plugin-detail-pages-as-an-extension-point/problem: - en: 39f7e246e6ab3beb - zh: dba3ba5206d81d8d -/agent-note-plugin-detail-pages-as-an-extension-point/decision: - en: df2cba037b6bcb4d - zh: 82d0a5dc61a1716c -/agent-note-plugin-detail-pages-as-an-extension-point/alternatives-considered: - en: cdcc8710be8ab1be - zh: d82781a3c353984f -/agent-note-plugin-detail-pages-as-an-extension-point/consequences: - en: 0567cb3fbfbdb4f8 - zh: 256811c9a7271da1 -/agent-note-plugin-detail-pages-as-an-extension-point/testing: - en: 8957db157d2f743e - zh: fb24db99bb5a55aa diff --git a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.i18n.yaml index 25c6bed4ad..ae4cd23658 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.i18n.yaml @@ -9,8 +9,8 @@ en: 837ebb0071a5611b zh: 23d0f0713889ead6 /agent-note-writer-acquisition-for-blank-session-reuse/decision: - en: 932bc5c0be3721de - zh: 000425c5f053e74e + en: 7f2676feb224e7ea + zh: babfda5b31dfd0fe /agent-note-writer-acquisition-for-blank-session-reuse/alternatives-considered: en: 2a67a53f7f396ba8 zh: 6df6ccc071fb0de2 diff --git a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.md b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.md index 58d222185a..c832604dfb 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.md +++ b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.md @@ -14,7 +14,7 @@ The Session catalog includes persisted blanks. Startup restoration selects the s The Agent retains the acquired handle throughout its lifetime. Checking ownership and releasing a probe lock would leave a race before resume. No PID or process identity enters persisted data or the Remote schema. -This decision extends the blank-reuse policy in the [Client Session scope decision](2026-07-25-web-client-session-scope-and-provide-channel.md); its real-Session slash support and Provider adoption rationale remain active. +This decision extends the blank-reuse policy in the [Client Session scope reference](2026-09-15-client-session-references.md); its real-Session slash support and Provider adoption rationale remain active. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.zh.md b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.zh.md index 0f3fdf0509..f5d5d58bc6 100644 --- a/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-17-process-local-blank-sessions.zh.md @@ -14,7 +14,7 @@ Session 列表包含持久化空白会话。启动恢复选择已保存的空白 Agent 在整个生命周期内持有取得的句柄。检查占用后释放探测锁,会在恢复前留下竞态。持久化数据与 Remote schema 均不引入 PID 或进程身份。 -本决策扩展 [Client Session scope 决策](2026-07-25-web-client-session-scope-and-provide-channel.zh.md)中的空白会话复用策略;其真实 Session 支持 slash 以及 Provider 收养的理由仍然有效。 +本决策扩展 [Client Session scope 参考](2026-09-15-client-session-references.zh.md)中的空白会话复用策略;其真实 Session 支持 slash 以及 Provider 收养的理由仍然有效。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml deleted file mode 100644 index 40bc5939fe..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-session-row-menu-actions-slot.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-session-row-menu-actions-slot.md -/agent-note-session-row-actions-as-slot-lists: - en: 6c14eadc272cbe5f - zh: d0fb9907cddcae5a -/agent-note-session-row-actions-as-slot-lists/problem: - en: 1afb986570a4bb49 - zh: 22862e82275038b7 -/agent-note-session-row-actions-as-slot-lists/decision: - en: 7a353dd9c0642f56 - zh: e503a953cccfdc47 -/agent-note-session-row-actions-as-slot-lists/alternatives-considered: - en: 19fc0c951e9e49b5 - zh: 9010c3fb55b71ce0 -/agent-note-session-row-actions-as-slot-lists/consequences: - en: cfc90eb041452a48 - zh: 882748ceb29f21ea diff --git a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml deleted file mode 100644 index 34faaaba25..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-settings-pages-as-companion-packages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-settings-pages-as-companion-packages.md -/agent-note-settings-pages-as-companion-packages: - en: ada5eadeb6e921e4 - zh: 1cc804b1720c2c98 -/agent-note-settings-pages-as-companion-packages/problem: - en: 8c23fffd3cef6ea8 - zh: 6072e1e4197afd7c -/agent-note-settings-pages-as-companion-packages/decision: - en: d0610e0445b8fdb7 - zh: 5e8af1fce960cae5 -/agent-note-settings-pages-as-companion-packages/alternatives-considered: - en: cc29e94d925d84b4 - zh: bdaec6d6ff2d511a -/agent-note-settings-pages-as-companion-packages/consequences: - en: ef4733505d43fb1e - zh: 320475e4b5a0f5e0 -/agent-note-settings-pages-as-companion-packages/testing: - en: 92bef8e1c609359b - zh: c4f2016243441b1e diff --git a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.i18n.yaml deleted file mode 100644 index 4aa201536b..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-shared-office-runtime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-shared-office-runtime.md -/agent-note-shared-office-runtime-for-desktop-and-sdk: - en: 28af688a50a2d5c6 - zh: ee73d6dd4fab9184 -/agent-note-shared-office-runtime-for-desktop-and-sdk/problem: - en: 4d08aed08c8aff27 - zh: d618b4ac68832867 -/agent-note-shared-office-runtime-for-desktop-and-sdk/decision: - en: f92ce88d82a076ed - zh: 341e486effad4b6d -/agent-note-shared-office-runtime-for-desktop-and-sdk/alternatives-considered: - en: d85334a384a32a7d - zh: 61c370390d9fdcf9 -/agent-note-shared-office-runtime-for-desktop-and-sdk/consequences: - en: f236fec61d99ba16 - zh: 859ee94e05a95aa4 diff --git a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml deleted file mode 100644 index 735a46bdc2..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-web-feature-routes-and-route-gate.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.md -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there: - en: ca746e8d558cfc31 - zh: 3524931b0bf9991f -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there/problem: - en: d4a1124ae3b6f6a8 - zh: 4814da5d08947bc7 -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there/decision: - en: 5b89e02010c2da46 - zh: d11e67d86ef27908 -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there/alternatives-considered: - en: d90dbd0c95914085 - zh: 6256f4fd76a7cb55 -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there/consequences: - en: 94530852d7746ddb - zh: ad2383c465053822 -/agent-note-feature-routes-follow-the-document-base-and-a-gate-keeps-them-there/testing: - en: 95c952092857cb53 - zh: 232cd731afcd3288 diff --git a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml deleted file mode 100644 index 83ed8c5841..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-agent-teams-single-bundle.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-18-agent-teams-single-bundle.md -/agent-note-enable-agent-teams-with-one-bundle: - en: c595b526791a8dfd - zh: 61f4bf6948e41bee -/agent-note-enable-agent-teams-with-one-bundle/problem: - en: 8dd802c775d24e42 - zh: 53cf2818dd93181e -/agent-note-enable-agent-teams-with-one-bundle/decision: - en: d513c3239b59b2ee - zh: 7692b7ae43befc9d -/agent-note-enable-agent-teams-with-one-bundle/alternatives-considered: - en: 32952c4e1b6f486d - zh: 6c9b91eaf0460d30 -/agent-note-enable-agent-teams-with-one-bundle/consequences: - en: be92cb1ce7c86cb6 - zh: 0da06f76df72f97e -/agent-note-enable-agent-teams-with-one-bundle/verification: - en: 8fc694c4c84401f1 - zh: 6540462f6896bef7 diff --git a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.i18n.yaml deleted file mode 100644 index d8ea21a542..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-chat-navigation-performance.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-18-chat-navigation-performance.md -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps: - en: c37ea37bfa045a00 - zh: 88a75b8739e99fb5 -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/problem: - en: c3ca28379b414fc5 - zh: 4a25378cef3f2259 -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/decision: - en: a15c7c13f167e5ac - zh: a15c7c13f167e5ac -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/decision/fixed-pitch-virtual-rail: - en: 79e61c0dbffe6f01 - zh: 47dd4758d115f8bb -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/decision/two-layer-transcript-navigation: - en: 022a8ad83ac099f1 - zh: b4393e39bfdd401d -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/decision/one-publication-per-historical-jump: - en: 510e80edc58ebdea - zh: cfbff46bff1e43bb -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/decision/theoretical-work-model: - en: 6786e39e812ad355 - zh: eca00072cf880a08 -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/alternatives-considered: - en: f590762936560d5c - zh: 0fc836bf1841733e -/agent-note-bound-turn-navigation-work-and-batch-historical-jumps/consequences: - en: 5f50c3d4a539f48c - zh: 5aee80a99d52233e diff --git a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.i18n.yaml deleted file mode 100644 index 828454ccca..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-localized-package-metadata.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.md -/agent-note-plugin-owned-localized-display-metadata: - en: b78ee8e580f068ea - zh: 17dd59c9b6273897 -/agent-note-plugin-owned-localized-display-metadata/problem: - en: 4022a58baf82e77d - zh: 8439268ed4df9b69 -/agent-note-plugin-owned-localized-display-metadata/decision: - en: f6c3e24681977f48 - zh: 84ae52ec62f4af96 -/agent-note-plugin-owned-localized-display-metadata/existing-decisions: - en: a84f44d6a3179d47 - zh: 8fb62f08bec5f75d -/agent-note-plugin-owned-localized-display-metadata/alternatives-considered: - en: abbdb1b8ab360380 - zh: edfe6c06b1d67fd7 -/agent-note-plugin-owned-localized-display-metadata/verification: - en: 8541a2431321ef61 - zh: 77599e0855d93da9 -/agent-note-plugin-owned-localized-display-metadata/consequences: - en: 2a4547c9657038df - zh: 1a4bebd06d2b9e38 diff --git a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml deleted file mode 100644 index 4325837109..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-profile-plugin-host-runtime-instances.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-18-profile-plugin-host-runtime-instances.md -/agent-note-profile-plugins-share-the-installation-s-host-runtime-instances: - en: 04d5248dde932469 - zh: 877d263ca4b0beef -/agent-note-profile-plugins-share-the-installation-s-host-runtime-instances/problem: - en: f9c4830ba4be20bc - zh: be46323e2eae9247 -/agent-note-profile-plugins-share-the-installation-s-host-runtime-instances/decision: - en: bcd92c487ebfab32 - zh: c50478b864562f0d -/agent-note-profile-plugins-share-the-installation-s-host-runtime-instances/alternatives-considered: - en: 1443d5fc869ffcf9 - zh: 76b21473d2892e7f -/agent-note-profile-plugins-share-the-installation-s-host-runtime-instances/consequences: - en: 075067e84219ae67 - zh: 035c60f3e11783d5 diff --git a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml deleted file mode 100644 index 93be98ebc5..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-lossless-projection-checkpoint-json.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-19-lossless-projection-checkpoint-json.md -/agent-note-lossless-projection-checkpoint-json: - en: 2b7b5f9e5da030df - zh: 76d65ddc85776939 -/agent-note-lossless-projection-checkpoint-json/problem: - en: 26e613246f31ee5a - zh: e0aa1e2f63bce802 -/agent-note-lossless-projection-checkpoint-json/decision: - en: 01161e9c42142971 - zh: e05e52d2af7fc4c5 -/agent-note-lossless-projection-checkpoint-json/alternatives-considered: - en: 1d64345d3d72ed5f - zh: 4c9d3410cfd815b3 -/agent-note-lossless-projection-checkpoint-json/consequences: - en: 94b619dc13cfff0b - zh: c432227d3333175f diff --git a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.i18n.yaml index 8892007905..88254d458d 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.i18n.yaml @@ -51,8 +51,8 @@ en: d0bed60317e6e3a8 zh: 5714160678adc049 /agent-note-resolve-profile-modules-along-the-ancestor-chain-with-an-interception-layer/decision/part-3-developer-integration-guide/2-development-mode-the-plugin-repository-is-outside-the-profile-tree: - en: d7ec5a747ebcb93e - zh: 7e1d0724973b32c1 + en: 22259a568693c641 + zh: d81d15671398d033 /agent-note-resolve-profile-modules-along-the-ancestor-chain-with-an-interception-layer/alternatives-considered: en: 5466618bb5e3d4d9 zh: 7d0b6d727e33d0d1 diff --git a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.md b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.md index 6dfb5511af..bb99cb2435 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.md +++ b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.md @@ -135,7 +135,7 @@ The plugin's own third-party dependencies are hoisted to `$DSH_HOME/profiles/` there, or link dsh packages to a local source repository and start dsh from that repository. Both make the running dsh and the repository's copies identical, but linked plugins do not require either layout. diff --git a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.zh.md b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.zh.md index 2474a9b040..4af29f7255 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.zh.md @@ -135,7 +135,7 @@ ESM 与 CommonJS 两个适配器调用同一个路由函数,主线程与 Harne `npm link`,或 `dsh plugin add ../my-plugin` 这类裸目录路径(pnpm 按 `link:` 处理),会让 profile 里的条目成为指向插件仓库的软链接,仓库目录成为 linked root。插件以真实路径加载。查找到其 manifest 的 peer 位置时,匹配的运行时条目由当前运行的安装提供,无论 dsh 来自 npm 全局安装、Desktop 内置还是源码仓启动。该位置的 devDependency 副本服务编译器,普通请求不会加载它。link 指向无 manifest 的 `src` 目录时,也可以通过同一条祖先查询使用父目录的 peer。 -manifest 的写法与 harness 自身的包相同:需要与宿主共享实例的 dsh 包同时声明在 `peerDependencies` 与 `devDependencies`,peer 让 `R/node_modules` 上的位置被占据,dev 副本给编译器和独立测试使用;第三方依赖以及 `@deepseek-ai/dsh-brand`、`@deepseek-ai/dsh-util-values` 这类无状态 dsh 工具包放在 `dependencies`。修改 `peerDependencies` 后,重新加载插件时的新解析会使用新声明;已加载模块的生命周期仍由 Node 和 Cordis 管理。 +manifest 的写法与 harness 自身的包相同:需要与宿主共享实例的 dsh 包同时声明在 `peerDependencies` 与 `devDependencies`,peer 让 `R/node_modules` 上的位置被占据,dev 副本给编译器和独立测试使用;第三方依赖以及 `@deepseek-ai/dsh-brand`、`@deepseek-ai/dsh-util-values` 这类无状态 dsh 工具包放在 `dependencies`。修改 `peerDependencies` 后,重新加载插件时的新解析会使用新声明;已加载模块的生命周期仍由 Node 和 Cordis 管理。版本相同也不会让不同目录中的副本共享模块局部状态。仅把 scope 标记改成 `Symbol.for` 同样无法共享 scope 父关系、carrier key 或注册表;承载身份的运行时必须解析到 Host 实例。 另两种可用布局:插件仓库自装 `@deepseek-ai/dsh` 并从仓库里启动 `pnpm exec dsh --profile `;或把 dsh 包 link 到本机源码仓并从源码仓启动。两者让运行中的 dsh 与仓库里的副本本来就是同一份,但 linked 插件不要求使用这两种布局。 diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml index 938a007c31..604c1fe195 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.i18n.yaml @@ -9,8 +9,8 @@ en: 2e7f99c81fbc941f zh: b0fac7cd5eb86ffe /agent-note-projection-cache-read-only-face-matches-the-lifecycle-identity-the-client-store-separates-cached-and-sequenced-rows/problem/mechanism: - en: 36d24e92eedf8cea - zh: 5dafc5758ee0f380 + en: 2594af08bbc79dba + zh: cdebd5d14d99cf3c /agent-note-projection-cache-read-only-face-matches-the-lifecycle-identity-the-client-store-separates-cached-and-sequenced-rows/problem/the-cache-s-two-uses-and-where-validation-lives: en: 055ead18162ca1cd zh: 9046bf04229e45d1 diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md index bfdd8bda22..5ef27cb076 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md @@ -12,7 +12,7 @@ This is neither cache corruption nor a version mismatch. The projcache record's ### Mechanism -A cache record (record format and predecessor recovery: [Projection-cache predecessor recovery and Session-format binding](2026-09-02-projcache-cross-version-read-compat.md)) is bound to a lifecycle identity: `formatVersion + createdAt + cwd + isSeeded + inheritedEventCount`, matched by `identityMatches` on full equality. Since #3346, `inheritedEventCount` (the length of the event prefix a fork inherits, the cut below) no longer appears in the logical header: the header keeps only the `isSeeded` bit and the exact cut follows the body. Since Session format v2 (#3398) the physical header line no longer stores `seedLength` either; the reader derives the cut from the seq of the `session/end-seed {inherited: true}` marker in the body. +A cache record (record format and predecessor recovery: [Projection-cache predecessor recovery and Session-format binding](../../../../packages/session/session-projection-cache/README.md)) is bound to a lifecycle identity: `formatVersion + createdAt + cwd + isSeeded + inheritedEventCount`, matched by `identityMatches` on full equality. Since #3346, `inheritedEventCount` (the length of the event prefix a fork inherits, the cut below) no longer appears in the logical header: the header keeps only the `isSeeded` bit and the exact cut follows the body. Since Session format v2 (#3398) the physical header line no longer stores `seedLength` either; the reader derives the cut from the seq of the `session/end-seed {inherited: true}` marker in the body. A header-only read therefore cannot obtain the cut: the JSONL backend's `fromHeaderLine` hard-codes `inheritedEventCount: 0` for header-only reads, and `SessionPersistenceSnapshot` carries only the header, the revision, and an optional eventCount. The Session-list and reference consumers grew the same guard: diff --git a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md index 470c96385c..e69db37e7e 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md @@ -12,7 +12,7 @@ Host 进程重启后,所有 fork 出来的会话(`SessionHeader.isSeeded === ### 机制 -缓存记录(记录格式与前代恢复见 [投影缓存前代恢复与 Session 格式绑定](2026-09-02-projcache-cross-version-read-compat.zh.md))绑定一份 lifecycle identity:`formatVersion + createdAt + cwd + isSeeded + inheritedEventCount`,`identityMatches` 做全等匹配。其中 `inheritedEventCount`(fork 继承的事件前缀长度,下称 cut)从 #3346 起不再出现在逻辑 header 里:header 只保留 `isSeeded` 这一位,精确 cut 跟随正文。Session 格式 v2 起(#3398),物理 header 行也不再存 `seedLength`,reader 从正文里 `session/end-seed {inherited: true}` 标记的 seq 推出 cut。 +缓存记录(记录格式与前代恢复见 [投影缓存前代恢复与 Session 格式绑定](../../../../packages/session/session-projection-cache/README.zh.md))绑定一份 lifecycle identity:`formatVersion + createdAt + cwd + isSeeded + inheritedEventCount`,`identityMatches` 做全等匹配。其中 `inheritedEventCount`(fork 继承的事件前缀长度,下称 cut)从 #3346 起不再出现在逻辑 header 里:header 只保留 `isSeeded` 这一位,精确 cut 跟随正文。Session 格式 v2 起(#3398),物理 header 行也不再存 `seedLength`,reader 从正文里 `session/end-seed {inherited: true}` 标记的 seq 推出 cut。 于是 header-only 的读取拿不到 cut:JSONL 后端 `fromHeaderLine` 对 header-only 读取硬编码 `inheritedEventCount: 0`,`SessionPersistenceSnapshot` 只有 header、revision 和可选的 eventCount。Session 列表与引用消费者都因此加了同一个守卫: diff --git a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.i18n.yaml index eba3d7545a..3bd20ef0bc 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.i18n.yaml @@ -138,5 +138,5 @@ en: 3b99a327464edc3e zh: dcffdc75369925dc /agent-note-remote-duplex-stream-one-kind-of-stream-an-uplink-channel-and-an-invocation-context/related: - en: f8995f478dc1d478 - zh: 96621e80a4d3cefc + en: 85d77fb246564024 + zh: 8b400818a91a3e3a diff --git a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.md b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.md index ea8e8cebcc..c1333ef62f 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.md +++ b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.md @@ -604,6 +604,6 @@ Retention cases in `gateway-stream.host.spec.ts` and `gateway.client.spec.ts` co ## Related -- [Remote event delivery](2026-08-10-remote-event-delivery.md): the unary `$events/result` answer path; the duplex `$events` item under Deferred partially supersedes it once it lands. +- [Remote event delivery](../../../../packages/api/remotes/README.md): the unary `$events/result` answer path; the duplex `$events` item under Deferred partially supersedes it once it lands. - [Session history and event transport](2026-08-18-session-history-and-event-transport.md): the `$stream()` supervisor and the journal / snapshot protocols above it, which this note leaves unchanged. - [Web sidebar terminal](../feature/2026-09-09-web-sidebar-terminal.md): the unary terminal `write` and `attachmentId`; the `attach` stream under Deferred replaces it once it lands. diff --git a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.zh.md b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.zh.md index 2e119bf054..791b5e3c81 100644 --- a/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-19-remote-duplex-stream.zh.md @@ -604,6 +604,6 @@ for await (const reply of stream) replies.push(reply) // ['> a', '> b'] ## 相关 -- [Remote 事件投递](2026-08-10-remote-event-delivery.zh.md):`$events/result` 的 unary 回答路径;「后续」里的双工 `$events` 落地后部分取代它。 +- [Remote 事件投递](../../../../packages/api/remotes/README.zh.md):`$events/result` 的 unary 回答路径;「后续」里的双工 `$events` 落地后部分取代它。 - [会话历史与事件传输](2026-08-18-session-history-and-event-transport.zh.md):`$stream()` 监督器与其上的 journal / snapshot 协议,本 Note 不改它们。 - [Web 侧边栏终端](../feature/2026-09-09-web-sidebar-terminal.zh.md):终端按键的 unary `write` 与 `attachmentId`;「后续」里的 `attach` 流落地后取代它。 diff --git a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.i18n.yaml index 641b4a5806..26bf24ff02 100644 --- a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.i18n.yaml @@ -21,8 +21,8 @@ en: a34c228751a1a329 zh: 8e8b962b55e7c68e /agent-note-stable-sidebar-tab-mounting-and-session-retention/decision/retention-versus-recovery: - en: aabfa99401aa0f73 - zh: 465ebf723c4393aa + en: 61e4a975fea17a9a + zh: 911f4d1caab1efd0 /agent-note-stable-sidebar-tab-mounting-and-session-retention/alternatives-considered: en: 84cfe7613657bfdb zh: 4a0a499eb5d36d22 diff --git a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.md b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.md index 3c1e23ad93..574667c79f 100644 --- a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.md +++ b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.md @@ -51,7 +51,7 @@ The [Desktop Browser decision](../feature/2026-09-20-desktop-browser-webview.md) | Application restart or window reload | Show the saved title and URL; load only after an explicit restore or address submission. | | Guest crash | Retry through the provider; page memory and native history are not durable. | -[Layout and provider recovery](2026-09-14-sidebar-layout-provider-recovery.md) remains independent: persisted tab identity, placement and address support cold starts, not DOM serialization. A hidden page may continue scripts, media and networking. +[Layout and provider recovery](../../../../packages/client/ui-sidebar-right/README.md) remains independent: persisted tab identity, placement and address support cold starts, not DOM serialization. A hidden page may continue scripts, media and networking. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.zh.md b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.zh.md index 000174a0c3..a2a88a209b 100644 --- a/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-20-sidebar-retained-tab-layout.zh.md @@ -51,7 +51,7 @@ Tab 提供 Body 保活持有,而不独立申请会话引用。View 的稳定 ` | 应用重启或窗口刷新 | 展示保存的标题和 URL,显式恢复或提交地址后才加载。 | | guest 崩溃 | 通过提供方重试;页面内存和原生 history 不持久化。 | -[布局与提供方恢复](2026-09-14-sidebar-layout-provider-recovery.zh.md)仍然独立:持久化的 Tab 身份、位置和地址支持冷启动,而不是序列化 DOM。隐藏页面可能继续执行脚本、播放媒体或联网。 +[布局与提供方恢复](../../../../packages/client/ui-sidebar-right/README.zh.md)仍然独立:持久化的 Tab 身份、位置和地址支持冷启动,而不是序列化 DOM。隐藏页面可能继续执行脚本、播放媒体或联网。 ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.i18n.yaml index f715253877..bbac97a7ad 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.i18n.yaml @@ -15,8 +15,8 @@ en: d7cf1bd544f163ee zh: 2433f3faa5f5abcb /agent-note-definition-owned-grouping-with-generic-node-group-assembly/decision/related-decisions: - en: d8307dcaf2a9ac72 - zh: 4852b1e2a4d09af0 + en: 0889aeb088ed31b2 + zh: 539f03253b48c59b /agent-note-definition-owned-grouping-with-generic-node-group-assembly/definition-input-state-and-registration: en: 9fb89bddbfd75627 zh: 950c319db123c637 diff --git a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.md b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.md index bccb48b0e4..39a31ae902 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.md +++ b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.md @@ -35,7 +35,7 @@ The grouping foundation provides a separate, Node-input `ConversationGroupDefini | Record | Relationship | |---|---| | [Business Node assembly](2026-08-09-client-conversation-node-assembly.md) | Retains event matching, Contexts, Locations, one business Node per Context, and target Builders; grouping adds another input category. | -| [Chat scroll and footer](../bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md) | Owns clipping and independent nested following without changing Group Definition membership. | +| [Chat scroll and footer](../../../../packages/client/ui-chat/README.md) | Owns clipping and independent nested following without changing Group Definition membership. | Grouping preserves the independent Node-assembly decisions. Cross-View navigation and `toolCallFocus` remain separate responsibilities; Group references carry no View handles or resource-navigation policy. diff --git a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.zh.md b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.zh.md index 5d2ec9e684..b469ad12ca 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-21-conversation-build-groups.zh.md @@ -35,7 +35,7 @@ Chat Builder 汇总整个目标的节点及索引。在其中固定创建 Chat | 记录 | 关系 | |---|---| | [业务节点组装](2026-08-09-client-conversation-node-assembly.zh.md) | 保留事件匹配、Context、Location、每个 Context 一个业务 Node 及目标 Builder,分组增加另一种输入类别。 | -| [Chat 滚动与页脚](../bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.zh.md) | 负责裁剪与独立的嵌套跟随,不改变 Group Definition 成员关系。 | +| [Chat 滚动与页脚](../../../../packages/client/ui-chat/README.zh.md) | 负责裁剪与独立的嵌套跟随,不改变 Group Definition 成员关系。 | 分组保留独立的节点组装决策。跨 View 导航与 `toolCallFocus` 仍是独立职责,Group 引用不携带 View 句柄或资源导航策略。 diff --git a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml deleted file mode 100644 index 4a59755b21..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-21-creator-skills-progressive-disclosure.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-21-creator-skills-progressive-disclosure.md -/agent-note-creator-skills-disclose-progressively-and-may-read-sources: - en: 374f4a41f3026e59 - zh: 4e7f90d18b03fd14 -/agent-note-creator-skills-disclose-progressively-and-may-read-sources/problem: - en: 1808717550a5e7ba - zh: e18dd5a813a2398d -/agent-note-creator-skills-disclose-progressively-and-may-read-sources/decision: - en: b83fb74f03ca75a7 - zh: e4f6b1c73ef96787 -/agent-note-creator-skills-disclose-progressively-and-may-read-sources/alternatives-considered: - en: d36d4f610925060a - zh: c0ed56b6c631789e -/agent-note-creator-skills-disclose-progressively-and-may-read-sources/consequences: - en: ed908afe74adf3bc - zh: 62a3cfba359dfbb6 diff --git a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.i18n.yaml index 55a0c25bdd..d2564b05ce 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.i18n.yaml @@ -9,8 +9,8 @@ en: feb9d491ad4a9016 zh: 291f0e6c913b0b19 /agent-note-desktop-close-shortcuts-follow-page-focus/decision: - en: c587d88575b6ce7e - zh: 2287cf4d6d2e7587 + en: f076fb4b2098899d + zh: 6f136e9d11714e02 /agent-note-desktop-close-shortcuts-follow-page-focus/alternatives-considered: en: 10c7bb2fc87bdd16 zh: 796ecde8bcf09d34 diff --git a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.md b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.md index dc97680644..ef53a03de7 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.md +++ b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.md @@ -14,7 +14,7 @@ The sidebar page owner resolves close against live focus and captures the target The macOS File menu routes Close Page or Window through the same owner and displays the accepted single-key binding. Native closure requires the current configuration revision, a focused and enabled product window, and inactive shortcut recording. The existing window lifecycle applies without a shortcut-specific confirmation: macOS keeps the application alive after its last window closes; Windows and Linux quit and stop the Host. -This decision owns File-menu and close behavior in place of the close-role choice in [the standard macOS menu note](../bug-fix/2026-09-16-desktop-window-menus.md). That note retains the rationale for native Window and application hide commands. [Shortcut preference persistence](2026-09-20-device-local-shortcut-preferences.md) owns accepted bindings and revision publication. +This decision owns File-menu and close behavior; the [Desktop reference](../../../../apps/desktop/README.md) documents native Window and application hide commands. [Shortcut preference persistence](2026-09-20-device-local-shortcut-preferences.md) owns accepted bindings and revision publication. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.zh.md b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.zh.md index 6ef60c084f..298c493bb7 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-21-desktop-page-close-shortcuts.zh.md @@ -14,7 +14,7 @@ Status: implemented macOS File 菜单通过同一个 owner 路由“关闭页面或窗口”,并显示已接受的单键绑定。原生关闭要求配置 revision 仍为当前值、产品窗口已聚焦且启用,并且没有在录制快捷键。现有窗口生命周期直接生效,不增加快捷键专用确认:macOS 最后一个窗口关闭后保留应用;Windows 和 Linux 退出并停止 Host。 -本决策接管[标准 macOS 菜单记录](../bug-fix/2026-09-16-desktop-window-menus.zh.md)中 close role 选择所涉及的 File 菜单和关闭行为。该记录保留原生 Window 与应用隐藏命令的理由。[快捷键偏好持久化](2026-09-20-device-local-shortcut-preferences.zh.md)负责已接受绑定及 revision 发布。 +本决策负责 File 菜单与关闭行为;[Desktop 参考](../../../../apps/desktop/README.zh.md)说明原生 Window 与应用隐藏命令。[快捷键偏好持久化](2026-09-20-device-local-shortcut-preferences.zh.md)负责已接受绑定及 revision 发布。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml index e6f2fb8718..e7aef9adc0 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml @@ -9,11 +9,11 @@ en: 2ba6c43b20a3d347 zh: 5647179ddaa38ad9 /agent-note-ship-experimental-capabilities-as-optional-bundles/decision: - en: 1c5a43b3ce2d3056 - zh: b59f549a12cf67d1 + en: e6ffc783859d7509 + zh: 8678f323985a24f4 /agent-note-ship-experimental-capabilities-as-optional-bundles/alternatives-considered: - en: a65d84004e48b3ef - zh: 17221f08e7a8a89b + en: 9ff0511566948d8b + zh: 4e1e37b79f63d390 /agent-note-ship-experimental-capabilities-as-optional-bundles/consequences: - en: a5c9699904fa72f1 - zh: 80763881150593e2 + en: 9d2b313f15fc9ce0 + zh: 522341c83565ac33 diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md index 9c3de87f91..d529b27542 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md @@ -10,9 +10,13 @@ The Web plugin page offered two optional bundles, Agent Teams and voice input. A ## Decision -`OPTIONAL_BUNDLES` lists Agent Teams, voice input, and Auto review. Every optional bundle declares `icon` and exports `./locale/*.json` with `meta.title` and `meta.description`, so the Official group renders a localized title, description, and image; `verify-default-product-isolation` rejects an optional bundle without them. The Inspector's `cordis.patch.yml` names the package instead of a built-artifact path, so the same file is the bundle patch and the `--patch` overlay for a built Web launch. +`OPTIONAL_BUNDLES` owns the installation's default-off selections, including maintained optional capabilities and experimental capabilities. Every bundle declares `icon` and exports `./locale/*.json` with `meta.title` and `meta.description`; the Official group displays that metadata. A bundle's presentation does not determine its [experimental status](../../../../packages/experimental/README.md#status). The isolation check validates admission and still rejects experimental dependencies of nonexperimental bundles. -An optional bundle is a runtime dependency of the installation, so its dependency graph is downloaded by every `dsh` install. The list therefore admits only packages whose graph is already in the installation's closure; Auto review adds nothing. The Inspector stays available through explicit installation and does not appear in the default plugin list. The browser-use and computer-use providers stay explicit compositions: Playwright MCP, Chrome DevTools MCP, and the native Cua Driver runtime binaries add about 85 MB and 21 packages to every install whether or not the bundle is enabled, and a shipped Cua Driver MCP switch would offer a capability whose executable the installation does not carry. Those provider packages keep locale display metadata for their component rows. Two more packages stay out for other reasons: `ptc-runtime-python` replaces the PTC runtime, and `workflow-ptc` rejects a non-TypeScript runtime at load while the Web presets carry `workflow-ptc` rows a bundle patch cannot reach; `browser-use-stagehand-native` requires a native model name and API key at schema validation with no configuration form on the page. +An optional bundle's dependencies are downloaded with every `dsh` installation. [Reasoning translation](2026-10-05-anonymous-reasoning-translation.md) uses three experimental packages without adding third-party runtime dependencies. Admission therefore considers installation cost and whether a switch supplies a usable composition without an additional configuration form. Browser and computer providers remain explicit compositions because they add substantial runtimes or require an external executable. Stagehand requires model credentials; Python PTC replaces a core provider and conflicts with TypeScript workflow consumers. Demonstrative hooks, webhooks, and mods stay outside the list. [Session Inspector](../feature/2026-09-24-session-inspector.md) owns the Inspector's distinct activation behavior. + +Bundles add scoped rows with the [profile patch operation](../../../../packages/boot/app-boot/README.md): `preset` names a declaration row, and the ordinary patch operates on its child list. Full Web presets receive the optional model tools; minimal retains its current shell and working-directory tools. Search, Ralph, and terminals supply their own isolated services. Search opens one in-memory index on demand per retained preset revision; the Host search policy stays unchanged. Badge skills and title providers act on the Host. Existing Agents retain their preset generation, and later user patch layers retain precedence. Title replacement waits for disposal of the previous provider. + +Claude Code and Codex install on demand through the separate `ON_DEMAND_BUNDLES` catalog; the [on-demand native-bundle decision](2026-10-05-official-on-demand-bundles.md) owns the cost, version, and offline-discovery trade-offs. ## Alternatives considered @@ -20,6 +24,8 @@ An optional bundle is a runtime dependency of the installation, so its dependenc **Mount the registration-only `computer-use` and `browser-use` services in `dsh-base`.** The shared composition would carry rows that only an optional provider bundle needs; a provider bundle can insert the service row from its own patch and dependencies. +**Register preset extensions at runtime.** A separate registration path would need its own ordering, module resolution, generation updates, and inspection behavior. Profile patches keep one effective composition for boot, dumps, validation, and user overrides. + ## Consequences -The Official group carries three optional-bundle entries, each tagged experimental by its experimental name. The runtime dependency closure of the installation is unchanged. A browser-use or computer-use provider still needs its Service Definition and the provider in a profile patch or composition. +Official discovery does not imply experimental status or a different plugin lifecycle. Switching a bundle off keeps its installed files; failed activation retains the existing saved-selection behavior. Scoped rows appear read-only under their bundle, and removal respects modules retained by live preset generations. Individual and combined composition tests cover the supported presets; live switching and recorded Sessions cover activation and model-visible behavior. diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md index 3198b409c7..13abff45a0 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md @@ -10,9 +10,13 @@ Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto r ## 决策 -`OPTIONAL_BUNDLES` 列出 Agent Teams、语音输入与 Auto review。每个可选 bundle 都声明 `icon` 并导出带 `meta.title` 与 `meta.description` 的 `./locale/*.json`,官方分组因此能渲染本地化标题、描述和图片;`verify-default-product-isolation` 会拒绝缺少这些声明的可选 bundle。Inspector 的 `cordis.patch.yml` 直接写包名而非构建产物路径,所以同一文件既是 bundle patch,也是已构建 Web 启动的 `--patch` overlay。 +`OPTIONAL_BUNDLES` 管理安装随附且默认关闭的选项,包括维护中的可选能力与实验性能力。每个 bundle 声明 `icon`,并导出带 `meta.title` 与 `meta.description` 的 `./locale/*.json`;官方分组显示这些元数据。Bundle 的展示方式不决定其[实验性状态](../../../../packages/experimental/README.zh.md#status)。隔离检查校验准入,并继续拒绝非实验 bundle 的实验性依赖。 -可选 bundle 是安装的运行时依赖,其依赖图会随每次 `dsh` 安装一起下载。因此名单只接纳依赖图已在安装闭包内的包;Auto review 不增加任何依赖。Inspector 保留显式安装方式,不在默认插件列表中显示。浏览器操作与电脑操作提供方仍保持显式组合:Playwright MCP、Chrome DevTools MCP 与原生 Cua Driver 运行时二进制会让每次安装多出约 85 MB、21 个包,无论 bundle 是否启用;而随附的 Cua Driver MCP 开关会提供一个安装本身并不携带其可执行文件的能力。这些提供方包保留 locale 显示元数据,供组件行使用。另有两个包因其他原因不纳入:`ptc-runtime-python` 会替换 PTC 运行时,而 `workflow-ptc` 在加载时拒绝非 TypeScript 运行时,且 Web preset 内含 bundle patch 无法触及的 `workflow-ptc` 行;`browser-use-stagehand-native` 在 schema 校验时就要求原生模型名称与 API 密钥,而插件页没有对应的配置表单。 +可选 bundle 的依赖随每次 `dsh` 安装下载。[思考过程翻译](2026-10-05-anonymous-reasoning-translation.zh.md)使用三个实验包,不增加第三方运行时依赖。因此准入需要考虑安装成本,以及开关能否在不增加配置表单的情况下提供可用组合。浏览器与电脑提供方仍使用显式组合,因为它们会增加较大的运行时或要求外部可执行文件。Stagehand 需要模型凭据;Python PTC 替换核心提供方,并与 TypeScript workflow 消费方冲突。演示性钩子、webhook 与模组不进入名单。[Session Inspector](../feature/2026-09-24-session-inspector.zh.md) 管理 Inspector 特有的激活行为。 + +Bundle 通过 [profile patch 操作](../../../../packages/boot/app-boot/README.zh.md)添加作用域行:`preset` 指定声明行,普通 patch 对其子列表生效。完整 Web 预设获得可选模型工具;minimal 保持当前的 shell 与工作目录工具。搜索、Ralph 与终端提供自己的隔离服务。搜索在每个保留的预设修订中按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。已有 Agent 保留其预设代际,后续用户 patch 层继续优先。标题替换等待前一个提供方完成清理。 + +Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,其成本、版本与离线发现取舍由[按需原生 bundle 决策](2026-10-05-official-on-demand-bundles.zh.md)管理。 ## 考虑过的替代方案 @@ -20,6 +24,8 @@ Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto r **在 `dsh-base` 中挂载仅负责注册的 `computer-use` 与 `browser-use` 服务。** 共享组合将携带只有可选提供方 bundle 才需要的行;提供方 bundle 可以从自己的 patch 和依赖插入服务行。 +**在运行时注册预设扩展。** 独立注册路径需要自己的排序、模块解析、代际更新与检查行为。Profile patch 为启动、配置导出、校验和用户覆盖保留同一个有效组合。 + ## 后果 -官方分组包含三个可选 bundle 条目,每项都因实验包名而带实验性标签。安装的运行时依赖闭包保持不变。浏览器操作或电脑操作提供方仍需在 profile patch 或组合中同时挂载其 Service Definition 与提供方。 +官方发现不表示实验性状态,也不引入不同的插件生命周期。关闭 bundle 保留已安装文件;激活失败保留现有的已保存选择行为。作用域行在 bundle 内只读展示,删除操作尊重 live 预设代际仍持有的模块。独立与组合测试覆盖受支持的预设;实时切换与已记录 Session 覆盖激活和模型可见行为。 diff --git a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.i18n.yaml deleted file mode 100644 index a9e885fe52..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-21-live-config-inspect-provider.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-21-live-config-inspect-provider.md -/agent-note-live-plugin-config-inspection-replaces-the-host-builtin-provider: - en: f57edbb8b3fa60ae - zh: 8d0fa0eb73bbdaf1 -/agent-note-live-plugin-config-inspection-replaces-the-host-builtin-provider/problem: - en: e01cc31ce200b24b - zh: 7462a52c3e59ec87 -/agent-note-live-plugin-config-inspection-replaces-the-host-builtin-provider/decision: - en: a7d21366030c31ac - zh: 047299df13e504b7 -/agent-note-live-plugin-config-inspection-replaces-the-host-builtin-provider/alternatives-considered: - en: a19dba57a4108fc6 - zh: 2e64893e8bcf6513 -/agent-note-live-plugin-config-inspection-replaces-the-host-builtin-provider/consequences: - en: efa454977043c76d - zh: d6bf61ac47310d3e diff --git a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.i18n.yaml index 567f3220b2..fae09a79ee 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.i18n.yaml @@ -12,8 +12,8 @@ en: ac461e0ea1702ecc zh: 745def8581dc7974 /agent-note-fatal-diagnostics-and-desktop-crash-reports/relationship-to-earlier-decisions: - en: 157e52fc4036044a - zh: e474d9c9a36f1381 + en: eced414858f08ef9 + zh: 40639593b0d5bb6f /agent-note-fatal-diagnostics-and-desktop-crash-reports/relationship-to-the-desktop-logging-design: en: 7dc438912d10574a zh: 07d05f9f17043d67 diff --git a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.md b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.md index 01cbb24fc8..0cf5f64b89 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.md +++ b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.md @@ -28,7 +28,7 @@ Four decisions ship together. ## Relationship to earlier decisions -This partially supersedes the [native Desktop fatal recovery](2026-09-15-desktop-native-fatal-recovery.md) note: the crash report file, not the Electron console, is now the record the dialog points to, and the dialog detail carries the report-path line; that note's dialog ownership, button set, and profile recovery remain active. The `uncaughtException` handling extends the release mechanism of the [fail-loud release](../bug-fix/2026-07-31-fail-loud-releases-the-terminal.md) note to synchronous throws. +This partially supersedes the [native Desktop fatal recovery](2026-09-15-desktop-native-fatal-recovery.md) note: the crash report file, not the Electron console, is now the record the dialog points to, and the dialog detail carries the report-path line; that note's dialog ownership, button set, and profile recovery remain active. The `uncaughtException` handling extends the release mechanism of the [fail-loud release](../../../../packages/boot/app-boot/README.md) note to synchronous throws. ## Relationship to the Desktop logging design diff --git a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.zh.md b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.zh.md index 38f6a6802b..c3513bbddd 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-22-fatal-diagnostics-and-crash-reports.zh.md @@ -28,7 +28,7 @@ macOS 上,启动审计把一组 Web 客户端行列为 `import failed (see con ## 与既有决定的关系 -本 note 部分取代 [Desktop 原生致命恢复](2026-09-15-desktop-native-fatal-recovery.zh.md):对话框指向的记录现在是崩溃报告文件而非 Electron 控制台,对话框详情多出报告路径一行;该 note 对对话框归属、按钮集合与 profile 恢复的决定仍然有效。`uncaughtException` 的处理把 [fail-loud release](../bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md) note 的 release 机制扩展到同步抛出。 +本 note 部分取代 [Desktop 原生致命恢复](2026-09-15-desktop-native-fatal-recovery.zh.md):对话框指向的记录现在是崩溃报告文件而非 Electron 控制台,对话框详情多出报告路径一行;该 note 对对话框归属、按钮集合与 profile 恢复的决定仍然有效。`uncaughtException` 的处理把 [fail-loud release](../../../../packages/boot/app-boot/README.zh.md) note 的 release 机制扩展到同步抛出。 ## 与 Desktop 日志体系设计的关系 diff --git a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.i18n.yaml index 48da0f768d..c1523d3b70 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.i18n.yaml @@ -12,17 +12,17 @@ en: a15c7c13f167e5ac zh: 63bcc2ef60c2f344 /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/decision/one-tool-owns-three-stages: - en: ea65da88c3df35bb - zh: a71afb4683a75290 + en: c4bac1088a4fd3bc + zh: 549348deae5485c1 /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/decision/start-marks-an-initialization-candidate: - en: 696a180246e23391 - zh: aeb2b1fdcfdf9631 + en: 7771032e6ad227e7 + zh: 313d32ab5312d965 /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/decision/converge-on-final-state-without-replaying-preparation: en: 603cbcf3379ad4f7 zh: e34d87ea348c756b /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/decision/shared-rows-reuse-their-existing-presentation: - en: 24536588cd0c979b - zh: 992d4c4657d6d034 + en: a5741c967212250b + zh: de6288916a054c6e /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/decision/relationship-to-existing-records: en: bd4984f66c507785 zh: 4d2372fdbc4c3cec @@ -30,5 +30,5 @@ en: 505095bec889700e zh: b77c492de3e8641a /agent-note-tool-calls-own-preparation-dispatch-and-results-by-callid/consequences: - en: 3cf36b54240013da - zh: 67e663b431bc67d2 + en: dd0e4561c50b1188 + zh: 0ec83177ef570a78 diff --git a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.md b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.md index f43eaf1a68..a99545b58b 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.md +++ b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.md @@ -18,7 +18,7 @@ The [Tool Definition](../../../../packages/client/ui-chat/src/client/conversatio | Stage | Creation or update evidence | Available data and presentation | |---|---|---| -| `preparing` | A live delta with a call ID and tool name | Call identity, name, and time; no complete arguments, and one non-expandable row. An opt-in Hook can read the raw argument prefix. | +| `preparing` | A live delta with a call ID and tool name | Call identity, name, time, and a lazy partial-argument view; one non-expandable row. | | `start` | `tool/call` | Complete arguments; enables the tool's existing call presentation. | | `result` | `tool/result` | The result and any complete arguments paired within the loaded window. | @@ -28,7 +28,7 @@ An argument block ending does not trigger dispatch presentation. Showing prepara The [Conversation Definition](../../../../packages/client/ui-conversation/src/client/contract/conversation.ts) retains its match/start/update interface and existing roles. Durable events and transient deltas may both match as start. The earliest start in the current event sequence initializes State; every later Match, including another start for the same ID, goes through update. -A named tool delta and `tool/call` are both initialization candidates. A live delta can create preparation; a historical `tool/call` creates the dispatched stage directly. Matching still reads only the current event, without Context lookup, additional roles, registries, or separate caches. +Every tool delta and `tool/call` are initialization candidates. A named live delta creates the preparing row; a historical `tool/call` creates the dispatched stage directly. Matching still reads only the current event, without Context lookup, additional roles, registries, or separate caches. Successful stream settlement publishes the final Assistant message while retaining transient deltas until `step/end`. Each `tool/call` updates its existing callId; the Assistant keeps its live ordering anchor until the same cleanup. Failed, interrupted, or abandoned streams retire their deltas immediately. On retirement the Assembler reselects starts and recomputes State from remaining Matches. Undispatched preparations hide without synthetic execution results. @@ -40,9 +40,9 @@ Groups include Tool nodes from preparation onward and classify and count them by ### Shared rows reuse their existing presentation -[ToolRow](../../../../packages/client/ui-tool/src/client/tool/components/ToolRow.tsx) reuses the existing icon, title, and row component across all stages. The shared row model selects the tool title and combines any generic tool-name prefix with the available argument summary. Preparation has no complete arguments or result, and the shared argument parser returns null without parsing JSON. The existing slot Hook binding optionally exposes a call-scoped raw prefix; it adds no preparation registration mechanism. ToolRow prevents preparation rows from expanding. +[ToolRow](../../../../packages/client/ui-tool/src/client/tool/components/ToolRow.tsx) reuses the existing icon, title, and row component across all stages. The shared row model selects the tool title and combines any generic tool-name prefix with the available argument summary. All stages expose the [lazy argument view](2026-09-24-preparing-tool-arguments.md); cards requiring dispatched arguments still return no call during preparation. ToolRow prevents preparation rows from expanding. -Read, read_image, write/edit, search, web, todo, question, details, and the generic fallback use this path. Custom rows such as Bash, Skill, Present, and Cordis keep their own preparation branches. No automatic/custom registration declaration or second tool-classification list in ToolTree is introduced. +Read, read_image, write/edit, search, web, todo, question, details, and the generic fallback use this path. Bash shares its component across stages; Skill, Present, and Cordis retain their own preparation branches. No automatic/custom registration declaration or second tool-classification list in ToolTree is introduced. ### Relationship to existing records @@ -60,8 +60,8 @@ This decision replaces the [business-node assembly](2026-08-09-client-conversati ## Consequences -- A tool may appear before its arguments are complete, but preparation neither implies execution nor exposes interactions requiring arguments. +- A tool may appear before its arguments are complete, but preparation never implies execution. Interactions use only fields already available. - Multiple starts with one ID belong to one lifecycle. Independent calls must use distinct IDs; a second-start error no longer detects identity reuse. -- The shared row primitive is reused across stages. Write/edit separate preparing and dispatched components so only the former subscribes to raw arguments; custom rows may also switch internal components by stage. -- Partial JSON parsing and `useToolCallDelta` are not provided. `useToolCallArgumentsPartial` exposes the existing Step's raw prefix without a second accumulator; write/edit use only its length for preparation progress. +- The shared row primitive is reused across stages. Write/edit and Bash keep their component identity; custom rows may switch internal components by stage. +- Partial-argument access and its update policy follow the lazy argument view; no separate raw-prefix Hook is required. - Assembly tests separately cover live preparation, withdrawal, active-stream reconstruction, durable history, and paged convergence. Component tests cover argument-free presentation and stage changes; a recorded session covers browser preparation and reload results. diff --git a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.zh.md b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.zh.md index accbd2e966..9bea117dd3 100644 --- a/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-22-tool-call-three-phases.zh.md @@ -18,7 +18,7 @@ Status: implemented | 阶段 | 创建或更新依据 | 可用数据与展示 | |---|---|---| -| `preparing` | 带调用 ID 和工具名的实时 delta | 调用身份、名称和时间;不提供完整参数,只显示不可展开的一行。可选钩子可读取原始参数前缀。 | +| `preparing` | 带调用 ID 和工具名的实时 delta | 调用身份、名称、时间和懒计算的部分参数视图;显示不可展开的一行。 | | `start` | `tool/call` | 完整参数;启用工具原有的调用展示。 | | `result` | `tool/result` | 调用结果,以及当前窗口内可配对的完整参数。 | @@ -28,7 +28,7 @@ Status: implemented 沿用 [Conversation Definition](../../../../packages/client/ui-conversation/src/client/contract/conversation.ts) 的 `match/start/update` 接口和既有角色。持久事件与瞬态 delta 都可以匹配为 start;当前事件序列中最早的 start 初始化 State,后续 Match,包括同 ID 的其他 start,都交给 update。 -Tool 的具名 delta 和 `tool/call` 都是创建入口。实时先收到 delta,就从 preparing 创建;历史先收到 `tool/call`,就从 start 创建。匹配函数仍然只读取当前事件,不查询 Context,不增加角色、注册表或旁路缓存。 +Tool 的每个 delta 和 `tool/call` 都是创建入口。具名实时 delta 创建准备行;历史 `tool/call` 直接创建派发阶段。匹配函数仍然只读取当前事件,不查询 Context,不增加角色、注册表或旁路缓存。 成功流结束时发布最终 Assistant 消息,瞬态 delta 保留到 `step/end`。每个 `tool/call` 更新已有 callId;Assistant 的实时排序锚点也保留到同一次清理。失败、中断或废弃的流立即移除 delta。清理时,Assembler 从剩余 Match 重新选择 start 并重算 State;未派发的准备节点隐藏,不合成执行结果。 @@ -40,9 +40,9 @@ Group 从准备阶段起就聚合 Tool 节点,按工具名分类和计数。As ### 通用行复用现有外壳 -[ToolRow](../../../../packages/client/ui-tool/src/client/tool/components/ToolRow.tsx) 在三个阶段复用既有图标、标题和行组件。公共行模型选择工具标题,并组合通用工具名前缀与已有参数摘要。准备阶段没有完整参数或结果,共享参数解析入口直接返回 null,不解析 JSON。既有 slot 钩子绑定可选地提供当前调用的原始前缀,不增加准备态注册机制;ToolRow 禁止准备行展开。 +[ToolRow](../../../../packages/client/ui-tool/src/client/tool/components/ToolRow.tsx) 在三个阶段复用既有图标、标题和行组件。公共行模型选择工具标题,并组合通用工具名前缀与已有参数摘要。所有阶段都提供[懒计算参数视图](2026-09-24-preparing-tool-arguments.zh.md);需要完整派发参数的卡片在准备阶段仍返回无调用。ToolRow 禁止准备行展开。 -read、read_image、write/edit、search、web、todo、question、details 和通用回退使用这条路径。Bash、Skill、Present、Cordis 等自定义外壳保留自己的准备态分支。不增加自动/自管注册声明,不在 ToolTree 维护另一份工具分类名单。 +read、read_image、write/edit、search、web、todo、question、details 和通用回退使用这条路径。Bash 在各阶段共用组件;Skill、Present、Cordis 保留自己的准备态分支。不增加自动/自管注册声明,不在 ToolTree 维护另一份工具分类名单。 ### 与已有记录的关系 @@ -60,8 +60,8 @@ read、read_image、write/edit、search、web、todo、question、details 和通 ## 影响 -- 工具可以在参数完整前出现,但准备展示不表示工具已经执行,也不暴露依赖参数的交互。 +- 工具可以在参数完整前出现,但准备展示不表示工具已经执行;交互只使用已可用的字段。 - 同一 ID 的多个 start 被视为同一个生命周期;业务必须为独立调用提供不同 ID,不能再依赖第二个 start 报错检测身份复用。 -- 各阶段复用通用行基础组件。write/edit 分离准备态与派发后组件,只有前者订阅原始参数;自定义外壳也可按阶段切换内部组件。 -- 不提供部分 JSON 解析或 `useToolCallDelta`。`useToolCallArgumentsPartial` 暴露既有 Step 的原始前缀,不另建累积器;write/edit 只使用其长度展示准备进度。 +- 各阶段复用通用行基础组件。write/edit 和 Bash 保持组件身份;自定义外壳可按阶段切换内部组件。 +- 部分参数的读取与更新策略遵循懒计算参数视图,不需要独立的原始前缀钩子。 - 组装测试分别覆盖实时准备态、撤回、活跃流重建、持久历史与分页收敛;组件测试检查无参数展示和阶段切换,录制会话覆盖浏览器中的准备态与重载结果。 diff --git a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.i18n.yaml index 28b2efa813..11a37ac266 100644 --- a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md /agent-note-hide-the-desktop-window-on-close-and-confirm-interruptible-quits: - en: 9e029938c58bf143 - zh: f60f9cf2067a02ee + en: 92bb54cd9fe6b168 + zh: b0952405c6066416 /agent-note-hide-the-desktop-window-on-close-and-confirm-interruptible-quits/problem: en: 782e934449eae28c zh: 0941465dff3a26d9 diff --git a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md index 67c7bf92e0..937cff4f32 100644 --- a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md +++ b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md) -Window timing and shutdown ownership otherwise follow [showing the window before the Host starts](2026-09-09-desktop-immediate-window-and-direct-start.md); the task rule reuses the [update restart check](2026-08-25-electron-desktop-packaging-and-updates.md). This partially supersedes the [standard macOS window menus](../bug-fix/2026-09-16-desktop-window-menus.md) decision: ⌘W no longer destroys the window through Electron's role, and Dock activation reopens the hidden window when no window is visible instead of only when none exists. That note's menu declarations stay in force. +Window timing and shutdown ownership otherwise follow [showing the window before the Host starts](../../../../apps/desktop/README.md); the task rule reuses the [update restart check](2026-08-25-electron-desktop-packaging-and-updates.md). ⌘W does not destroy the window through Electron's role, and Dock activation reopens the hidden window when no window is visible instead of only when none exists. The native menu declarations in the [Desktop reference](../../../../apps/desktop/README.md) stay in force. ## Problem diff --git a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md index e5a59bcd0b..02633e6e04 100644 --- a/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-09-23-desktop-close-to-background-and-quit-confirmation.md) | 中文 -窗口时序和关闭归属仍遵循[在 Host 启动前显示窗口](2026-09-09-desktop-immediate-window-and-direct-start.zh.md);任务判定复用[更新重启检查](2026-08-25-electron-desktop-packaging-and-updates.zh.md)。本记录部分取代[标准 macOS 窗口菜单](../bug-fix/2026-09-16-desktop-window-menus.zh.md)决策:⌘W 不再通过 Electron 的 role 销毁窗口,Dock 激活在没有可见窗口时重新打开隐藏窗口,而不再只在没有任何窗口时才创建。该记录中的菜单声明仍然有效。 +窗口时序和关闭归属仍遵循[在 Host 启动前显示窗口](../../../../apps/desktop/README.zh.md);任务判定复用[更新重启检查](2026-08-25-electron-desktop-packaging-and-updates.zh.md)。⌘W 不通过 Electron 的 role 销毁窗口,Dock 激活在没有可见窗口时重新打开隐藏窗口,而不再只在没有任何窗口时才创建。[Desktop 参考](../../../../apps/desktop/README.zh.md)中的原生菜单声明仍然有效。 ## 问题 diff --git a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.i18n.yaml deleted file mode 100644 index 5bd54753a0..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-23-schedule-local-menu.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.md -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client: - en: a9ed8f9cf0bdfd66 - zh: ff11822efe6b48b7 -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client/problem: - en: 076e6ca799bf2045 - zh: d3f87030015cdf3c -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client/decision: - en: c35ab9fa2549866d - zh: 01855f87b884dc93 -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client/alternatives-considered: - en: 3648e55bb76a4381 - zh: 83c98e0789a6df9c -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client/consequences: - en: f6380c38e35bce37 - zh: 1379ff1763d0eb10 -/agent-note-own-the-schedule-detail-menus-inside-the-schedule-client/testing: - en: f1de601175678a9a - zh: 58fddec29a462b98 diff --git a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.md b/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.md deleted file mode 100644 index fd3266bbc4..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Note: Own the Schedule detail menus inside the Schedule client - -Status: implemented - -English | [中文](2026-09-23-schedule-local-menu.zh.md) - -## Problem - -The Automation tasks detail needs a menu whose list carries a pinned control: the Time zone row's search field must stay above the scrolling zone rows. The shared `Menu` in `@deepseek-ai/dsh-client-ui-primitives` renders rows, separators, labels, a selection mark, and a keyboard walk, but it has no header slot. The Delivery records tab shows a saved record's message id behind an info glyph, and the reader must be able to read and copy that id. - -Serving either need by extending `ui-primitives` puts one feature's behavior into the package every client plugin consumes. The same branch had already removed unrelated `ui-layout` and `ui-sidebar-right` edits after review asked why a Schedule change touched them, and a shared primitive extended for a single caller still costs every future reader of that primitive. - -## Decision - -`packages/client/ui-schedule` owns its menu. `src/client/TaskMenu.tsx` with `TaskMenu.module.css` holds a schedule-local copy that renders rows, separators, labels, disabled and destructive rows, a trailing `selectedId` mark, the pointer and keyboard walk, portaled placement, the pinned `header` slot, and the `data-menu-field` focus handover. It imports the shared `useAnchoredPosition` and `useDismissOnOutsidePointer` hooks, which are the placement and dismissal behavior any anchored panel needs, and it imports no runtime value from `ui-primitives`. The four detail menus use it. - -The Delivery records tab names and copies a record's message id through an info button. The button's accessible name states the action and the record (`Copy message ID: `), the shared `Tooltip` states the id on hover and on keyboard focus, and activation copies it through the shared `writeClipboard` helper and reports `Copied` in a status region beside the glyph. `Tooltip` therefore keeps its master shape: the mode that held one bubble open under the pointer is gone, and the Delivery records view does not use the `HoverCard` that opens on the pointer alone. - -`packages/client/ui-primitives` returns to master except the clock glyph's stroke. The client bundle purity gate in `packages/client/tsdown.client.ts` is the neighboring rule for the other direction of this boundary: a client plugin may import another plugin's package only as types or as a module-table request, so shared runtime vocabulary travels through an inline-safe layer or a cordis service rather than a package edge. - -## Alternatives considered - -**Add a `header` prop to the shared `Menu`.** This is what the branch first did: an optional slot plus the `data-menu-field` focus handover, with tests in the shared package. It gives the smallest diff and keeps one menu implementation, and it was rejected because the only caller is the Schedule detail: `ui-primitives` would carry API that exists for one feature, and every client plugin's bundle would read a header contract none of them use. - -**Add an `interactive` mode to the shared `Tooltip`.** Shipped first and then removed: a grace period between anchor and bubble, pointer hold, click toggling, and selectable text, with six tests. It lost because the shared tooltip already opens on focus and the shared clipboard helper already performs the copy, so the mode added a second way to hold one bubble open for a single caller. - -**Name and copy the message id through `HoverCard`.** Shipped second and then replaced: the card holds while the pointer rests on it, selects its text, and copies on activation. It lost because `HoverCard` opens on `onPointerEnter` alone, so the already-focusable glyph showed a keyboard reader nothing and offered no copy path. - -**Render the zone search field outside the menu list.** A search input above the trigger, or a separate row in the rule card, needs no primitive change. It was rejected because the field would scroll away from the list it filters or sit in a different surface than the rows it filters. - -**Build the zone picker on the schedule-local `PickerPopover`.** The Date and Time pickers already use that panel, so the zone list could reuse it. It was rejected because `PickerPopover` positions and dismisses a panel but renders no list semantics: the keyboard walk, the row roles, and the selected-row mark would have to be rebuilt inside the Schedule client anyway, which is the copy this note describes with more surface than the menu it replaces. - -## Consequences - -The Schedule client carries a second menu implementation: 346 lines of component, 189 lines of CSS, and 403 lines of tests, in exchange for `ui-primitives` returning to master except the clock glyph's stroke. The copy is trimmed to what the detail uses, and the absent capabilities stay absent: submenus, component-rendered rows, a pinned footer, multiple selection ids, the fill selection mode, dense and compact spacing, mount-time autofocus, a caller-supplied anchor rect, pointer-leave closing, and a side parameter. A later Schedule menu that needs one of them extends the copy or revisits this decision. - -Copying is an activation, not a text selection: the tooltip shows the id but does not accept the pointer, so the desktop gesture that copies the id is a click on the glyph. That is the capability the removed interactive mode had and this shape gives up. Shared behavior stays shared: placement, dismissal, focus handover, the duplication gate's exclusion markers naming both owners, and per-file coverage all apply to the copy. The copied region carries `jscpd:ignore-start` and `jscpd:ignore-end` with the two owners named, following the [cross-package value dependencies note](../../archived/process/2026-08-23-client-cross-package-value-dependencies.md); the schedule-specific header, handover, and Tab semantics sit outside that region. - -The copy places its list with `useAnchoredPosition`, which clamps every edge by the same 12px margin, while the shared menu's top edge used the frame-top clearance helper. A list that must clamp upward against the top of a macOS window can therefore sit about 12px lower than the shared menu did in the same situation; the stylesheet still reserves the clearance for the list's own maximum height, so the common case is unchanged. - -## Testing - -`packages/client/ui-schedule/tests/task-menu.client.spec.tsx` pins the pointer and keyboard walk, the `header` field's Home/End ownership, the `data-menu-field` handover after placement, the selected-row mark, disabled rows, portal placement, outside dismissal, and focus return; the three menu-header tests that lived in `ui-primitives` moved there with the capability. `delivery-history.client.spec.tsx` pins the tooltip that keyboard focus raises, the button's accessible name, the clipboard write, and the copy confirmation that clears. The coverage gate reports 100% per file for `packages/client/ui-schedule/src`, and the duplication gate reports no clones across `packages/client/ui-schedule` and `packages/client/ui-primitives`. diff --git a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.zh.md b/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.zh.md deleted file mode 100644 index 924f14f5ad..0000000000 --- a/.agents/notes/implemented/architecture/2026-09-23-schedule-local-menu.zh.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Note: Schedule 详情菜单由 Schedule 客户端自己拥有 - -Status: implemented - -[English](2026-09-23-schedule-local-menu.md) | 中文 - -## Problem - -Automation tasks 详情需要一个列表顶部钉住控件的菜单:Time zone 行的搜索框必须停在滚动时区行之上。`@deepseek-ai/dsh-client-ui-primitives` 里的共享 `Menu` 会渲染行、分隔线、标题行、选中标记和键盘走位,但没有 header 槽。Delivery records 页把一条已保存记录的 message id 放在 info 图标背后,读者要能读到并复制这个 id。 - -用扩展 `ui-primitives` 来满足其中任一需求,都会把单个功能的行为放进每个 client 插件都消费的包里。同一条分支此前已经因为 review 质询“Schedule 的改动为什么碰它们”而撤掉了无关的 `ui-layout` 与 `ui-sidebar-right` 编辑;而一个只为单一调用方扩展的共享原语,对之后每一个读它的人都是成本。 - -## Decision - -`packages/client/ui-schedule` 自己拥有菜单。`src/client/TaskMenu.tsx` 与 `TaskMenu.module.css` 保存一份 schedule 本地副本,渲染行、分隔线、标题行、disabled 与 dangerous 行、`selectedId` 尾随标记、指针与键盘走位、portal 定位、钉住的 `header` 槽,以及 `data-menu-field` 焦点交接。它导入共享的 `useAnchoredPosition` 与 `useDismissOnOutsidePointer` hook——任何锚定面板都需要的定位与关闭行为——不从 `ui-primitives` 导入任何运行时值。详情的四个菜单都改用它。 - -Delivery records 页用一个 info 按钮给出并复制记录的消息 id。该按钮的可访问名称同时说明动作与记录(`Copy message ID: `),共享 `Tooltip` 在悬停与键盘聚焦时给出该 id,激活时通过共享的 `writeClipboard` 复制,并在图标旁用 status 区域报告 `Copied`。因此 `Tooltip` 保持 master 形态:那个让气泡在指针下保持展开的模式已不存在,Delivery records 页也不使用只在指针下打开的 `HoverCard`。 - -`packages/client/ui-primitives` 除时钟图标描边外回到 master。`packages/client/tsdown.client.ts` 的 client bundle purity gate 是这条边界另一方向上的相邻规则:client 插件只能以类型或 module-table request 的形式导入另一个插件的包,因此共享运行时词汇要么走 inline-safe 层,要么走 cordis service,而不是包与包之间的边。 - -## Alternatives considered - -**给共享 `Menu` 加 `header` prop。** 这是分支最初的做法:一个可选槽加 `data-menu-field` 焦点交接,并在共享包里带测试。它的 diff 最小、只保留一份菜单实现;被否决是因为唯一的调用方是 Schedule 详情:`ui-primitives` 会背上只为单个功能存在的 API,而每个 client 插件的 bundle 都要读一份没人使用的 header 契约。 - -**给共享 `Tooltip` 加 `interactive` 模式。** 先落地后撤除:锚点与气泡之间的宽限期、指针停留、点击切换、可选中的文字,外加六个测试。被否决是因为共享 tooltip 本来就支持聚焦展开、共享剪贴板助手本来就负责复制,这个模式只是为单一调用方多出了一种保持气泡展开的方式。 - -**用 `HoverCard` 给出并复制消息 id。** 第二个落地又被替换的版本:指针停在卡片上时保持展开、文字可选中、激活即复制。被否决是因为 `HoverCard` 只在 `onPointerEnter` 打开,于是那个本来就可聚焦的图标对键盘读者什么都不显示,也没有复制入口。 - -**把时区搜索框渲染在菜单列表之外。** 放在触发器上方的输入框,或规则卡里的独立一行,都不需要改原语。被否决是因为该输入框会与它所过滤的列表分离滚动,或落在与它过滤的行不同的界面层上。 - -**用 schedule 本地的 `PickerPopover` 实现时区选择器。** Date 与 Time picker 已经用它。被否决是因为 `PickerPopover` 只负责定位与关闭面板,不渲染列表语义:键盘走位、行 role、选中行标记都要在 Schedule 客户端里重建,那正是本 note 描述的副本,而且比它所替代的菜单要维护更多东西。 - -## Consequences - -Schedule 客户端多出一份菜单实现:346 行组件、189 行 CSS、403 行测试,换来 `ui-primitives` 除时钟图标描边外回到 master。副本按详情实际使用裁剪,缺失的能力保持缺失:submenu、组件渲染行、钉住的 footer、多个 selectedId、fill 选中模式、dense 与 compact 间距、挂载时 autofocus、调用方提供锚点矩形、指针离开即关闭,以及 side 参数。日后某个 Schedule 菜单需要其中一项时,要么扩展副本,要么重新审视这个决定。 - -复制是一次激活,而不是选中文本:tooltip 给出 id 但不接受指针,所以桌面端复制该 id 的手势是点击图标;这正是被撤掉的交互模式所具备、而当前形态放弃的能力。共享行为仍是共享的:定位、关闭、焦点交接、点名两个 owner 的 duplication gate 排除标记,以及 per-file 覆盖率都适用于这份副本。被拷贝的区域带 `jscpd:ignore-start` 与 `jscpd:ignore-end` 并点名两个 owner,遵循 [cross-package value dependencies note](../../archived/process/2026-08-23-client-cross-package-value-dependencies.md);schedule 专有的 header、交接与 Tab 语义不在该区域内。 - -副本用 `useAnchoredPosition` 定位列表,它对四条边都用同一个 12px margin,而共享菜单的顶边用的是 frame-top clearance helper。因此在 macOS 窗口顶部需要向上钳制的列表,可能比共享菜单原先的位置低约 12px;样式表仍为该列表自身的最大高度保留 clearance,常见情形不变。 - -## Testing - -`packages/client/ui-schedule/tests/task-menu.client.spec.tsx` 钉住指针与键盘走位、`header` 输入框对 Home/End 的独占、落位后的 `data-menu-field` 交接、选中行标记、disabled 行、portal 定位、外部关闭与焦点回返;原先位于 `ui-primitives` 的三个菜单 header 测试随该能力一起迁入。`delivery-history.client.spec.tsx` 钉住键盘聚焦展开的工具提示、按钮的可访问名称、剪贴板写入,以及会自行清除的复制确认。覆盖率门禁对 `packages/client/ui-schedule/src` 报告 per-file 100%,duplication 门禁在 `packages/client/ui-schedule` 与 `packages/client/ui-primitives` 上报告 0 克隆。 diff --git a/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.i18n.yaml new file mode 100644 index 0000000000..fcc4c5d557 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.i18n.yaml @@ -0,0 +1,31 @@ +# Bilingual-pair consistency record for 2026-09-24-preparing-tool-arguments.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.md +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation: + en: 07f6420c67ea2700 + zh: b9b53250e050cf6e +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/problem: + en: c97b3edd4ef1d063 + zh: 5c8b6065e662f326 +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/decision: + en: a15c7c13f167e5ac + zh: 63bcc2ef60c2f344 +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/decision/a-lazily-computed-argument-view-lives-in-dsh-util-values: + en: eb004194b0c08464 + zh: d09c6353ed7138de +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/decision/parsing-happens-in-the-tool-definition-not-in-react: + en: 518206f94d82993d + zh: c6cab3b2f71dd18b +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/decision/argument-declaration-order: + en: 0fd7c9f0b8cf6a28 + zh: 7d410403283b9996 +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/alternatives-considered: + en: 7a033f81c557c58f + zh: 2ec66b0db6cf5d9e +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/verification: + en: c1e09085a137be1c + zh: ec79806f84f59dec +/agent-note-incremental-tool-argument-scanning-and-argument-order-during-preparation/consequences: + en: f1c44f9d58d36920 + zh: bb297337882ec407 diff --git a/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.md b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.md new file mode 100644 index 0000000000..60a9bc6a15 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.md @@ -0,0 +1,88 @@ +# Agent Note: Incremental tool-argument scanning and argument order during preparation + +Status: implemented + +English | [中文](2026-09-24-preparing-tool-arguments.zh.md) + +## Problem + +[Three-stage tool calls](2026-09-22-tool-call-three-phases.md) make tool identity visible before `tool/call`. A raw argument prefix alone cannot supply field-aware presentation: write/edit need a complete path and decoded content length, while bash/run_code need the description before the command or program finishes streaming. + +The order in which the model generates arguments also decides what can appear first. On 2026-09-24, 310 controlled requests against the real DeepSeek V4 API exercised bash/run_code/write/edit: + +| Variant | Change | bash first key is description | run_code first key is description | +|---|---|---|---| +| Baseline | — | 0/20 | 1/20 | +| `properties` order only | description moved first | 0/20 | 0/20 | +| `properties` + `required` together | Same plus reordered `required` | Forced tool_choice 10/10; free calling Pro 7/10, Flash 0/10 | Forced 10/10; free 20/20 | +| Together + one ordering instruction in the description text | Appends "Provide `description` before `command` in the arguments." | Free calling Flash 19/20, Pro 20/20 | 20/20 | + +In these samples, reordering `required` changes argument order where reordering `properties` alone does not; V4 Flash's free bash calls also need the explicit instruction. The `dsh-tools` schema DSL generates `required` in property declaration order, so changing the declaration order in source reorders both. The probe uses a minimal system prompt, not the complete Harness prompt. + +## Decision + +### A lazily computed argument view lives in `dsh-util-values` + +`PartialArguments` is a tool-independent lazy reader. It retains separate argument fragments and a top-level key/value range index, or borrows an already parsed PTC payload. Reads index only newly received boundaries; unrequested values are neither decoded, counted, nor parsed. Requested fields materialize from their own ranges, without a per-tool field specification or registry. + +The boundary scanner tracks quotes, backslash parity, and matching nested brackets across fragments. Key names are decoded for lookup, while value contents are deferred. `complete()` reports a closing delimiter, not validated content; `invalid` reports errors already found by indexing or a content read. Content errors do not prevent indexing subsequent fields. Formal tool-input validation remains outside this presentation reader. + +Requested unescaped lengths derive directly from offsets. Escaped strings maintain content cursors only for requested length, text, or bounded-prefix reads; a limit query stops once its answer is known. Closed strings use native decoding when their full text is first requested. Arbitrary late reads require retaining the original fragments until authoritative complete text replaces them. + +The view judges change itself: `append(delta)` only retains the fragment, and `refresh()` compares remembered answers at publication time. With nothing read yet, it does not scan or report a change. Only refresh advances an existing answer's comparison baseline; an intervening read cannot suppress another consumer's update. The business states its granularity as it reads: `stringLength('content', { step: 1024 })` makes only a kilobyte crossing count, `text('description')` makes every character count, and `textPrefix()` limits decoded text to the requested UTF-16 prefix. An `offset` includes completed strings when edit progress combines old and new text. + +### Parsing happens in the Tool Definition, not in React + +The [Tool Definition](../../../../packages/client/ui-chat/src/client/conversation-nodes/tool.ts) matches every `tool-call-delta` carrying an id as a start candidate: the earliest opens the Context whether or not it is named, later ones fold as updates. The named delta creates the preparing root with a fresh streaming view; every delta only calls `args.append(delta)`. At the `animation-frame` publication point, `buildViewNode()` refreshes observed answers and replaces the root only when they change; otherwise it reuses the currently published root. The Context and its published Node own these identities without a separate preparation cache. A stream whose arguments precede the name is not scanned: a view opened late sees a non-object prefix, turns invalid, and reports no fields. + +Every stage's block exposes `name` and `args`; legacy `argsRaw` and result `call: { name, argsRaw } | null` remain available. `block-end` and `tool/call` reconcile argument fragments against their authoritative full text with length and exact per-fragment comparisons. Matching text seals the same reader and preserves its indexed fields; missing or conflicting deltas create a new `fromText()` view. No cumulative string or probabilistic hash is needed. PTC readers use `fromObject(payload)`, results reuse the started reader, and an unpaired result uses the shared empty view. Card models continue to read finalized `argsRaw`. + +Assistant blocks retain tool identity and first-token timing, not argument deltas. Complete argument text comes from `block-end` or the durable message. Parameter-only deltas preserve Assistant State, while the Tool Definition owns preparing reads and notifications. + +The Assembler shares one immutable Match per input and lifecycle role, including its Location, while each Definition retains independent State. Empty dependency sets require no replay work. + +Rows use the same readers across stages: read/write/edit show an openable path once `file_path` closes; write/edit show decoded content length in 1024-character units while content streams; bash/pwsh/run_code show the description prefix. Chat group detail reads the same view using its existing field priority. Without usable detail it stays empty while fields can still arrive, then falls back to the tool name once `closed()` is true, for every category. + +Group detail normalizes an initial 512-unit decoded prefix and expands it only when whitespace or multi-unit grapheme clusters prevent deciding the 160-cluster result. A filled prefix remains unchanged as its field grows, while a later higher-priority field still republishes the detail. String-length checks do not materialize the full text. Detail already within 160 UTF-16 units requires no grapheme traversal. + +Third-party tools receive the same view without registration or a separate subscription. Write/edit and Bash share their component across stages. The mutable argument reader is a narrow exception to JSON-compatible owner data: its Definition publishes a new block reference when an observed answer changes. Scan caches use private fields so read history does not affect structural comparison of equal sources. + +### Argument declaration order + +`tool-bash` and `tool-pwsh` declare `description` before `command` and include the ordering instruction. Both `parameters` declarations of `run_code` put `description` before `code` and share a parameter description requesting that generation order for TypeScript and Python. Write/edit declare `file_path` first and ask for it before `content` or `old_string`/`new_string` in the path parameter description. Read retains its leading `file_path`; other tools retain their argument order. + +## Alternatives considered + +**Parse inside a hook closure (a selector subscribing to the Step source).** Only the subscribing row sees the parsed result, so group titles need another subscription and each row owns parser lifetime. Definition-owned views share parsed fields across consumers. + +**Keeping a per-tool field specification in the Definition (built-in table plus a runtime registry).** The Definition is a pure function without ctx, so a runtime registry could only reach it through a factory closure, and a third-party tool would register in two places, the slot and the registry; the specification only ever said which fields keep text, information that on-demand decoding from the raw text no longer needs. + +**Third-party `partial-json`.** It reparses the cumulative input each frame and does not expose string completion separately from the decoded value. + +**Eagerly parse every field.** Large fields such as `content` usually need only a length; eagerly materializing their text adds decoding and memory costs before a consumer asks for it. Observing the entire key list also republishes a block when an unused argument appears; detail keeps field-specific observations. + +**Slice the normalized field for a truncated detail.** A V8 substring can retain the entire field. Joining the bounded selection of grapheme clusters keeps the displayed prefix independent of that source. + +**Reorder `properties` only.** Measured 0/40 effective; the model follows the `required` order. + +## Verification + +- Parser tests cover lazy scanning, observed-answer changes, empty and repeated keys, split escapes, non-string values, invalid input, sealed views, and read-independent structural equality. +- Row and grouping tests cover streamed paths and descriptions, content length, shared row identity, name fallback only when arguments cannot grow, and at most 161 clusters per truncation pass without changing whitespace or Unicode handling. +- Negative controls restore read-time acknowledgement, repeated Match allocation, and short-text segmentation; four focused tests fail at their corresponding assertions and pass after restoration. Definitions retain independent State; Turn and Step records remain Session-owned. +- Additional controls eagerly parse unread containers, accept same-length conflicting text, or rebuild Assistant State for parameter-only deltas. Each fails its owning assertion; the boundary-index implementation passes after restoration. + +The required [conversation-fold benchmark](../../../../benchmarks/conversation-fold/conversation-fold.bench.client.ts) drives the real Tool Definition, Assembler, Chat groups, and published write-row reads with 16-character fragments, flushing every 64 fragments. Three fresh compiled Node workers report all samples and their median; setup and forced GC are outside timing, and retained heap is measured with the Assembler and argument view still reachable. Row reads validate decoded progress and the file path; the worker also reports process peak RSS. + +Local Chat-only measurements on 2026-09-29 compare the pre-feature baseline with the optimized implementation for 512 KiB write content and 32,769 body deltas. On Linux x64, AMD EPYC 7763, Node 24.18.0, median unprofiled processing time is 212.16 → 176.12 ms, cumulative allocation including collected objects is 132.06 → 76.65 MiB, and retained heap is 24.89 → 21.84 MiB. These measurements exclude model, network, browser rendering, and non-Chat targets; they do not establish whole-page or CI-runner latency. + +Match calls remain 393,228; update calls rise from 98,307 to 131,076 because the Tool Definition consumes every argument delta. The baseline displays raw-length write progress rather than decoded argument reads. + +The CI budgets remain 750/375 ms and 37.5/10 MiB: reference expectations of 300/150 ms use the shared 2× time scale and 1.25× headroom, while 30/8 MiB heap expectations use headroom only. Historical cumulative-indexing/full-segmentation controls take 3984.9–4035.5 ms for write and 1471.2–1494.9 ms for bash, exceeding those unchanged budgets. + +## Consequences + +- Observed argument changes replace preparing blocks; unread calls retain their block reference. Views retain source text, and group headers can request fields independently of tool rows. Started and settled views scan on first read without publishing updates. +- Schema order and description instructions encourage label-first generation but do not guarantee it. Rows show usable fields as they arrive; a late path delays its display and the accompanying content progress. +- Presentation remains Client-derived; argument scanning adds no Session event or persistence format. +- A stream whose name arrives after its arguments gives up scanning; that call's preparing row falls back to the title alone. diff --git a/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.zh.md b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.zh.md new file mode 100644 index 0000000000..653b517aed --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.zh.md @@ -0,0 +1,88 @@ +# Agent Note: 准备阶段的工具参数增量扫描与参数顺序 + +Status: implemented + +[English](2026-09-24-preparing-tool-arguments.md) | 中文 + +## 问题 + +[三阶段工具调用](2026-09-22-tool-call-three-phases.zh.md)让工具身份在 `tool/call` 之前就可见。只有原始参数前缀不足以按字段展示:write/edit 需要完整路径和解码后的内容长度,bash/run_code 需要在命令或程序流入结束前显示描述。 + +模型生成参数的顺序也决定了什么能先显示。2026-09-24 用真实 DeepSeek V4 API 对 bash/run_code/write/edit 做了 310 次受控请求: + +| 变体 | 改了什么 | bash 首键为 description | run_code 首键为 description | +|---|---|---|---| +| 现状 | — | 0/20 | 1/20 | +| 只调 `properties` 顺序 | description 提前 | 0/20 | 0/20 | +| `properties` + `required` 同调 | 同上并调 `required` | 强制 tool_choice 10/10;自由调用 pro 7/10、flash 0/10 | 强制 10/10;自由 20/20 | +| 同调 + 描述文本加一句顺序指令 | 追加「Provide `description` before `command` in the arguments.」 | 自由调用 flash 19/20、pro 20/20 | 20/20 | + +在这些样本中,单调 `properties` 无效时,调整 `required` 能改变参数顺序;V4 Flash 自由调用 bash 还需要显式指令。`dsh-tools` 的 schema DSL 按属性声明顺序生成 `required`,所以改源码声明顺序会同时调整两者。探针使用极简系统提示词,不是完整 Harness 提示词。 + +## 决策 + +### 懒计算的参数视图住在 `dsh-util-values` + +`PartialArguments` 是一个与工具无关的懒读器。它分别保留参数分片和顶层键值范围索引,或借用已解析的 PTC 参数。读取只索引新收到的边界;未请求的值不解码、不计数、不解析。请求字段时仅从该字段范围生成值,不需要每工具字段规格或注册表。 + +边界扫描器跨分片跟踪引号、反斜杠奇偶和嵌套括号配对。键名为查找而解码,值内容延后处理。`complete()` 表示结束分隔符已到达,不代表内容已校验;`invalid` 报告索引或内容读取已经发现的错误。内容错误不阻止后续字段建立索引。正式工具入参校验仍位于此展示读器之外。 + +被请求的无转义字符串长度直接由偏移量计算。转义字符串仅为已请求的长度、文本或有界前缀维护内容游标;上限查询在答案确定后停止。完整字符串首次被请求全文时使用原生解码。支持任意晚到的读取,需要保留原分片,直到权威完整文本替换它们。 + +变化由视图自己判定:`append(delta)` 只保留分片,`refresh()` 在发布时比较已观察的答案。没人读过时,不扫描也不报告变化。只有 refresh 推进已有答案的比较基线;中途读取不能吞掉其他消费方的更新。业务在读的时候说明粒度——`stringLength('content', { step: 1024 })` 让跨 KB 才算变化,`text('description')` 让每个字符都算变化,`textPrefix()` 将解码文本限制在请求的 UTF-16 前缀内。edit 进度合计新旧文本时,`offset` 计入已完成的字符串。 + +### 解析在 Tool Definition 层,不在 React + +[Tool Definition](../../../../packages/client/ui-chat/src/client/conversation-nodes/tool.ts) 把每个带 id 的 `tool-call-delta` 都匹配为 start 候选:最早的一个打开 Context(无论是否带名),后续折叠为 update。带名 delta 创建 preparing 根并挂上一个新的流式视图;每个 delta 只做 `args.append(delta)`。在 `animation-frame` 发布点,`buildViewNode()` 刷新已观察答案,仅在变化时替换根块,否则复用当前已发布根块。Context 和已发布 Node 持有这些身份,不另建准备阶段缓存。参数在名字之前到达的流不扫描:迟开的视图看到非对象前缀即 invalid、零字段。 + +三个阶段的块都提供 `name` 和 `args`,原有 `argsRaw` 和 result 的 `call: { name, argsRaw } | null` 仍可使用。`block-end` 和 `tool/call` 通过长度及逐分片精确比较,将流式参数与权威全文核对。文本一致时封存原读器并保留字段索引,分片缺失或冲突时创建新的 `fromText()` 视图。不需要累加字符串或概率性 hash。PTC 读器使用 `fromObject(payload)`,结果复用已派发读器,未配对结果使用共享空视图。卡片模型继续读取最终 `argsRaw`。 + +Assistant 块保留工具身份和首 token 时间,不保留参数 delta。完整参数文本来自 `block-end` 或持久消息。纯参数 delta 保持 Assistant State 不变,preparing 读取与通知由 Tool Definition 负责。 + +Assembler 为同一输入和生命周期角色共享一个不可变 Match 及其 Location,各 Definition 仍保留独立 State。空依赖集合无需执行回放。 + +工具行在各阶段使用相同读器:read/write/edit 在 `file_path` 闭合后显示可打开的路径;write/edit 在内容流入时按 1024 字符单位显示解码后长度;bash/pwsh/run_code 显示描述前缀。Chat 分组详情按既有字段优先级读取同一视图。没有可用详情时,只要字段还可能到达就留空;`closed()` 为真后才回退工具名,所有类别一致。 + +分组详情先规范化 512 单元的解码前缀,仅当空白或多单元字素簇使其无法判定 160 字素簇的结果时才扩大前缀。前缀填满后,字段继续增长不会改变它,但稍后出现的更高优先级字段仍会发布新详情。字符串长度检查不会物化全文。详情不超过 160 个 UTF-16 单元时,无需遍历字素。 + +三方工具无需注册或独立订阅就能取得同一视图。write/edit 和 Bash 在各阶段共用组件。可变参数读器是 owner 数据要求 JSON 兼容的一项限定例外:已读取答案变化时,所属 Definition 发布新的块引用。扫描缓存使用私有字段,同源视图的结构比较不受读取历史影响。 + +### 参数声明顺序 + +`tool-bash`、`tool-pwsh` 在 `command` 前声明 `description`,并包含顺序指令。`run_code` 两处 `parameters` 将 `description` 放在 `code` 前,共用的参数描述要求 TypeScript 和 Python 都按此顺序生成。write/edit 首先声明 `file_path`,并在路径参数描述中要求先生成它,再生成 `content` 或 `old_string`/`new_string`。read 保留首位的 `file_path`;其他工具保留原有参数顺序。 + +## 考虑过的替代方案 + +**在 hook 闭包里解析(选择器订阅 Step source)。** 只有订阅行能看到解析结果,分组标题需要另一份订阅,每行还要负责解析器生命周期。Definition 持有的视图让消费方共享已解析字段。 + +**在 Definition 里维护每工具字段规格(内建表 + 运行时注册表)。** Definition 是纯函数、拿不到 ctx,运行时注册表只能经工厂闭包注入,三方工具要在坑位与注册表两处登记;而规格表本质只是"哪些字段保留文本",懒计算按需从原始文本切出原文后这条信息不再需要。 + +**第三方 `partial-json`。** 每帧重解析累计输入,且不独立于解码值暴露字符串完成状态。 + +**提前解析所有字段。** `content` 等大字段通常只需要长度;在消费方请求前构造全文,会增加解码和内存成本。观察整个键列表还会在无关参数出现时重新发布块,因此详情保持逐字段观察。 + +**从规范化字段切出截断详情。** V8 的子字符串可能保留整个字段。拼接有界数量的字素簇,使展示前缀不再依赖该原文。 + +**只调 `properties` 顺序。** 实测 0/40 生效;模型跟随 `required` 顺序。 + +## 验证 + +- 解析器测试覆盖懒扫描、已读取答案变化、空键与重复键、跨片转义、非字符串值、非法输入、封存视图,以及不受读取历史影响的结构相等。 +- 工具行和分组测试覆盖流式路径与描述、内容长度、共用行身份、参数不再增长后才回退工具名,以及不改变空白或 Unicode 处理语义的每次截断遍历最多 161 个字素簇。 +- 负控恢复读取时确认变化、重复分配 Match 和短文本字素遍历;四项聚焦测试在对应断言失败,恢复后通过。各 Definition 保留独立 State,Turn 与 Step 记录仍归各 Session 所有。 +- 另有负控提前解析未读容器、接受同长度的冲突文本,或为纯参数 delta 重建 Assistant State。每项都在所属断言失败,恢复边界索引实现后通过。 + +必跑的 [conversation-fold benchmark](../../../../benchmarks/conversation-fold/conversation-fold.bench.client.ts) 使用真实 Tool Definition、Assembler、Chat 分组及发布后的 write 行读取,以 16 字符分片输入,每 64 片 flush。三个独立的已编译 Node worker 报告全部样本及中位数;准备工作和显式 GC 不计时,保留 Assembler 与参数视图后测量驻留堆。行读取校验解码进度与文件路径;worker 还报告进程峰值 RSS。 + +2026-09-29 的本地 Chat 专项测量,以 512 KiB write 内容和 32,769 个正文 delta 对比功能改造前基线与优化实现。在 Linux x64、AMD EPYC 7763、Node 24.18.0 上,无探针处理耗时中位数为 212.16 → 176.12 ms,包含已回收对象的累计分配为 132.06 → 76.65 MiB,驻留堆为 24.89 → 21.84 MiB。测量不含模型、网络、浏览器渲染和非 Chat 目标,不代表整页或 CI runner 延迟。 + +Match 调用保持 393,228 次;update 从 98,307 增至 131,076 次,因为 Tool Definition 消费每个参数 delta。基线按原文长度显示 write 进度,不读取解码后的参数。 + +CI 预算保持 750/375 ms 和 37.5/10 MiB:300/150 ms 参考预期使用共享的 2× 时间倍率和 1.25× 余量,30/8 MiB 堆预期只使用余量。历史累计索引/全文分段负控中,write 耗时为 3984.9–4035.5 ms,bash 为 1471.2–1494.9 ms,均超过未调整的预算。 + +## 影响 + +- 已读取参数变化时替换准备态块,无人读取的调用保留块引用。视图保留源文本,分组标题可以独立于工具行请求字段。已派发和已结算视图在首次读取时扫描,不发布更新。 +- schema 顺序和描述指令引导模型先生成展示标签,但不保证生成顺序。工具行在字段可用时显示它;路径晚到会推迟路径及其后的内容进度展示。 +- 展示仍由 Client 派生;参数扫描不新增 Session 事件或持久化格式。 +- 名字晚于参数到达的流放弃扫描,该调用的准备行退回只显示标题。 diff --git a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.i18n.yaml index 15f69188be..c72fdec87f 100644 --- a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.i18n.yaml @@ -9,8 +9,8 @@ en: 20f659a15fb55352 zh: 49d39c95da1b8aa5 /agent-note-session-log-otlp-strings-and-bounded-requests/decision: - en: 0f0866232326658b - zh: aa608d17ba712d8a + en: 6e18311171c724bd + zh: 0214676ea3ee769c /agent-note-session-log-otlp-strings-and-bounded-requests/alternatives-considered: en: 0dc933b18a1a3573 zh: 77655740322c4e45 diff --git a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.md b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.md index 57fbe72dc5..686df954fd 100644 --- a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.md +++ b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.md @@ -14,7 +14,7 @@ The shared `otel` Cordis plugin owns ordinary-event and Session-log transport im Session logs use a separate provider and byte/count queue. Each record is measured once with the SDK serializer including a full envelope; the sum conservatively bounds a combined request. Greedy packing avoids full-candidate serialization and recursive re-encoding. Single oversized records receive one diagnostic and do not block later events. One HTTP request owns the transport slot until settlement, even after its watchdog fires; shutdown expiry stops the remaining queue. Scope name/version and explicit transport options remain owned by the backend. -The [telemetry revival](../feature/2026-07-23-session-telemetry-otel-revival.md) remains authoritative for redaction, capture, and best-effort handoff. This decision replaces its SDK-only queue/batching rule: SDK batch deadlines cannot own an operation split into multiple HTTP requests. The shared Session-log channel schedules complete requests; the SDK still owns transport/retry. Feedback authorization and the separate DeepSeek model-request contribution remain unchanged. +The [telemetry revival](../../../../packages/session/session-telemetry/README.md) remains authoritative for redaction, capture, and best-effort handoff. This decision replaces the earlier SDK-only queue/batching rule: SDK batch deadlines cannot own an operation split into multiple HTTP requests. The shared Session-log channel schedules complete requests; the SDK still owns transport/retry. Feedback authorization and the separate DeepSeek model-request contribution remain unchanged. ## Alternatives considered diff --git a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.zh.md b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.zh.md index f6e8306978..947f5cf129 100644 --- a/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-25-session-log-otel-byte-limits.zh.md @@ -14,7 +14,7 @@ Session 日志包含嵌套 JSON 和大型工具结果。产品采集端接收带 Session 日志使用独立 provider 和字节/条数队列。每条记录使用 SDK 序列化器计量一次,包含完整 envelope;其总和保守约束合并请求大小。顺序组包避免候选批次的完整序列化和递归重复编码。单条超限记录只产生一次诊断,不阻塞后续事件。每个 HTTP 请求在结束前始终占有传输槽位,即使其监测期限已经触发;关闭期限到期后停止剩余队列。scope 名称/版本和显式传输选项仍由后端拥有。 -[遥测恢复决策](../feature/2026-07-23-session-telemetry-otel-revival.zh.md) 继续约束脱敏、捕获和尽力交接。本决策替代其仅使用 SDK 队列/聚合的规则:SDK 批次期限无法拥有拆成多个 HTTP 请求的操作。共享 Session 日志通道调度完整请求,SDK 仍负责传输和重试。反馈授权和独立的 DeepSeek 模型请求附加路径不变。 +[遥测恢复参考](../../../../packages/session/session-telemetry/README.zh.md) 继续约束脱敏、捕获和尽力交接。本决策替代先前仅使用 SDK 队列/聚合的规则:SDK 批次期限无法拥有拆成多个 HTTP 请求的操作。共享 Session 日志通道调度完整请求,SDK 仍负责传输和重试。反馈授权和独立的 DeepSeek 模型请求附加路径不变。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.i18n.yaml new file mode 100644 index 0000000000..0187f43093 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.i18n.yaml @@ -0,0 +1,37 @@ +# Bilingual-pair consistency record for 2026-09-30-profile-package-refresh-and-manifest-invalidation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration: + en: ea66719b3dcdd21c + zh: 893364551594ddaf +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/problem: + en: e59e3f5c61158701 + zh: 2ced78e3c84f604e +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision: + en: a15c7c13f167e5ac + zh: a15c7c13f167e5ac +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision/responsibilities: + en: 3ffd0c78fa204b04 + zh: f468bab00a7ff357 +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision/runtime-resolution: + en: ca45b973e1968533 + zh: 002f23bb70e4fed7 +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision/plugin-manager: + en: 9b04926c175f22e8 + zh: fbf05dd4065b0738 +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision/hmr-package-configuration-expiry: + en: c7de8dafe47a5587 + zh: 6010a70d8451edd1 +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/decision/future-work: + en: 31818f8f7f80470b + zh: ecb7ac85bb49cf6d +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/alternatives-considered: + en: 7c1c6d21570cda0d + zh: e6f8334c85f1b404 +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/verification: + en: 939cedceed200cb2 + zh: c7bb02d0da400c8d +/agent-note-refresh-runtime-resolution-after-package-operations-and-expire-hmr-package-configuration/consequences: + en: 1b9e31b4e773fac5 + zh: c140ce07fc64bd55 diff --git a/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md new file mode 100644 index 0000000000..b1b86be199 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md @@ -0,0 +1,119 @@ +# Agent Note: Refresh runtime resolution after package operations and expire HMR package configuration + +Status: implemented + +English | [中文](2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md) + +## Problem + +Two pieces of Host state did not follow changes while the process ran: + +- **Runtime resolution was built only at startup.** After a GUI install or enablement, nothing published the latest generation, so a bundle's private dependencies never reached the profile fallback and its bare plugins could not be found. After disablement or removal, a new generation could not remove profile-scoped entries or profile-local names, so stale records and package metadata remained. A newly installed package directory is new, and Node holds no stale cache for it; only the table was missing. +- **Package configuration and module evaluation have separate caches.** Clearing a plugin's module cache does not refresh the package.json fields that Node uses for exports, main, imports, and format detection. A package.json can also be imported as a JSON module, in which case its consumers need ordinary module reloads. + +## Decision + +### Responsibilities + +| Owner | Does | Does not | +|---|---|---| +| resolver (`app-boot/src/profile-resolution/resolver.ts`) | `replace()` allows removing `scope: profile` entries and profile-local names | Touch any Node cache | +| plugin-manager | Calls `pluginPackages.refresh()` at fixed points of package operations | Construct resolutions or manage module and package-configuration caches | +| HMR (`packages/boot/hmr`) | Expires package configuration and preserves ordinary reloads for manifests loaded as JSON modules | Reload plugins for configuration-only manifests; handle `node_modules` package replacement | + +### Runtime resolution + +The [generation Note](2026-09-09-profile-resolution-generations.md) owns package identity and Worker inheritance rules. + +Validation of a new generation in `replace()`: + +- `scope: profile` entries may be removed. +- Profile-local package names may be removed. +- Retained profile entries keep their normalized directory, version, and scope, but may change declaring anchor when another selected bundle supplies the same package. Installation mappings and declaring anchors remain unchanged. The profile scope cannot change; a new local name cannot override an existing entry; a published link name cannot select another real directory. + +### Plugin Manager + +| Operation | Publication point | +|---|---| +| Install a new package | After pnpm succeeds and the bundle selection is saved, before reconciliation, whether or not the bundle is enabled | +| Overwrite an installed package | No publication; the result is `restart-required` | +| Enable a bundle | After the selection is saved, before reconciliation | +| Disable a bundle | With HMR, after reconciliation stops its plugins; without HMR, no publication, and running plugins keep the current table | +| Remove | After pnpm remove succeeds, inside the HMR transaction | +| Failed or cancelled install, failed removal | No publication | + +`createRuntimeResolution()` returns a `ProfileRuntimeResolution`, which privately keeps the installation anchor, Harness home, and profile directory it was computed from. Its `computeLatestResolution()` rereads that profile with the same inputs and returns a new object for the latest generation, leaving the original unchanged; Workers receive only the table fields. `PluginPackages.refresh()` calls the current resolution's `computeLatestResolution()` and then `replace()`; a resolution constructed as plain data cannot be refreshed and makes the call throw. + +Without HMR, deselecting a startup bundle does not stop its plugins. While such a bundle remains deselected, later package operations keep the existing runtime table instead of publishing its removal. Other no-HMR installations and removals of non-running bundles still publish normally. + +A successful package operation is not rolled back when publication is rejected afterwards. The result reports the failed runtime application and retains the successful disk changes. Recomputing uses the captured profile directory on disk, not synthetic in-memory layers; a computed resolution without a profile recomputes installation packages only. + +### HMR package-configuration expiry + +In HMR's change dispatch, a changed file named `package.json` outside `node_modules` goes to `PackageManifests.invalidate()`. A manifest in the host dependency graph still requests a host reload; one loaded as a JSON module still reloads its consumers. A configuration-only manifest schedules no module reload. Configuration-owned paths stay with their dedicated watcher. Source changes in the same batch reload afterwards. + +`invalidate(manifest)` records the directory as expired. Afterwards: + +- package.json reads, scope lookups, type lookups, and nearest-manifest lookups below that directory read the current file. Ownership follows the real directory when a consumer reaches the package through a link. +- ESM `ResolveCache` is cleared. Its entries do not record every consulted manifest, and a package entry may resolve outside the package directory. +- CommonJS `_pathCache` is cleared for the same reason. Unrelated requests recompute their resolution without unloading their modules. +- CommonJS loads using the default resolver pass the freshly resolved filename to the native loader. Its private request alias cannot select an older entry; cached module instances remain intact. + +Configuration invalidation alone leaves loaded modules unchanged. Loader entries retain their raw import results before export normalization. HMR matches imported Node module objects to cached ModuleJob namespaces and reloads the loaded URLs. All matching entry records, including disabled entries, update only after a successful reload and remain unchanged on failure. Stopping and restarting a Loader entry resolves its package name again and selects the new entry. + +Entry names are scoped by configuration-tree base URL and retain all distinct imported namespaces. An uninitialized entry contributes the name without erasing loaded namespaces; name-based resolution is used only when none is recorded, or for `cordis:` builtins. Dependency analysis considers every recorded module. Entries sharing a plugin runtime use one replacement operation, while each Loader entry selects its replacement by its original namespace. Module imports and export normalization finish before the old runtime is removed. Failed imports or activation restore the previous modules and plugin implementations and clean up partially activated replacements. + +An instance without a Loader entry has no recorded module identity. If a shared runtime's replacement modules have different plugin callbacks, HMR reports an ambiguity error and rolls back instead of assigning that instance to an arbitrary module. + +Package invalidation lives in `packages/boot/hmr/src/package-manifest.ts`, which encapsulates its Node internal interfaces. `index.ts` dispatches manifest changes and locates loaded entries by module identity. HMR's `node_modules` exclusion is unchanged. + +| Interface | Action | +|---|---| +| modules binding `readPackageJSON`, `getPackageScopeConfig`, `getPackageType` | Replaced: paths in an expired directory read the current file; other calls reach the native method | +| `package_json_reader.getNearestParentPackageJSON` | Replaced: expired directories bypass its JS cache; a lookup without a manifest returns the native absent result | +| The ESM Loader's `ResolveCache` instance | The prototype `get` is replaced for one lookup to obtain the instance and restored at once; the resolution cache is then cleared | +| CommonJS `Module._pathCache` | The request-to-filename cache is cleared | +| CommonJS `Module._load` | Default-resolver requests load by resolved filename; builtins and registered resolve hooks keep the original path | + +The binding's `getNearestParentPackageJSON` is called only by `package_json_reader` and is not replaced. Each HMR instance owns a separate configuration cache. Its replacements are installed at the first expiry and restored when the service is disposed, alongside watcher and reload-queue cleanup. + +### Future Work + +- Online replacement inside `node_modules`, same-path reinstall, changed link targets, and cross-package reload propagation remain unsupported. Package updates need process restart; this does not guarantee that every management result already reports that requirement correctly. +- TSX versions using an asynchronous loader thread keep that thread's package configuration outside these hooks. Synchronizing it is deferred; this change does not provide general Worker cache synchronization. +- CommonJS private-request-cache refresh with registered synchronous resolve hooks is deferred. Those requests retain their original loader behavior. +- Disposing HMR restores the native readers, whose previous cached configuration can become visible again. Preserving invalidation state across HMR replacement is deferred. + +## Alternatives considered + +**Read current disk on every lookup (#4703).** Resolution follows the disk, but module caches stay and running plugins do not reload. Same-URL contents do not take effect, and different URLs load a second version beside the first in one process. It also maintains its own entry resolver, bypasses custom bare-name hooks, and does not refresh runtime resolution, so GUI-installed private dependencies still cannot be found. + +**Reread every observed manifest when runtime resolution publishes (#5496).** This places package-configuration expiry in table publication, decoupled from module caches: overwriting an installed package reports restart, yet the next unrelated publication makes new imports load the new version while the old plugin still runs. Package configuration is content and belongs with module caches, in HMR. + +**Let HMR reload packages inside `node_modules`.** It requires finding affected plugins over a module graph that includes `node_modules`, preventing duplicate evaluation of shared libraries and Cordis, and handling missing dynamic-import edges, module side effects, and Workers. It is not done here; those scenarios keep requiring restart. + +**Reload every plugin for a package.json change.** Configuration-only manifests do not require module evaluation. Manifests actually imported as JSON modules retain ordinary dependency-driven reloads. + +**Re-import reloaded plugins by package name.** It would let an entry rename follow a source reload, but changes `partialReload`'s rule that the loaded URL is the reload unit. Entry renames already take effect when the Loader entry restarts, so this is not changed. + +**Keep one namespace per entry name.** Entries with the same name and base URL can have loaded different modules before and after a manifest change. Keeping only the first or last namespace loses a live module; an uninitialized entry must not erase another entry's imported namespace. + +## Verification + +| Coverage | Location | +|---|---| +| A new generation removes profile entries and local names and still rejects retained-entry changes | `packages/boot/app-boot/tests/profile-resolution.spec.ts` | +| GUI install, deferred enablement, disablement, removal, overwrite, failure, and cancellation, with and without HMR | `packages/boot/plugin-manager/tests/package-reload.spec.ts` | +| Captured-directory recomputation, computed installation-only refresh, and plain-data rejection | `packages/boot/app-boot/tests/profile-resolution.spec.ts`, `packages/boot/app-boot/tests/profile-resolution-service.spec.ts` | +| Sequential no-HMR operations, shared dependency declarers, and disk-success/publication-failure outcomes | `packages/boot/plugin-manager/tests/package-reload.spec.ts` | +| Expired exports, main, imports, type, scope, and nearest manifests; native-reader parity; actual CommonJS loads and retained module instances; the `node_modules` boundary; restoration | `packages/boot/hmr/tests/package-manifest.spec.ts` | +| Configuration-only manifests, JSON-module and host reloads, source reloads, same-name and shared-runtime entries, import and activation rollback, entry restarts, and the `node_modules` exclusion | `packages/boot/hmr/tests/package-manifest-dispatch.spec.ts` | + +Tests need no API key and make no model calls. + +## Consequences + +- With HMR, supported package operations publish the updated private dependencies and remove unused mappings after plugins stop. Without HMR, a pending startup-bundle disablement defers later publications until restart, preserving running plugins' mappings. +- In the supported thread, package.json configuration reads use the changed fields; JSON modules retain their own reload behavior. The separate module, loader-thread, and Worker limitations above still apply. +- Package configuration below expired directories is parsed in JavaScript and must keep the native reader's field and error semantics; tests compare it with the native reader on Node 22, 24, and 26. +- The implementation depends on several Node internal interfaces, and these tests must run again for Node upgrades. diff --git a/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md new file mode 100644 index 0000000000..9ee201f27a --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md @@ -0,0 +1,119 @@ +# Agent Note: 包操作刷新 runtime resolution 与 HMR 包配置失效 + +Status: implemented + +[English](2026-09-30-profile-package-refresh-and-manifest-invalidation.md) | 中文 + +## Problem + +Host 运行期间有两处状态没有随变化更新: + +- **runtime resolution 只在启动时构造。** GUI 安装或启用 bundle 后,没有人发布最新代,bundle 的私有依赖进不了 profile fallback,它的 bare 插件找不到。停用、卸载后,新一代又不允许移除 profile 范围的条目和本地包名,旧记录和包元数据一直留着。新安装的包目录本身是新的,Node 对它没有旧缓存,缺的只是这张表。 +- **包配置与模块求值使用独立缓存。** 清理插件的模块缓存不会刷新 Node 用于 exports、main、imports 和格式判断的 package.json 字段。package.json 也可以被作为 JSON 模块导入,此时其消费方需要普通的模块重载。 + +## Decision + +### 职责 + +| 负责方 | 做什么 | 不做什么 | +|---|---|---| +| resolver(`app-boot/src/profile-resolution/resolver.ts`) | `replace()` 允许移除 `scope: profile` 条目和 profile 本地包名 | 不碰 Node 的任何缓存 | +| plugin-manager | 在包操作的确定时点调用 `pluginPackages.refresh()` | 不构造 resolution;不管模块缓存与包配置缓存 | +| HMR(`packages/boot/hmr`) | 使包配置失效,并保留作为 JSON 模块加载的 manifest 的普通重载 | 不因仅作为配置的 manifest 重载插件;不处理 `node_modules` 包替换 | + +### runtime resolution + +包身份与 Worker 继承规则由[代际说明](2026-09-09-profile-resolution-generations.zh.md)定义。 + +`replace()` 对新一代的校验: + +- `scope: profile` 的条目可以移除。 +- profile 本地包名可以移除。 +- 保留的 profile 条目保持规范化目录、版本与作用域不变;另一个已选 bundle 提供同一包时,可以更换声明锚点。安装范围的映射与声明锚点保持不变。profile 范围不能变;新本地包名不能覆盖已有条目;已发布的 link 名称不能换真实目录。 + +### plugin-manager + +| 操作 | 发布时点 | +|---|---| +| 安装新包 | pnpm 成功、保存 bundle 选择之后,reconciliation 之前;无论是否立即启用都发布 | +| 覆盖已安装的包 | 不发布,结果为 `restart-required` | +| 启用 bundle | 保存选择后发布,再做 reconciliation | +| 停用 bundle | 有 HMR 时,reconciliation 让插件退出后再发布;没有 HMR 时不发布,运行中的插件继续使用原表 | +| 卸载 | pnpm remove 成功后,在 HMR 事务内发布 | +| 安装失败、取消、卸载失败 | 不发布 | + +`createRuntimeResolution()` 返回 `ProfileRuntimeResolution`,它私有保存构造时的 installAnchor、home 和 profile 目录;`computeLatestResolution()` 用同一组输入重新读取 profile,返回表示最新代的新对象,原实例不变;Worker 只收到表数据。`PluginPackages.refresh()` 调用当前 resolution 的 `computeLatestResolution()` 再 `replace()`;以纯数据构造的 resolution 无法刷新,调用时抛错。 + +没有 HMR 时,取消启动 bundle 的选择不会停止它的插件。只要该 bundle 仍未选中,后续包操作就保留当前 runtime 表,不发布其移除。其他无 HMR 的安装和未运行 bundle 卸载仍正常发布。 + +包操作成功后若发布被拒绝,不回滚已完成的包操作。结果报告运行时应用失败,并保留成功的磁盘变更。重新计算使用捕获的磁盘 profile 目录,不继承内存合成 layers;没有 profile 的已计算 resolution 只重算安装范围的包。 + +### HMR 包配置失效 + +HMR 的文件变化分派中,文件名为 `package.json` 且不在 `node_modules` 内的变化,交给 `PackageManifests.invalidate()`。位于宿主依赖图中的 manifest 仍请求宿主重载;作为 JSON 模块加载的 manifest 仍重载消费方。仅作为配置的 manifest 不安排模块重载。配置专属路径仍由其独立 watcher 处理。同一批的源码变化随后重载。 + +`invalidate(manifest)` 把该目录登记为已失效,之后: + +- 位于该目录下的 package.json 读取、scope 查询、type 查询、最近 package.json 查询,都按当前磁盘解析。消费方经由链接路径访问时,按真实目录判断归属。 +- 清空 ESM `ResolveCache`。其条目不记录查询过的全部 manifest,包入口也可能解析到包目录之外。 +- 基于相同原因清空 CommonJS `_pathCache`。无关请求重新计算解析结果,不卸载其模块。 +- 使用默认 resolver 的 CommonJS 加载把重新解析的文件名交给原生 loader。其私有请求别名不再选中旧入口,已缓存的模块实例保持不变。 + +仅使配置失效不会改变已加载模块。Loader entry 保留导出规范化之前的原始导入结果。HMR 将已导入的 Node 模块对象与缓存中 ModuleJob 的模块命名空间按对象身份匹配,重载已加载 URL。所有匹配的 entry 记录,包括已停用的 entry,仅在重载成功后更新,失败时保持不变。停止并重新启动 Loader entry 时,会重新按包名解析并选择新入口。 + +entry 名称以配置树 base URL 为作用域,并保留所有不同的已导入命名空间。尚未初始化的 entry 只登记名称,不抹去已加载的命名空间;仅在没有记录命名空间或入口为 `cordis:` 内置模块时按名称解析。依赖分析考虑每个已记录模块。共享插件运行时的 entry 使用同一次替换操作,但每个 Loader entry 按自身原命名空间选择替换实现。模块导入与导出规范化在移除旧运行时之前完成。导入或激活失败时,恢复原来的模块和插件实现,并清理已部分激活的替换实例。 + +没有 Loader entry 的实例不具备已记录的模块身份。如果共享运行时的替换模块具有不同的插件回调,HMR 会报告歧义错误并回滚,而不是将该实例随意分配给某个模块。 + +包配置失效实现在 `packages/boot/hmr/src/package-manifest.ts`,封装其使用的 Node internal 接口。`index.ts` 分派 manifest 变更,并按模块身份定位已加载 entry。HMR 的 `node_modules` 排除规则不变。 + +| 接口 | 动作 | +|---|---| +| modules binding 的 `readPackageJSON`、`getPackageScopeConfig`、`getPackageType` | 替换:路径位于已失效目录时按当前磁盘解析,其余调用原生方法 | +| `package_json_reader.getNearestParentPackageJSON` | 替换:已失效目录绕过它的 JS 缓存;没有 manifest 时返回原生的缺失结果 | +| ESM Loader 的 `ResolveCache` 实例 | 临时替换原型的 `get`,取得实例后立即恢复,再清空解析缓存 | +| CJS `Module._pathCache` | 清空请求到文件名的缓存 | +| CJS `Module._load` | 默认 resolver 的请求按解析后的文件名加载;内置模块及注册的 resolve hooks 保留原路径 | + +binding 的 `getNearestParentPackageJSON` 只被 `package_json_reader` 自己调用,不替换。每个 HMR 实例拥有独立的配置缓存,在第一次失效时安装 hook,并在服务销毁时恢复,同时清理 watcher 和重载队列。 + +### 后续工作 + +- 不支持 `node_modules` 内在线替换、同路径重装、link 换目标及跨包重载传播。包更新需要重启进程;这不保证每个管理操作的结果都已正确报告这一要求。 +- 使用异步 loader 线程的 TSX 版本,其线程内包配置不受这些 hook 管理。线程同步留待后续;本次不提供通用 Worker 缓存同步。 +- 注册了同步 resolve hooks 时,CommonJS 私有请求缓存刷新留待后续;这些请求保留原有 loader 行为。 +- 销毁 HMR 会恢复原生 reader,其此前缓存的配置可能重新可见。跨 HMR 替换保留失效状态留待后续。 + +## Alternatives considered + +**每次查找都读当前磁盘(#4703)。** 解析能跟上磁盘,但不清模块缓存,也不重载运行中的插件。同一 URL 时新内容不生效,不同 URL 时新旧版本在同一进程并存。它还要维护一份入口解析实现,并且绕过自定义 bare-name hook。它也没有刷新 runtime resolution,GUI 安装的私有依赖依然找不到。 + +**在 runtime resolution 发布时全量重读已见过的 manifest(#5496)。** 它把包配置失效放进了绑定表发布,和模块缓存脱了钩:覆盖已安装的包时报需要重启,但下一次无关的发布会让新导入拿到新版本,旧插件仍在运行。包配置缓存属于内容,应该和模块缓存由同一方负责,也就是 HMR。 + +**让 HMR 支持 `node_modules` 内的包重载。** 这需要沿包含 `node_modules` 的模块图确定受影响插件,防止共享库和 Cordis 被重复求值,还要处理动态 import 漏边、模块副作用和 Worker。本次不做,这些场景继续要求重启。 + +**package.json 变化时重载所有插件。** 仅作为配置的 manifest 不要求模块求值。实际作为 JSON 模块导入的 manifest 保留普通的依赖驱动重载。 + +**按插件名重新 import 被重载的插件。** 这能让入口改名随源码重载生效,但会改变 `partialReload` 以已加载 URL 为重载单位的规则。入口改名在 Loader entry 重启时已经生效,所以本次不改。 + +**每个 entry 名称只保留一个命名空间。** 名称和 base URL 相同的 entry,可能在 manifest 变化前后加载了不同模块。只保留最早或最后一个命名空间会遗漏仍在使用的模块;尚未初始化的 entry 也不能抹去其他 entry 的已导入命名空间。 + +## Verification + +| 覆盖面 | 位置 | +|---|---| +| 新一代可移除 profile 条目与本地包名、仍拒绝修改保留条目 | `packages/boot/app-boot/tests/profile-resolution.spec.ts` | +| GUI 安装、延迟启用、停用、卸载、覆盖、失败、取消,有无 HMR | `packages/boot/plugin-manager/tests/package-reload.spec.ts` | +| 捕获目录后的重新计算、无 profile 的已计算 resolution 刷新、纯数据拒绝 | `packages/boot/app-boot/tests/profile-resolution.spec.ts`、`packages/boot/app-boot/tests/profile-resolution-service.spec.ts` | +| 无 HMR 连续操作、共享依赖声明来源、磁盘成功但发布失败 | `packages/boot/plugin-manager/tests/package-reload.spec.ts` | +| 失效后的 exports、main、imports、type、scope、最近 package.json,原生 reader 对照,实际 CommonJS 加载及旧模块实例保留,node_modules 边界,恢复 | `packages/boot/hmr/tests/package-manifest.spec.ts` | +| 配置专用 manifest、JSON 模块与宿主重载、源码重载、同名及共享运行时的 entry、导入与激活失败回滚、entry 重启和 node_modules 排除规则 | `packages/boot/hmr/tests/package-manifest-dispatch.spec.ts` | + +测试不需要 API key,不调用模型。 + +## Consequences + +- 有 HMR 时,支持的包操作发布更新后的私有依赖,并在插件停止后移除不再需要的映射。没有 HMR 时,待生效的启动 bundle 停用会将后续发布延迟到重启,保留运行中插件的映射。 +- 在支持的线程内,package.json 配置读取使用更新后的字段;JSON 模块保留自己的重载行为。上述模块、loader 线程及 Worker 的独立限制仍然适用。 +- 包配置读取改由 JS 解析 package.json,需要与原生读取保持字段和错误语义一致;测试在 Node 22、24、26 上与原生读取逐项比较。 +- 依赖多个 Node internal 接口,Node 升级时需要重跑这些测试。 diff --git a/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.i18n.yaml new file mode 100644 index 0000000000..16a39c6917 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.i18n.yaml @@ -0,0 +1,34 @@ +# Bilingual-pair consistency record for 2026-09-30-structured-draft-initialization.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.md +/agent-note-initial-draft-content-and-structured-reference-restoration: + en: 88202480bcf49e10 + zh: 3f2aff8ad2be0faa +/agent-note-initial-draft-content-and-structured-reference-restoration/problem: + en: 6aa396e12264ee48 + zh: 56849a18acaf830a +/agent-note-initial-draft-content-and-structured-reference-restoration/decision: + en: 9cf2afd9d5e663e9 + zh: ad59b1bec6ec4666 +/agent-note-initial-draft-content-and-structured-reference-restoration/decision/data-model: + en: 7f33895364a9f5cc + zh: f1c79ea15f5c151e +/agent-note-initial-draft-content-and-structured-reference-restoration/decision/classes-and-methods: + en: ca81423a57f898a0 + zh: 98962cc1c6bce113 +/agent-note-initial-draft-content-and-structured-reference-restoration/decision/data-flow: + en: dd57e639e9328f1d + zh: 5e16cb2e3a73597c +/agent-note-initial-draft-content-and-structured-reference-restoration/decision/asynchronous-skill-matching: + en: 3b6a99c417384470 + zh: b62951995eda2fb0 +/agent-note-initial-draft-content-and-structured-reference-restoration/alternatives-considered: + en: eddaec471425fafc + zh: b206ecdb12e8c33d +/agent-note-initial-draft-content-and-structured-reference-restoration/verification: + en: 341f4f36936f686a + zh: 479fbe04e810f640 +/agent-note-initial-draft-content-and-structured-reference-restoration/consequences: + en: ff4f9527db80f790 + zh: f3e57093c058f643 diff --git a/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.md b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.md new file mode 100644 index 0000000000..678129340f --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.md @@ -0,0 +1,88 @@ +# Agent Note: Initial draft content and structured reference restoration + +Status: implemented + +English | [中文](2026-09-30-structured-draft-initialization.zh.md) + +## Problem + +New-task entry points need to place unsent content in the composer. A draft contains not only text but also file, folder, and Session references the user has selected. Saving only their text projection loses the chips' source, target, and presentation fields when the input object is disposed. Filling the composer after mounting also cannot guarantee that the first input-model read contains the complete document. + +## Decision + +Keep the existing `SessionInputShell`, `DraftEditorRuntime`, Conversation store, and Session reference lifetime. Plain text and structured drafts enter through the input model. Initial import finishes before `InputHub.shellFor()` returns, without React effects restoring text. Existing input objects are not restored again. React mounts the same editor and binds the existing persistence callback. + +The second parameter of `startSession(workspaceId?, options?)` contains `prompt` and `clearPreviousDraft`. `prompt` accepts only a string; structured drafts remain internal to restoration and Workspace transfer. Omitting options preserves the existing flow. Without clearing, existing content is preserved and only an empty draft adopts the prompt. Explicit clearing replaces the target Session's text document, without deleting attachments or changing other Sessions. Prefilling does not send, execute commands, or change `blank`. + +### Data model + +`DraftSnapshot` contains `text` and `references`. Each reference retains `offset`, `length`, `source`, `ref`, `label`, `appearance`, `clipboardText`, and its existing invalid flag. Positions use JavaScript string coordinates. Reference spans must be ordered, non-overlapping, and equal the corresponding `clipboardText`. Snapshots exclude Lexical NodeKeys, DOM, runtime occurrenceIds, functions, and temporary highlights. + +A string represents input without structured references; an ambiguous `@` query never selects a file automatically. The public `SessionInput.setDraft` and `InputActions.setDraft` methods accept only text. Internal structured restoration reuses the editor's reference restoration without listing directories or reading files. Plain text and file, folder, and Session chips all pass through the registered editor transforms and projection. + +The existing `dsh.conversation.` key remains. Readers accept both legacy strings and structured `draft` values; new writes store structured values. The original Conversation store still writes the complete record, without a second writer for that key. Reference changes must reach persistence even when the visible text does not change. + +### Classes and methods + +| Location | Change and responsibility | +| --- | --- | +| `UiWorkspace` / `UiWorkspaceService.startSession` | The second parameter is `StartSessionOptions`; the existing `beforeOpen` preparation point passes initialization to the exact target binding. Creation, reuse, and navigation cancellation remain unchanged | +| `SessionInputResolver` / `InputHub.requestDraftInitialization` | Accept the binding and options and delegate to that binding's input object, without looking up a global current Session | +| `InputHub.shellFor` | After registering a new object and its cleanup, read the Session's saved draft, import it through the input model, and return. Reused objects are not restored again | +| `readConversationDraft(sessionId)` in `stores.ts` / `parseStoredDraft` in `draft.ts` | Read the original storage key, accept legacy strings, and check structured reference fields and positions without creating a store or another writer | +| `SessionInputShell.setDraft` | The concrete internal method accepts a string or `DraftSnapshot` and imports one document through the editor; public input methods accept only text. Neither path simulates keyboard events or submits | +| `SessionInputShell.requestDraftInitialization` | Apply clear, preserve, and prefill rules to the restored model; refuse changes during submission without clearing first | +| `SessionInputShell.draftSnapshot` / `persistCurrentDraft()` | Cache snapshots without runtime IDs by content revision; `InputActions.persistDraft()` delegates explicit saving, while `bindDraftPersistence(write)` only binds the callback | +| `DraftEditorRuntime.restoreDraft` | Rebuild ReferenceChipNodes from semantic reference fields; existing failed-send restoration uses the same implementation | +| `SessionInputShell.refreshLexiconSubscription()` / `DraftEditorRuntime.refreshLexiconSubscription()` | `InputHub` owns the Session-scope `inject` callback that connects the lexicon after caching the shell, reconnects when the service arrives or is replaced, and unsubscribes on disposal. Both connection and catalog updates rescan the current editor without writing old text back; `setDraft` does not establish the subscription | +| `ConversationStoreState` / `createConversationStore` | Support structured input and legacy strings in draft storage, keeping View state and the original key | +| `DefaultConversationViews` | Remove restoration and prefill effects, retaining persistence binding and unbinding. Initial saving reads the owner's current snapshot, never text captured during render | +| The existing `selectWorkspace` callback in `apply.ts` | Keep the existing cross-Workspace draft-transfer rules, passing `draftSnapshot` instead of plain text so transfer retains references | + +There is no new draft service, storage key, general task framework, or Session creation entry point. SlotRenderer, Host Session logs, command execution, and file-discovery RPCs are unchanged. `InputActions` remains the shared input-action entry; components receive no separate content-initialization callback. + +The call parameters are `startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions)`, `requestDraftInitialization(binding: SessionBinding, options: DraftInitializationOptions)`, `SessionInputShell.requestDraftInitialization(options)`, and the internal `SessionInputShell.setDraft(input: string | DraftSnapshot)`. `StartSessionOptions` aliases `DraftInitializationOptions`, whose fields are `prompt?: string` and `clearPreviousDraft?: boolean`. A shallow copy captures these values before asynchronous creation, and the result addresses only the retained target binding. Initialization distinguishes applied, preserved, and submission-blocked outcomes. Refusal does not clear first; superseded navigation does not write a draft. + +### Data flow + +```text +stored draft / startSession options + → DraftInput + → SessionInputShell.setDraft + → DraftEditorRuntime → Lexical → InputState + → InputHub.shellFor returns + → React mounts the editor + +user edits / input actions + → editor → projection + → DraftSnapshot + → Conversation store +``` + +### Asynchronous skill matching + +Skills remain editable text, not file chips. A pending lexicon never locks the draft. Slash names remain ordinary text until a catalog supplies a matching name; an absent provider, unavailable catalog, or unmatched name produces no skill highlight. The existing subscription rescans the current document on arrival instead of saving and restoring the text present when the request began. This changes neither text nor structured references, does not take the caret, and adds no user Undo step. Commands retain their existing validation and execution path at submission. + +## Alternatives considered + +**Calling setDraft after mounting.** The first model read and render can still be empty, and later restoration can overwrite a clear. Initialization cannot depend on React-effect ordering. + +**Saving only clipboardText and searching references again.** Text does not identify a reference's source and presentation fields. Searching candidates does not restore the user's previous selection; existing structure should be restored directly. + +**Waiting for all remote lexicons before allowing input.** File references already have their data, and skill highlighting is not an editing prerequisite. Blocking the draft on those queries adds unnecessary waiting. + +**Splitting persistence keys or adding a draft-management service.** The existing input owner and store can perform the work by sharing the input representation and import path. + +## Verification + +The core model invariants are semantic equivalence after import, export, and JSON round trips without comparing runtime node IDs; a complete document on the first input-object read; and stable draft snapshots across selection or skill-highlight changes. [Document-model cases](../../../../packages/client/ui-conversation/tests/draft-document.client.spec.ts) cover strings, empty drafts, multiline Unicode text, file/folder/Session references, duplicates, invalid flags, placeholder sanitization, and invalid stored spans. [Input-lifecycle cases](../../../../packages/client/ui-conversation/tests/draft-hub.client.spec.ts) cover first reads, reuse, lexicon-service arrival, unloading, reconnection, and retired bindings. Initialization is refused during submission, and lexicon rescans add no Undo step. + +[Navigation cases](../../../../packages/client/ui-workspace/tests/workspaces-service.client.spec.ts) check exact retained targets, option copying before asynchronous creation, superseded requests without draft writes, and refusal without changing selection. [Assembly cases](../../../../packages/client/ui-conversation/tests/apply-inject.client.spec.tsx) check that existing Workspace transfer retains structured references. The related regression run passed 708 cases in 44 files; after the shared draft module moved to its owning location, the three affected model and assembly files passed another 66 cases. + +The [browser e2e](../../../../apps/web/tests/draft-initialization.e2e.ts) observes the shipped Workspace API through the existing module-loader test instrumentation and calls public `startSession` with text options, without an additional test plugin. Structured fixtures enter through the existing saved-draft restoration path. The cases cover absent, empty, and nonempty prompts; explicit and inherited Workspaces; preservation and clearing; creation and reuse; repeated real-UI Session switching and editing; reload; legacy strings; and Workspace-picker transfer. Both orders of delayed creation responses preserve the newer target and leave the other draft unchanged. A delayed real skill-catalog response rematches current text without replacing chips, selection, or Undo history. Assertions inspect chip fields, file previews, and persisted snapshots. Before sending, Host logs contain neither `user/message` nor `turn/start`. + +## Consequences + +Missing reference information in legacy strings cannot be reconstructed losslessly, so they restore as plain text without searching and guessing files. Attachment File objects, upload state, cross-browser synchronization, multiple independent editors, and persistent Undo history are outside this change. + +This decision shares input ownership with the [editor-isolation proposal](../../proposed/architecture/2026-09-14-composer-model-and-draft-editor.md) without implementing its multi-editor goal. [Reference previews](../../../../packages/client/ui-input-trigger/README.md) and [Session reference lifetimes](2026-09-15-client-session-references.md) continue to govern source routing and generation cleanup independently; neither is superseded. diff --git a/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.zh.md b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.zh.md new file mode 100644 index 0000000000..07c185a969 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-09-30-structured-draft-initialization.zh.md @@ -0,0 +1,88 @@ +# Agent Note: 草稿初始内容与结构化引用恢复 + +Status: implemented + +[English](2026-09-30-structured-draft-initialization.md) | 中文 + +## 问题 + +新任务入口需要把一段尚未发送的内容放入输入框。草稿不仅包含文字,也包含用户已经选定的文件、目录和 Session 引用;只保存引用的文字投影,会在输入对象销毁后丢失胶囊的来源、目标和展示字段。页面挂载后的填字也不能保证第一次读取输入模型时已经得到完整内容。 + +## 决策 + +保留现有 `SessionInputShell`、`DraftEditorRuntime`、Conversation store 和 Session 引用生命周期。普通文字与结构化草稿统一经过输入模型导入;初始导入在 `InputHub.shellFor()` 返回对象前完成,不由 React effect 恢复文字。已存在的输入对象不重新恢复。React 只挂载同一编辑器,并绑定现有持久化回调。 + +`startSession(workspaceId?, options?)` 的第二参数保留 `prompt` 与 `clearPreviousDraft`。`prompt` 仅接受字符串;结构化草稿只用于内部恢复和工作区携带。无选项时维持原流程。不清空时保留已有内容,空稿才采用 prompt;显式清空作用于目标 Session 的文字文档,不删除附件,也不修改其他 Session。预填不自动发送、执行命令或改变 `blank`。 + +### 数据模型 + +`DraftSnapshot` 由 `text` 和 `references` 组成。每个引用保留 `offset`、`length`、`source`、`ref`、`label`、`appearance`、`clipboardText` 及已有的无效标记;位置使用 JavaScript 字符串坐标。引用位置必须有序、不重叠,且对应的正文片段与 `clipboardText` 一致。不保存 Lexical NodeKey、DOM、运行时 occurrenceId、函数或临时高亮。 + +字符串等价于不含引用结构的输入;不会据模糊的 `@` 查询擅自选中某个文件。公开的 `SessionInput.setDraft` 与 `InputActions.setDraft` 仅接受文本。内部结构化恢复直接复用编辑器已有的引用恢复能力,无需查询文件目录或读取文件内容。恢复普通文字、文件胶囊、目录胶囊和 Session 胶囊都经过已注册的编辑器变换和投影流程。 + +现有 `dsh.conversation.` key 保留,`draft` 可读取旧字符串,也可读取新的结构化值;新写入保存结构化值。仍由原 Conversation store 写整个记录,不增加第二个同 key 写入者。引用变化即使没有改变显示文字,也必须进入持久化快照。 + +### Class 与方法 + +| 位置 | 改动与职责 | +| --- | --- | +| `UiWorkspace`/`UiWorkspaceService.startSession` | 第二参数类型为 `StartSessionOptions`;在原 `beforeOpen` 准备点向准确的目标 binding 传入初始化请求。创建、复用与导航取消路径不变 | +| `SessionInputResolver`/`InputHub.requestDraftInitialization` | 接收 binding 和 options,交给该 binding 的输入对象,不重新查找全局当前 Session | +| `InputHub.shellFor` | 新对象登记及清理接线后,读取该 Session 保存的草稿,通过输入模型导入,然后返回;复用对象不再恢复 | +| `stores.ts` 的 `readConversationDraft(sessionId)`/`draft.ts` 的 `parseStoredDraft` | 只读原存储 key;兼容字符串并检查结构化引用的位置和字段,不创建 store 或增加写入者 | +| `SessionInputShell.setDraft` | 具体类的内部方法接受字符串或 `DraftSnapshot`,向编辑器发送一次文档导入;公开输入方法仅接受文本。两条路径均不模拟键盘事件、不提交内容 | +| `SessionInputShell.requestDraftInitialization` | 在已经恢复的模型上执行清空/保留/填入规则;提交中拒绝修改,失败前不先清稿 | +| `SessionInputShell.draftSnapshot`/`persistCurrentDraft()` | 按内容修订号缓存不含运行时 ID 的快照;`InputActions.persistDraft()` 委托显式保存方法;`bindDraftPersistence(write)` 仍只绑定回调 | +| `DraftEditorRuntime.restoreDraft` | 接受引用的语义字段,重建 ReferenceChipNode;既有发送失败恢复继续使用同一实现 | +| `SessionInputShell.refreshLexiconSubscription()`/`DraftEditorRuntime.refreshLexiconSubscription()` | `InputHub` 拥有 Session scope 的 `inject` 回调,在缓存输入对象后连接词典,服务到达或替换时重接,销毁时退订。连接和词典更新都会重扫当前 editor,不覆盖旧文本;`setDraft` 不负责建立订阅 | +| `ConversationStoreState`/`createConversationStore` | draft 支持兼容旧字符串的结构化输入,View 状态及原 key 保持 | +| `DefaultConversationViews` | 删除恢复/预填 effect;保留持久化回调的绑定和解绑。初始保存只能读取 owner 的当前快照,不能把 render 时的旧文字写回 | +| `apply.ts` 中现有 `selectWorkspace` 回调 | 沿用原有跨工作区携带规则,但传递 `draftSnapshot` 而不是纯文本,防止携带时再次丢失引用 | + +不新增草稿服务、存储 key、通用任务框架或另一套 Session 创建入口;不改 SlotRenderer、Host Session 日志、命令执行和文件发现 RPC。`InputActions` 保留统一输入操作入口,组件不单独注入另一条内容初始化回调。 + +调用参数依次为 `startSession(workspaceId?: WorkspaceId, options?: StartSessionOptions)`、`requestDraftInitialization(binding: SessionBinding, options: DraftInitializationOptions)`、`SessionInputShell.requestDraftInitialization(options)`,以及内部的 `SessionInputShell.setDraft(input: string | DraftSnapshot)`。`StartSessionOptions` 直接引用 `DraftInitializationOptions`,字段为 `prompt?: string` 和 `clearPreviousDraft?: boolean`;进入异步创建前浅拷贝这些值,结果只作用于最终保留的 binding。初始化结果区分已应用、保留原稿和提交期间拒绝;拒绝不先清空,导航被后续操作取消时不写稿。 + +### 数据流 + +```text +stored draft / startSession options + → DraftInput + → SessionInputShell.setDraft + → DraftEditorRuntime → Lexical → InputState + → InputHub.shellFor returns + → React mounts the editor + +user edits / input actions + → editor → projection + → DraftSnapshot + → Conversation store +``` + +### 异步 skill 匹配 + +skill 是可编辑文字,不转为文件胶囊。词典尚未返回时不锁草稿;斜杠名称先保持普通文字,词典提供匹配名称后才高亮。提供方缺席、词典不可用或名称不匹配时不显示 skill 高亮。数据到达后通过现有订阅机制重匹配当前文档,而不是保存请求发出时的文字并回写。此过程不修改正文和结构化引用,不抢光标、不增加用户 Undo 步骤。命令仍只在用户提交时走既有校验和执行路径。 + +## 考虑过的替代方案 + +**挂载后调用 setDraft。** 首次模型读取与显示仍可能为空,清空还可能被稍后的旧稿恢复覆盖;初始化责任不能依赖 React effect 的先后顺序。 + +**只保存 clipboardText,再重新搜索引用。** 文字不足以表达引用的来源和展示字段;搜索候选也不等于恢复用户已经作出的选择。已有结构应直接恢复。 + +**等待所有远程词典再开放输入。** 文件引用已有数据,skill 高亮也不是编辑前提;让网络查询阻塞草稿会引入不必要的等待。 + +**拆分持久化 key 或新增草稿管理服务。** 现有输入 owner 和 store 已能承担职责,只需统一输入数据与导入路径。 + +## 验证 + +数据模型的核心约束是:有效草稿经过导入、导出和 JSON 往返后语义相等,运行时节点 ID 不参与比较;初次获取输入对象即具有完整文档;选区和 skill 高亮变化不改变草稿快照。[文档模型用例](../../../../packages/client/ui-conversation/tests/draft-document.client.spec.ts)覆盖字符串、空稿、多行与 Unicode、文件/目录/Session 引用、重复引用、无效标记、占位字符净化和非法存储跨度;[输入生命周期用例](../../../../packages/client/ui-conversation/tests/draft-hub.client.spec.ts)验证首次读取、复用、词典服务到达/卸载/重连和旧 binding 失效。提交中拒绝初始化,词典重匹配不增加 Undo 步骤。 + +[导航用例](../../../../packages/client/ui-workspace/tests/workspaces-service.client.spec.ts)验证初始化作用于准确的保留对象、参数在异步创建前复制、导航取消不写稿、拒绝时保持原选择。[组合用例](../../../../packages/client/ui-conversation/tests/apply-inject.client.spec.tsx)验证现有工作区携带路径不把引用变成纯文本。相关回归共 44 个文件、708 项通过;共享草稿模块归位后,受影响的三个模型与组合文件再验证 66 项通过。 + +[浏览器 e2e](../../../../apps/web/tests/draft-initialization.e2e.ts)通过已有模块加载观测方式取得实际 Workspace API,调用公开 `startSession` 传入文本选项,不新增测试插件。结构化 fixture 经既有已存草稿路径恢复。用例覆盖缺省、空字符串和非空提示词,明确传入及沿用当前工作区,保留与清空、创建与复用、真实界面的 Session 多轮切换和编辑、刷新、旧字符串及工作区选择器携带。延迟创建响应的两种返回顺序都保持较新的目标选择,并且不改动另一份草稿。延迟返回的真实 skill 目录只重匹配当前文字,不替换胶囊、选区或 Undo 历史。断言检查胶囊字段、文件预览和持久化快照。未点击发送前,Host 日志没有 `user/message` 或 `turn/start`。 + +## 后果与边界 + +旧字符串草稿缺失的引用信息不能无损推断,按普通文字恢复;不为兼容旧数据自动查询并猜测文件。附件文件对象、上传状态、跨浏览器同步、多个独立编辑器和完整 Undo 持久化不在本次范围。 + +本决策与[编辑器隔离方案](../../proposed/architecture/2026-09-14-composer-model-and-draft-editor.zh.md)共享输入 owner 的归属,但不实现其多编辑器目标;[引用预览](../../../../packages/client/ui-input-trigger/README.zh.md)和[Session 引用生命周期](2026-09-15-client-session-references.zh.md)继续各自约束来源路由和代次清理,不被本次决策取代。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml new file mode 100644 index 0000000000..ee7758db34 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-anonymous-reasoning-translation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md +/agent-note-anonymous-translation-remains-separate-from-model-history: + en: ab8fee99b1f6d828 + zh: 0e324a42eebc1403 +/agent-note-anonymous-translation-remains-separate-from-model-history/problem: + en: 5e10347fa87f98b5 + zh: cf3569631af8d66f +/agent-note-anonymous-translation-remains-separate-from-model-history/decision: + en: f13c134f53ed63c9 + zh: 868aacd453d26c82 +/agent-note-anonymous-translation-remains-separate-from-model-history/alternatives-considered: + en: 083d50f8564b0378 + zh: 50af332e64ad06f3 +/agent-note-anonymous-translation-remains-separate-from-model-history/consequences: + en: 57c774c6fb14a1b4 + zh: 5a05393e6f1d25b1 diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md new file mode 100644 index 0000000000..0fa78afa4e --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.md @@ -0,0 +1,31 @@ +# Agent Note: Anonymous translation remains separate from model history + +Status: implemented + +English | [中文](2026-10-05-anonymous-reasoning-translation.zh.md) + +## Problem + +Readers need reasoning in their chosen language without another model credential or replacement of the original assistant output. Translation needs an owner for external requests, cancellation, and failures independent of Agent execution. + +## Decision + +The experimental translator owns explicit Google or Bing routing, provider-language normalization, and bounded network requests. Its Service Definition and both providers share one package because their current consumers use the same operation and lifetime. The optional GUI consumer owns settings, paragraph scheduling, per-disclosure caches, and translated display. Its authenticated Remote calls the translator; the agent loop and Session event types remain independent. + +Bing is the configured default. Enabling the bundle permits transmission of expanded reasoning to the chosen service, disclosed in plugin metadata and settings. Failure never selects another provider. Original remains available; translated text neither replaces persisted assistant output nor enters model input. The [optional-bundle admission policy](2026-09-21-experimental-capabilities-as-optional-bundles.md) keeps the feature disabled until selected. + +The reasoning-body chain carries original text and streaming status. Native Chat owns its original Markdown fallback; consumers do not replace the assistant renderer or alter Session projection. Only expanded bodies start translation. Collapsing, changing selection, or unloading aborts outstanding work and excludes late results. + +## Alternatives considered + +**Translate through the conversation's LLM provider.** This adds model requests and depends on model credentials, pricing, and routing. Anonymous translation has separate availability and requires none of those credentials. + +**Rewrite reasoning in the Session or provider stream.** This changes the original trajectory and can affect model reasoning passback. Display translation preserves the original model history. + +**Silently try another endpoint after failure.** This sends potentially private text to another operator without an explicit selection. Retry uses the same provider; switching providers is a user action. + +## Consequences + +Anonymous translation adds no third-party translation library or model-token usage. Anonymous endpoints provide no developer availability guarantee. Both providers send source text in POST request bodies. Translations may alter Markdown or code formatting and do not translate collapsed summaries. + +The translator retains Session-bound results under the [shared machine-translation persistence decision](2026-10-05-persistent-machine-translation.md); its stateless calls do not record results. diff --git a/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md new file mode 100644 index 0000000000..293f74a5ee --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-anonymous-reasoning-translation.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 免登录翻译独立于模型历史 + +Status: implemented + +[English](2026-10-05-anonymous-reasoning-translation.md) | 中文 + +## 问题 + +读者需要用指定语言阅读思考内容,同时无需额外模型凭据,也不替换原始 assistant 输出。翻译需要独立于 Agent 执行的所有者,负责外部请求、取消与失败。 + +## 决策 + +实验性 translator 负责显式 Google 或 Bing 路由、Provider 语言标签映射及有界网络请求。Service Definition 与两种 Provider 共用一个包,因为当前消费者使用相同操作与生命周期。可选 GUI 消费者负责设置、段落调度、展开组件内缓存及译文展示。其经过身份验证的 Remote 调用 translator;agent loop 与 Session 事件类型保持独立。 + +配置的默认服务为 Bing。启用 Bundle 允许将展开的思考内容发送给所选服务,插件元数据与设置公开说明接收方。失败时不选择其他 Provider。原文始终可访问;译文不替换持久化 assistant 输出,也不进入模型输入。[可选 Bundle 准入规则](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md) 保证功能在显式选择前保持关闭。 + +reasoning-body chain 传递原始文本与流式状态。原生 Chat 负责原始 Markdown fallback;消费者不替换 assistant 渲染器,也不改变 Session projection。只有展开内容才开始翻译。折叠、切换设置或卸载都会中止未完成工作并忽略迟到结果。 + +## 考虑过的替代方案 + +**通过对话使用的 LLM Provider 翻译。** 这会增加模型请求,并依赖模型凭据、定价与路由。免登录翻译有独立的可用性,且无需这些凭据。 + +**重写 Session 或 Provider 流中的思考内容。** 这会改变原始轨迹,并可能影响思考内容回传。展示翻译保留原始模型历史。 + +**失败后静默尝试另一端点。** 这会在没有显式选择的情况下将潜在私有文本发送给另一个运营方。重试使用同一 Provider;切换 Provider 是用户操作。 + +## 后果 + +匿名翻译不增加第三方翻译库或模型 token 消耗。免登录端点不保证开发者可用性。两种服务均通过 POST 请求体发送原文。译文可能改变 Markdown 或代码格式,也不翻译折叠摘要。 + +translator 按[统一的机器翻译持久化决策](2026-10-05-persistent-machine-translation.zh.md)保留绑定 Session 的结果;无状态调用不记录结果。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml new file mode 100644 index 0000000000..4af09bcaae --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-official-on-demand-bundles.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md +/agent-note-discover-official-bundles-without-installing-their-runtimes: + en: 1f2387f10628638b + zh: c835a8ee615e9da6 +/agent-note-discover-official-bundles-without-installing-their-runtimes/problem: + en: a590bcef6c9760c0 + zh: 19e6f61c2c7d8d3b +/agent-note-discover-official-bundles-without-installing-their-runtimes/decision: + en: 31e3c52841f31a11 + zh: ac2ad6e54ef9e150 +/agent-note-discover-official-bundles-without-installing-their-runtimes/alternatives-considered: + en: 42cb0f808a145871 + zh: 225012615d1ecdf5 +/agent-note-discover-official-bundles-without-installing-their-runtimes/consequences: + en: 899b07d90024e019 + zh: 01d22ac4b2444feb diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md new file mode 100644 index 0000000000..0b0ad0cf55 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md @@ -0,0 +1,37 @@ +# Agent Note: Discover Official bundles without installing their runtimes + +Status: implemented + +English | [中文](2026-10-05-official-on-demand-bundles.zh.md) + +## Problem + +Native subagent integrations bring their own runtimes. Shipping those dependencies with every DSH installation charges that cost to people who never delegate to them, while requiring a package name hides the integrations from the Plugins page. + +## Decision + +`ON_DEMAND_BUNDLES` admits the Claude Code and Codex bundles to an installation-owned catalog. The catalog embeds their package-owned localized metadata and icons, so discovery works offline without resolving or importing either package. Official identifies project maintenance; optional delivery and experimental maturity remain independent properties. + +Selection uses the ordinary third-party bundle installer, profile dependency, scoped patches, compatibility evaluation, progress, cancellation, registry fallback, and build-script approval. Native authentication and configuration remain provider-owned. + +The running installation owns the target. Published applications offer the exact DSH release version and save an exact registry dependency pin. Source Web and development Desktop offer ordinary `link:` dependencies into the same checkout as the running CLI, using its prepared workspace dependencies and peers. The source target follows the actual installation, never the current working directory or a prerelease suffix; an incomplete checkout does not fall back to the registry. A development checkout can share a version string with different published code, so replacing an installed registry copy or another checkout’s link compares the source as well as the version. Replacement is explicit, preserves selection, and reports required restarts; startup does not download providers. [Plugin Manager](../../../../packages/boot/plugin-manager/README.md#use-this-package) owns the operational details. + +Every Official package remains in the DSH release family. Packed DSH dependencies, optional dependencies, and peers use the exact family version. Publication prerequisites place catalog packages before the DSH package advertising them without adding their runtimes to DSH production dependencies. Catalog verification rejects missing metadata, unsupported membership, and stale generated content. + +The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch and preset-generation behavior. Native bundles require the full Web preset targets; custom profiles mount their providers and tools explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup. + +## Alternatives considered + +**Ship the native runtimes with every installation.** This makes first activation offline, but increases the default download for unused integrations. + +**Query the registry to build the discovery list.** This avoids embedded metadata, but makes discovery depend on network availability and does not bind the offered package version to the running application. + +**Give Official packages a separate installer or activation registry.** A second path would duplicate package-operation and profile-composition behavior. Installation-owned discovery data can reuse the existing third-party mechanisms. + +**Always install the registry package with the running version.** Version equality identifies a published release, but cannot identify unreleased source changes. It can deliver an older provider API and a provider-only patch to a development Host that expects current tool contributions. + +**Build a separate development registry or artifact cache.** Ordinary local links already keep provider code and its dependencies in the running checkout. A second delivery system would add lifecycle and invalidation work without improving that development promise. + +## Consequences + +Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and respects retained preset modules. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery. diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md new file mode 100644 index 0000000000..6fbdaa6e8f --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md @@ -0,0 +1,37 @@ +# Agent Note: 发现 Official bundle 而不安装其运行时 + +Status: implemented + +[English](2026-10-05-official-on-demand-bundles.md) | 中文 + +## 问题 + +原生子智能体集成携带自己的运行时。把这些依赖放进每次 DSH 安装,会让从不向它们委派任务的用户也承担成本;要求用户知道包名,又会让插件页无法展示这些集成。 + +## 决策 + +`ON_DEMAND_BUNDLES` 将 Claude Code 和 Codex bundle 纳入安装方维护的目录。目录内嵌包自身拥有的本地化元信息和图标,因此发现过程离线可用,无需解析或导入这两个包。Official 表示项目维护;可选交付与实验成熟度仍是独立属性。 + +选择操作复用普通第三方 bundle 的安装器、profile 依赖、作用域 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。原生认证与配置仍由提供方拥有。 + +当前运行的安装决定目标。已发布应用提供精确的 DSH 发布版本,并保存精确的注册表依赖版本。源码 Web 和开发 Desktop 提供指向当前 CLI 所在 checkout 的普通 `link:` 依赖,使用其中已准备好的工作区依赖和 peer。源码目标跟随实际安装位置,不由当前工作目录或预发布版本后缀决定;不完整的 checkout 不回退到注册表。开发 checkout 可以与内容不同的已发布代码共用版本字符串,因此替换已安装的注册表副本或其他 checkout 的链接时,同时比较来源和版本。替换需显式触发,保留选择状态,并报告是否需要重启;启动时不下载提供方。[插件管理器](../../../../packages/boot/plugin-manager/README.zh.md#use-this-package)拥有操作细节。 + +所有 Official 包仍属于 DSH 发布家族。打包后的 DSH 依赖、可选依赖与 peer 均使用家族精确版本。发布前置关系要求先发布目录包,再发布宣传它们的 DSH 包,同时不把这些运行时加入 DSH 生产依赖。目录校验拒绝缺失的元信息、不受支持的成员和过期生成内容。 + +[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 与预设代际行为。原生 bundle 要求完整 Web 预设目标存在;自定义 profile 显式挂载提供方和工具。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。 + +## 考虑过的替代方案 + +**每次安装都附带原生运行时。** 首次激活可离线完成,但会为未使用的集成增加默认下载量。 + +**查询注册表构建发现列表。** 可以避免内嵌元信息,但发现过程依赖网络可用性,且无法把提供的包版本绑定到运行中的应用。 + +**为 Official 包增加独立安装器或激活注册表。** 第二条路径会重复包操作和 profile 组合行为。安装方维护的发现数据可以复用已有第三方机制。 + +**始终安装与当前版本相同的注册表包。** 版本相同可以标识已发布版本,但不能标识尚未发布的源码修改。这可能向期待当前工具贡献的开发 Host 交付旧提供方 API 和仅含提供方的 patch。 + +**建立独立的开发注册表或产物缓存。** 普通本地链接已能让提供方代码及其依赖来自当前 checkout。第二套交付系统会增加生命周期和失效处理,而不改善这一开发保证。 + +## 后果 + +注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并尊重预设仍保留的模块。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.i18n.yaml new file mode 100644 index 0000000000..33da8f55cb --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-paid-native-flash-reasoning-translation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.md +/agent-note-paid-native-flash-translation-uses-independent-queries: + en: 37a2416d68e5a73d + zh: ae439aa6ec6a2feb +/agent-note-paid-native-flash-translation-uses-independent-queries/problem: + en: 1029e393c5794564 + zh: 6044cd7da7fc0947 +/agent-note-paid-native-flash-translation-uses-independent-queries/decision: + en: be443b5b9ed2d0a4 + zh: 399c3c196a3a5858 +/agent-note-paid-native-flash-translation-uses-independent-queries/alternatives-considered: + en: e5ec698f302c8ae8 + zh: b3a7d4f037ca6bdb +/agent-note-paid-native-flash-translation-uses-independent-queries/consequences: + en: 74d2e9a7f22b6555 + zh: 249e56f0c7071fe3 diff --git a/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.md b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.md new file mode 100644 index 0000000000..948ae90f1b --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.md @@ -0,0 +1,27 @@ +# Agent Note: Paid native Flash translation uses independent queries + +Status: implemented + +English | [中文](2026-10-05-paid-native-flash-reasoning-translation.zh.md) + +## Problem + +Readers may choose model-based translation in exchange for additional charges. Reusing their conversation request would inherit its history and generation controls, while routing through an arbitrary configured model would conceal the translation's billing and receiver choices. + +## Decision + +Paid translation is an explicit alternative to the [default anonymous path](2026-10-05-anonymous-reasoning-translation.md). Only active built-in `deepseek-account` and `deepseek-official` owners with configured credentials and the exact `deepseek-flash` catalog ID qualify. Display names and versions do not participate, and pi-ai routes are excluded. Each fragment receives one fresh text-only query, a simple translation instruction and disabled thinking; no previous fragment or main history is supplied. + +The original Session keeps `plugin:translator/request`, the [shared translation request record](2026-10-05-persistent-machine-translation.md), with exact model-visible input in optional `metadata.modelRequest` before dispatch. The existing `appendPluginRecord` writer snapshots its JSON payload and marks it ignorable without replacing main history. Read-only cache lookup precedes native admission and needs no live Session. A cache miss uses the active Session writer and flushes before dispatch; the GUI joins normal Session activation when needed, with its ordinary startup and resume effects. Format migration retains these informational records on a best-effort basis. Translation does not supply the main Session identity to the native request extensions, so a fragment cannot authorize uploading its full conversation log. The configured paid choice is enforced by the Host before billing. + +## Alternatives considered + +**Reuse the conversation's model call.** Its history, model and thinking settings are unrelated to a translation fragment, and session-log delivery could transmit more than the selected text. A fresh native Flash call makes these choices explicit. + +**Offer every model or version label.** This requires additional receiver and billing decisions. The stable `deepseek-flash` ID supports future versions while preserving the requested built-in route restriction. + +**Replace the original assistant output.** Translated text is retained separately as a shared result record; original reasoning remains the source of main model history. + +## Consequences + +Paid queries consume additional input and output tokens. The GUI discloses billing for uncached fragments. Reopening reuses persisted successful results, including when native credentials or model configuration are unavailable. Failures never choose another route. Local audit retention is independent of provider log delivery, and anonymous translation remains the default. diff --git a/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.zh.md b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.zh.md new file mode 100644 index 0000000000..9feb7e8dff --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-paid-native-flash-reasoning-translation.zh.md @@ -0,0 +1,27 @@ +# Agent Note: 付费原生 Flash 翻译使用独立查询 + +Status: implemented + +[English](2026-10-05-paid-native-flash-reasoning-translation.md) | 中文 + +## 问题 + +读者可选择模型翻译并承担额外费用。复用对话请求会继承其历史及生成控制,而路由到任意已配置模型会隐藏翻译的计费及接收方选择。 + +## 决策 + +付费翻译是[默认匿名路径](2026-10-05-anonymous-reasoning-translation.zh.md)的显式替代选项。只有内置 `deepseek-account` 与 `deepseek-official` 所有者处于活动状态、凭据已配置,且目录包含 ID 恰好为 `deepseek-flash` 的模型时才符合条件。显示名称和版本不参与匹配,pi-ai 路由被排除。每个片段执行一次新的纯文本查询,使用简单翻译指令并关闭思考,不携带此前片段或主历史。 + +原始 Session 在派发前通过实验性记录 `plugin:translator/request` 在可选 `metadata.modelRequest` 中保留精确的模型可见请求,采用[统一的翻译请求记录](2026-10-05-persistent-machine-translation.zh.md)。现有 `appendPluginRecord` 写入器快照其 JSON 载荷并标为可忽略,不替换主历史。只读缓存查找先于原生准入,且不要求 Session 已激活。缓存未命中时使用已激活 Session 的写入器,在派发前刷新;GUI 按需等待正常 Session 激活,保留普通启动与恢复行为。格式迁移以尽力而为的方式保留这些信息性记录。翻译不向原生请求扩展提供主 Session 身份,因此单个片段不能授权上传完整对话日志。Host 在计费前强制要求已配置的付费选择。 + +## 考虑过的替代方案 + +**复用对话的模型调用。** 其历史、模型及思考设置与翻译片段无关,Session 日志派送还可能传输超出所选文本的内容。新的原生 Flash 调用使这些选择明确。 + +**提供所有模型或版本标签。** 这需要额外的接收方及计费决策。稳定的 `deepseek-flash` ID 支持未来版本,同时保留所要求的内置路由限制。 + +**替换原始 assistant 输出。** 译文通过统一的结果记录单独保留;原始思考仍是主模型历史的来源。 + +## 后果 + +付费查询消耗额外的输入和输出 token。GUI 说明未缓存片段的计费。重新展开复用已持久化的成功结果,即使原生凭据或模型配置不可用。失败时不选择其他路由。本地审计保留独立于提供方日志派送,匿名翻译仍为默认选项。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.i18n.yaml new file mode 100644 index 0000000000..d54e1be33d --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-persistent-machine-translation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.md +/agent-note-machine-translation-results-use-shared-session-records: + en: 0587db1f8b7b3c6d + zh: 63af01050024d4d1 +/agent-note-machine-translation-results-use-shared-session-records/problem: + en: 4194ba9786778876 + zh: 6b99613a4d0dd5d2 +/agent-note-machine-translation-results-use-shared-session-records/decision: + en: fb8800dede33177b + zh: b3f5098e54d5d982 +/agent-note-machine-translation-results-use-shared-session-records/alternatives-considered: + en: 3f251877332d74cc + zh: 61991d5fd4704a99 +/agent-note-machine-translation-results-use-shared-session-records/consequences: + en: c51bbb4ec6b7f20b + zh: 856dbfdf47509d90 diff --git a/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.md b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.md new file mode 100644 index 0000000000..0826274392 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.md @@ -0,0 +1,35 @@ +# Agent Note: Machine translation results use shared Session records + +Status: implemented + +English | [中文](2026-10-05-persistent-machine-translation.zh.md) + +## Problem + +Readers need a saved translation when they reopen reasoning or restart the application. A disclosure-only cache repeats external requests, can change an already displayed result and can incur repeated model charges. Storing only model inputs leaves successful translations unavailable after restart. + +## Decision + +The translator owns `plugin:translator/request` and `plugin:translator/result` for every provider. A request records its selected provider, exact source text, source and target languages, and recipe identity. Optional metadata carries provider-owned request details. A result references the request's Session sequence and retains the completed translated text. Requests are durable before dispatch, and results are durable before they return to the GUI. + +The browser binds displayed translations to the disclosure's Session, and the Host validates the configured provider and explicit target language. The authenticated Client supplies the original text and Session identity; the Host does not verify that the text belongs to that Session. Successful records are reusable across disclosure lifetimes and restarts. Identity includes the provider and recipe, so changing the receiver, language or translation settings cannot substitute another result. Identical concurrent translations wait for one result; distinct fragments can run in parallel. + +Read-only lookup precedes provider work. New records use the active Session's existing writer through `appendPluginRecord` and targeted flush. An uncached inactive Session rejects before provider work; the GUI consumer joins normal Session controller activation and retries once. + +These records accompany the [original reasoning](2026-10-05-anonymous-reasoning-translation.md) and never replace its events or model input. They use the existing experimental record writer and best-effort format-migration retention. Provider availability gates new requests; a saved result remains readable without another inference. Stateless translator calls remain available without a Session. + +## Alternatives considered + +**Keep only disclosure-local results.** Reopening loses the displayed value and sends the same text again, with possible repeat charges. + +**Give anonymous and model translations different result formats.** The reader needs the same source-to-translation relationship from every provider. Provider-owned audit details belong in optional metadata. + +**Replace original assistant reasoning.** A translation is derived data. Keeping it alongside the original preserves model history and access to the source. + +**Open a separate writer for historical translations.** This competes with normal Session activation and requires coordination between two writer owners. The GUI already activates opened conversations; using its existing controller keeps that ownership in one place. + +## Consequences + +The Session retains translated text and request identity in addition to original reasoning. Only completed persisted results supply cached text; failed or incomplete attempts do not. A completion that has already entered the append-only log may survive a late caller cancellation. Records do not introduce released Session schema history, and future format migrations may drop experimental state. + +Saved results remain readable without activation. A new translation may wait for normal Session activation, including preset startup hooks, resume markers, interrupted-turn repair, and any required generation migration. These ordinary lifecycle effects can append context; translation does not bypass them. Cache reads leave older generations unpublished. The translator owns no second writer and requires no core lifecycle changes. diff --git a/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.zh.md b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.zh.md new file mode 100644 index 0000000000..bf9b9b2c56 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-persistent-machine-translation.zh.md @@ -0,0 +1,35 @@ +# Agent Note: 机器翻译结果使用统一的 Session 记录 + +Status: implemented + +[English](2026-10-05-persistent-machine-translation.md) | 中文 + +## 问题 + +读者在重新展开思考内容或重启应用后仍需要已保存的译文。只在展开区域缓存会重复外部请求、可能改变已显示结果,也可能重复产生模型费用。仅存储模型输入无法在重启后恢复成功译文。 + +## 决策 + +translator 对所有 Provider 统一使用 `plugin:translator/request` 和 `plugin:translator/result`。请求记录所选 Provider、精确原文、源语言和目标语言,以及翻译规则标识。可选 metadata 保存 Provider 自有请求细节。结果引用请求的 Session 序号并保留完成后的译文。请求在派发前持久化,结果在返回 GUI 前持久化。 + +浏览器将展示翻译绑定到展开区域的 Session,Host 校验配置的提供方和显式目标语言。已认证的 Client 提供原文与 Session 身份;Host 不校验原文是否属于该 Session。成功记录可跨展开组件生命周期和重启复用。身份包含 Provider 和翻译规则,因此改变接收方、语言或翻译设置不会混用其他结果。相同的并发翻译等待同一结果,不同片段可并行执行。 + +只读查找先于 Provider 调用。新记录通过已激活 Session 的现有写入器调用 `appendPluginRecord` 并定向刷新。未激活 Session 缓存未命中时,在 Provider 调用前拒绝;GUI 消费者等待 Session controller 的正常激活,再重试一次。 + +这些记录与[原始思考内容](2026-10-05-anonymous-reasoning-translation.zh.md)一同保留,从不替换其事件或模型输入。它们使用现有的实验性记录写入器,格式迁移采用尽力保留语义。Provider 可用性限制新请求,已保存结果无需再次推理即可读取。不指定 Session 时,translator 仍支持无状态调用。 + +## 考虑过的替代方案 + +**只保留展开区域内的结果。** 重新展开会丢失已展示值并再次发送同样的文本,可能重复计费。 + +**匿名翻译与模型翻译使用不同的结果格式。** 读者对所有 Provider 都需要相同的原文到译文关系。Provider 自有审计细节属于可选 metadata。 + +**替换原始 assistant 思考内容。** 翻译属于派生数据。与原文并存可以保留模型历史和原文访问能力。 + +**为历史翻译另开写入器。** 这会与正常 Session 激活竞争,并需要协调两个写入所有者。GUI 已经会激活打开的对话;使用其现有 controller 可将所有权集中在一处。 + +## 后果 + +Session 在原始思考内容之外保留译文和请求身份。只有完成并持久化的结果提供缓存译文,失败或未完成尝试不提供。已进入 append-only 日志的完成记录可能在调用者迟到取消后保留。记录不引入已发布的 Session schema 历史,未来格式迁移可能丢弃实验性状态。 + +已保存结果无需激活即可读取。新翻译可能等待正常 Session 激活,包括 preset 启动钩子、恢复标记、中断轮次修复及所需的 generation 迁移。这些普通生命周期行为可能追加上下文;翻译不会绕过它们。缓存读取不会发布旧 generation。translator 不拥有第二个写入器,也不需要修改核心生命周期。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.i18n.yaml new file mode 100644 index 0000000000..0d4737ef26 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-shared-agents-root-instructions.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md +/agent-note-shared-agents-root-joins-the-user-global-instruction-chain: + en: d29282678b243512 + zh: f81e134f59df5491 +/agent-note-shared-agents-root-joins-the-user-global-instruction-chain/problem: + en: 8fd7583122a2b082 + zh: 032acf0c713780f7 +/agent-note-shared-agents-root-joins-the-user-global-instruction-chain/decision: + en: 69008d5fd1bb091b + zh: a7c4d0334834e6c7 +/agent-note-shared-agents-root-joins-the-user-global-instruction-chain/alternatives-considered: + en: 825c2470ba83573e + zh: d0ffd4fea4f8f0d0 +/agent-note-shared-agents-root-joins-the-user-global-instruction-chain/consequences: + en: cc30674b9b41d0f3 + zh: 36ac52fb078dbec4 diff --git a/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md new file mode 100644 index 0000000000..5687130961 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.md @@ -0,0 +1,37 @@ +# Agent Note: Shared agents root joins the user-global instruction chain + +Status: implemented + +English | [中文](2026-10-05-shared-agents-root-instructions.zh.md) + +## Problem + +DSH loads one user-global instruction file, `$DSH_HOME/AGENTS.md`. People who run several agent tools keep the instructions they share across those tools in the shared agent configuration root (`~/.agents/AGENTS.md`, or `$DSH_AGENTS_HOME`) that `dsh-skill-filesystem` already scans for skills. Those instructions reached DSH only when the person copied or symlinked them into the harness home, which duplicates one source of truth into DSH-owned data and leaves no way to tell which copy a session loaded. + +## Decision + +The user-global instruction scope has two ordered roots: `$DSH_HOME/AGENTS.md` first, then `/AGENTS.md`. The instruction loader resolves the harness home from `$DSH_HOME` or `~/.dsh`, and `agentsHome` from `$DSH_AGENTS_HOME` or `~/.agents`. `resolveAgentsHome` and `agentsHomeDisplay` in `dsh-home-paths` own that resolution for both `dsh-agent-instructions` and `dsh-skill-filesystem`, so the shared root has one resolver whose tilde expansion and blank-environment handling match the harness home. + +The two files are candidates of one candidate group. They deduplicate by trimmed content, so identical copies render once from the harness home, and an unobservable candidate preserves the group's last-good state. A duplicate of budget-retained content keeps being reconciled, so deleting or changing the retained candidate promotes it; a byte-budget omission leaves that candidate and its content duplicates unprobed. The harness-home file keeps the `user-global` scope key that earlier sessions recorded; the shared file carries `agents-global`, and reconciliation probes both roots on resume, so an external edit, a removal, or a newly created file appears as a change notice instead of a new baseline. + +A user-global scope directory is an internal reconciliation key, so it stays out of model-visible prose: a global delta states `These user-global instructions apply to all work.` instead of naming a scope directory, and a project path whose first component would collide with one renders with a leading `.` so it keeps its own scope. + +`USER_GLOBAL_DIRECTORIES` fixes the root order, and discovery, baseline reconciliation, and deduplication all iterate it. The harness home therefore decides content duplicates and keeps its position in the baseline, while distinct content from the shared root follows it. + +## Alternatives considered + +**Replace the harness-home file with the shared root.** The harness home owns DSH-specific configuration, and its `AGENTS.md` is the user-global file the package documents, tests, and renders today. Removing it would drop a DSH-only layer for anyone who shares instructions across tools, and sessions that recorded `user-global` state would lose their referent. + +**Load the shared root only when the harness-home file is absent.** The two files then alternate as the session-visible user-global file, which makes a DSH-specific file silently suppress the shared one instead of composing with it. + +**Load the shared file as a second candidate name in the harness home.** `instructionFileCandidates` names files within one directory; the two roots are different directories, so the option cannot express this chain without redefining candidate semantics. + +**Rely on path identity instead of content for the duplicate case.** The common layout is `$DSH_HOME/AGENTS.md` symlinked to `~/.agents/AGENTS.md`, which is two paths for one file. Path identity drops neither, so the same text would render twice under two display labels. + +## Consequences + +- Shared instructions load without a copy or symlink, and the symlinked layout still renders once, from the harness home. +- A session that started before this change receives one transition when the shared file exists with content that differs from the harness-home file. +- `dsh-agent-instructions` exposes no `dshHome` or `agentsHome` config field; its two roots are process policy. `dsh-skill-filesystem` retains its explicit home fields, which select that provider's own skill roots. +- The two user-global files share one fate on probe failure: the group preserves its last-good state together, because one digest decides which member renders. +- `packages/context/agent-instructions/tests/agent-instructions.spec.ts` pins the two-root order, content deduplication, duplicate promotion after the retained candidate changes or disappears, the global delta wording, the `replace` and `remove` transitions, `$DSH_AGENTS_HOME` resolution, the `~/.agents` display label, and the escaped project directory name. diff --git a/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.zh.md b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.zh.md new file mode 100644 index 0000000000..bea805d075 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-shared-agents-root-instructions.zh.md @@ -0,0 +1,37 @@ +# Agent Note: 共享 agents 根目录加入用户全局指令链 + +Status: implemented + +[English](2026-10-05-shared-agents-root-instructions.md) | 中文 + +## 问题 + +DSH 只加载一个用户全局指令文件:`$DSH_HOME/AGENTS.md`。同时使用多个 agent 工具的人,会把跨工具共享的指令放在共享 agent 配置根目录(`~/.agents/AGENTS.md`,或 `$DSH_AGENTS_HOME`)——`dsh-skill-filesystem` 已经在该根目录下扫描 skill。这些指令只有被复制或 symlink 到 harness home 之后才能进入 DSH,这会把唯一真源复制进 DSH 自有数据,也让人无法判断会话加载的是哪一份。 + +## 决策 + +用户全局指令 scope 有两个有序根目录:先是 `$DSH_HOME/AGENTS.md`,然后是 `/AGENTS.md`。指令加载器从 `$DSH_HOME` 或 `~/.dsh` 解析 harness home,从 `$DSH_AGENTS_HOME` 或 `~/.agents` 解析 `agentsHome`。`dsh-home-paths` 中的 `resolveAgentsHome` 与 `agentsHomeDisplay` 为 `dsh-agent-instructions` 和 `dsh-skill-filesystem` 共同负责该解析,因此共享根目录只有一个解析器,其波浪号展开与空白环境变量处理都与 harness home 一致。 + +这两个文件是同一个候选组中的候选:按去除首尾空白后的内容去重,完全相同的副本只从 harness home 渲染一次,候选不可观测时保留该组最后一次成功状态。预算保留内容的重复候选仍参与对账,因此保留的候选被删除或改变时它会被提升为可见;字节预算省略会让该候选及其内容重复项不再被探测。harness home 文件保留更早会话记录过的 `user-global` scope key;共享文件使用 `agents-global`。对账在恢复会话时会探测两个根目录,因此外部编辑、移除或新建文件都会表现为变更通知,而不会重新生成基线。 + +用户全局 scope 目录是对账用的内部 key,因此不会进入模型可见文本:全局增量声明 `These user-global instructions apply to all work.`,而不指名 scope 目录;首段路径与 scope 目录同名的项目路径会带前导 `.` 渲染,从而保留自己的 scope。 + +根目录顺序由 `USER_GLOBAL_DIRECTORIES` 固定,发现、基线对账与去重都按它迭代。因此由 harness home 决定内容重复项,并在基线中保持自己的位置;共享根目录中内容不同的文件排在其后。 + +## 备选方案 + +**用共享根目录替换 harness home 文件。** harness home 存放 DSH 专有配置,其 `AGENTS.md` 是该包当前记录、测试并渲染的用户全局文件。移除它会剥夺跨工具共享指令者的 DSH 专有层,也会让记录了 `user-global` 状态的会话失去指涉对象。 + +**仅当 harness home 文件不存在时才加载共享根目录。** 这样两个文件会轮流充当会话可见的用户全局文件,使 DSH 专有文件静默压制共享文件,而不是与之组合。 + +**把共享文件作为 harness home 中的第二个候选名加载。** `instructionFileCandidates` 命名的是同一个目录内的文件;这两个根目录是不同目录,因此该选项无法表达这条指令链,除非重新定义候选语义。 + +**重复项改用路径标识而非内容判断。** 常见布局是把 `$DSH_HOME/AGENTS.md` symlink 到 `~/.agents/AGENTS.md`,这是同一个文件的两个路径。路径标识无法丢弃其中任何一个,同一段文本会以两个显示标签渲染两次。 + +## 影响 + +- 共享指令无需复制或 symlink 即可加载;symlink 布局仍然只从 harness home 渲染一次。 +- 在此变更之前启动的会话,若共享文件存在且内容与 harness home 文件不同,会收到一次变更通知。 +- `dsh-agent-instructions` 不暴露 `dshHome` 或 `agentsHome` 配置字段;它的两个根目录属于进程策略。`dsh-skill-filesystem` 保留显式 home 字段,用于选择该提供方自身的 skill 根目录。 +- 探测失败时两个用户全局文件共享同一命运:整个组一起保留最后一次成功状态,因为决定哪个成员渲染的是同一个 digest。 +- `packages/context/agent-instructions/tests/agent-instructions.spec.ts` 固定了两个根目录的顺序、内容去重、保留候选改变或消失后的重复项提升、全局增量措辞、`replace` 与 `remove` 变更、`$DSH_AGENTS_HOME` 解析、`~/.agents` 显示标签,以及转义后的项目目录名。 diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml new file mode 100644 index 0000000000..bab272d8e1 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-10-06-web-desktop-plugin-identity-compatibility.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation: + en: 45d8ead4ca3db272 + zh: 4ef8e729d5d7e8c1 +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/problem: + en: 8112c1c059d1cf10 + zh: 852d23c56a93a555 +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/decision: + en: ac725c281d7dfb41 + zh: 5adcaa6ca6eab384 +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/deferred-naming-cleanup: + en: 4c428f65ef611d07 + zh: 1fdc5467b0203d08 +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered: + en: 1bf03723fea6c9a4 + zh: 2d0d2fc997f7c022 +/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences: + en: 60f7c823b5d2ed6f + zh: 75f375487cab3c56 diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md new file mode 100644 index 0000000000..def042e26a --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.md @@ -0,0 +1,40 @@ +# Agent Note: Preserve Web and Desktop plugin identities across experimental consolidation + +Status: implemented + +English | [中文](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md) + +## Problem + +Moving a capability into the experimental directory changes its ownership and support classification. Renaming its published package or an existing configuration row additionally changes how installed profiles refer to that capability. Those identity changes can make a working Web or Desktop customization require edits unrelated to the capability's behavior. + +## Decision + +The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of naming exceptions. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated exception is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection. + +Existing badge, Ralph, and native delegation row IDs remain valid selectors inside their applicable Host or preset scope. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows, and a complete preset replacement remains authoritative. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle. + +Compatibility protection covers Web and Desktop profiles, including existing native provider package identities, selections, configuration, and authentication. Headless, SDK, and ACP compositions may require changes. Native bundles use the ordinary shared patch operation and require the full preset targets; separate provider-only compatibility wrappers are unnecessary for the protected profiles. Public APIs remain pre-stable, and new output metadata stays required unless an actual consumer needs an accommodation. + +## Deferred naming cleanup + +| Retained identity | Unconstrained cleanup | Why it is deferred | +|---|---|---| +| Published names in the naming-exception map | Give every experimental package the `@deepseek-ai/dsh-experimental-` prefix | Existing dependencies, imports, module declarations, and profile files use the established names | +| Existing badge, Ralph, and native-tool row IDs | Give optional contributions consistently distinct `optional-*` selectors | Existing user overrides use those IDs; the ownership move does not require a new selector | + +These naming changes are deferred, not prerequisites for experimental classification. A later cleanup requires an explicit release and migration decision, an inventory of affected Web/Desktop configurations, and installed-profile upgrade evidence. Directory moves or pre-stable API status alone do not retire these exceptions. The package list stays in the typed policy rather than being copied into this note or package READMEs. + +## Alternatives considered + +**Rename every package and row during consolidation.** Uniform spelling makes the new ownership visible, but adds dependency and configuration migration to a change that can preserve those references. + +**Publish aliases or keep duplicate compatibility rows.** Parallel identities need continuing resolution and removal rules and can register the same capability twice. Retaining the established identity avoids that additional mechanism. + +**Keep provider-only bundles through additional wrapper packages.** Web and Desktop already provide the required presets. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance. + +## Consequences + +The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides, disabled state, and preset replacement. No general alias or automatic profile migration mechanism is introduced. + +The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope. diff --git a/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md new file mode 100644 index 0000000000..1f513060ea --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-06-web-desktop-plugin-identity-compatibility.zh.md @@ -0,0 +1,40 @@ +# Agent Note: 在实验能力整合中保留 Web 和 Desktop 插件身份 + +Status: implemented + +[English](2026-10-06-web-desktop-plugin-identity-compatibility.md) | 中文 + +## 问题 + +把能力迁入 experimental 目录会改变其归属和支持分类。重命名已发布的包或既有配置行,还会改变已安装 profile 引用该能力的方式。这些身份变化可能要求正常工作的 Web 或 Desktop 自定义配置进行与能力行为无关的修改。 + +## 决策 + +迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是命名例外唯一手工维护的清单。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的例外会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。 + +既有徽章、Ralph 和原生委派行 ID 在对应的 Host 或预设作用域中保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行,完整预设替换仍具有决定权。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。 + +兼容性保护覆盖 Web 和 Desktop profile,包括既有原生提供方的包身份、选择状态、配置和认证。Headless、SDK 和 ACP 组合可以要求修改。原生 bundle 使用普通的共享 patch 操作并要求完整预设目标存在;受保护的 profile 不需要单独的仅提供方兼容包装包。公共 API 仍处于稳定前阶段,新增输出元信息保持必填,除非实际消费者需要适配。 + +## 暂缓的命名清理 + +| 保留的身份 | 不受兼容性约束时的清理 | 暂缓原因 | +|---|---|---| +| 命名例外映射中的已发布名称 | 为每个实验包使用 `@deepseek-ai/dsh-experimental-` 前缀 | 既有依赖、导入、模块声明和 profile 文件使用原有名称 | +| 既有徽章、Ralph 和原生工具行 ID | 为可选贡献统一使用独立的 `optional-*` 选择器 | 既有用户覆盖使用这些 ID;归属迁移不要求新的选择器 | + +这些命名变更被暂缓,不是实验分类的前提。后续清理需要明确的发布与迁移决策、受影响 Web/Desktop 配置清单,以及已安装 profile 的升级验证。仅目录迁移或公共 API 处于稳定前阶段,不足以删除这些例外。包清单保留在带类型的策略中,不复制到本记录或各包 README。 + +## 考虑过的替代方案 + +**在整合时重命名所有包和配置行。** 统一拼写可以体现新归属,但会为本可保留引用的变更加上依赖和配置迁移。 + +**发布别名或保留重复兼容行。** 并行身份需要持续维护解析和移除规则,还可能重复注册同一能力。保留既有身份可以避免增加这种机制。 + +**通过额外包装包保留仅提供方 bundle。** Web 和 Desktop 已提供所需预设。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。 + +## 后果 + +仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置、禁用状态和预设替换的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。 + +[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml deleted file mode 100644 index 5247ed21c3..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-20-jsonl-storage-identity.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-20-jsonl-storage-identity.md -/agent-note-bind-jsonl-session-identity-before-mutation: - en: 1d52db2391f1433e - zh: 3bcb9dca6b77bfdd -/agent-note-bind-jsonl-session-identity-before-mutation/problem: - en: 960d783083269ddd - zh: ef52b53d99b520d1 -/agent-note-bind-jsonl-session-identity-before-mutation/decision: - en: 3fc8ab92d1f0f98b - zh: 855b863c7d90cdd9 -/agent-note-bind-jsonl-session-identity-before-mutation/alternatives-considered: - en: 5269f154c37cb8de - zh: 983f18775457361a -/agent-note-bind-jsonl-session-identity-before-mutation/consequences: - en: 2c88ff974542760f - zh: 489996a16ba6907b diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml deleted file mode 100644 index dbd450eb86..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-31-fail-loud-releases-the-terminal.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md -/agent-note-fail-loud-releases-the-terminal-before-exiting: - en: c070ae81b744a18b - zh: 8379e89b11e263c4 -/agent-note-fail-loud-releases-the-terminal-before-exiting/problem: - en: 705893ebb3a11d92 - zh: 7d19929c62213a3c -/agent-note-fail-loud-releases-the-terminal-before-exiting/decision: - en: a28315c307acdf90 - zh: fb7b2df986ffdffc -/agent-note-fail-loud-releases-the-terminal-before-exiting/alternatives-considered: - en: 5ee239978e8f1ece - zh: 55267740c7726c25 -/agent-note-fail-loud-releases-the-terminal-before-exiting/consequences: - en: 19241cdfdb6c4e56 - zh: 24534e8660ae2f96 -/agent-note-fail-loud-releases-the-terminal-before-exiting/testing: - en: 8a7b0c74ec32d5a0 - zh: 5bbb564ecc8a9870 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml deleted file mode 100644 index 23c1697c7e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-03-cli-signal-shutdown-escalation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless: - en: d8f8ef8811dfe378 - zh: ca728da97cb35920 -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless/problem: - en: 1042d3e1c2cb1e45 - zh: d32f05dd83eee04b -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless/decision: - en: 5738aa92d9185c4f - zh: 1999e41d43063c12 -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless/alternatives-considered: - en: 53239a7e43d70aa1 - zh: d5ab1cc4c52513e6 -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless/consequences: - en: 888679c978b87125 - zh: c68d962f516f675e -/agent-note-bounded-escalating-signal-shutdown-for-web-and-headless/testing: - en: e2dd8e925ff94a13 - zh: b9430f38c27982ff diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml deleted file mode 100644 index a261460c22..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-06-bwrap-private-pid-namespace.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-06-bwrap-private-pid-namespace.md -/agent-note-isolate-bwrap-from-the-host-pid-namespace: - en: 56e719d5aa015e57 - zh: 2568d89bb33ab663 -/agent-note-isolate-bwrap-from-the-host-pid-namespace/problem: - en: 2da6f0dd324f054d - zh: 4a414f04e638aa47 -/agent-note-isolate-bwrap-from-the-host-pid-namespace/decision: - en: d1000f28dd8cd2c6 - zh: 9b35f7085ae68b99 -/agent-note-isolate-bwrap-from-the-host-pid-namespace/alternatives-considered: - en: e794f6d8578bffef - zh: 61feee3ed99c615d -/agent-note-isolate-bwrap-from-the-host-pid-namespace/verification: - en: 7f9a12f088ce869f - zh: 8d1ea7519de1605d -/agent-note-isolate-bwrap-from-the-host-pid-namespace/consequences: - en: 4543b72f4d669fdf - zh: 7576f60bac467013 diff --git a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml deleted file mode 100644 index 82875f587c..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-06-host-backed-web-preferences.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md -/agent-note-persist-web-user-preferences-through-host-settings: - en: da28d8677a4d9f9c - zh: a5311ab734f576d3 -/agent-note-persist-web-user-preferences-through-host-settings/problem: - en: 18b583ecb58b9e06 - zh: 55570d758b75852a -/agent-note-persist-web-user-preferences-through-host-settings/decision: - en: 2846c0d0f8615da7 - zh: 511d61ea4ea6efa6 -/agent-note-persist-web-user-preferences-through-host-settings/alternatives-considered: - en: a91ebb47ec1ee356 - zh: d5ce0b7f48195093 -/agent-note-persist-web-user-preferences-through-host-settings/consequences: - en: 7defab72acfd5e78 - zh: 566cbbd3eb3cd9e3 diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml deleted file mode 100644 index 8f8930a2f0..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-07-cancel-convergence-wake-latch.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md -/agent-note-latch-wake-ups-that-land-in-the-cancel-convergence-window: - en: 0b877f8208b50162 - zh: dd6b6af7d96a5f86 -/agent-note-latch-wake-ups-that-land-in-the-cancel-convergence-window/problem: - en: 4539f5fa2c7dc7e4 - zh: f7b6cbe04ca0ffc2 -/agent-note-latch-wake-ups-that-land-in-the-cancel-convergence-window/decision: - en: 23d5dbc6b75ca908 - zh: 116ae93f6a48e6ed -/agent-note-latch-wake-ups-that-land-in-the-cancel-convergence-window/alternatives-considered: - en: 6df22ff84b2dc081 - zh: 38d1559935b66fb9 -/agent-note-latch-wake-ups-that-land-in-the-cancel-convergence-window/consequences: - en: 7d72c68239cd7e31 - zh: 19045871dcceb4bf diff --git a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml deleted file mode 100644 index a3575ac176..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-07-ptc-executor-collapse.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-07-ptc-executor-collapse.md -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire: - en: 7958ca7ba1201af2 - zh: bc7f74c91444eed6 -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/problem: - en: a6fcd5114ffde259 - zh: 05940375a30756d0 -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/decision: - en: 66f55639766a6d02 - zh: a8819b045463a927 -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/alternatives-considered: - en: 6119e2aff3b6be5a - zh: e01a6c669a9ec6aa -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/alternatives-considered/filter-get-the-registry-view-by-mode: - en: 31b6fed689eea5db - zh: ace070ad616bca62 -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/alternatives-considered/filter-at-the-agent-loop-entry: - en: 9dbdb9939baccd49 - zh: 11437030cd211000 -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/alternatives-considered/reject-via-a-shipped-guard: - en: 9011947651f58129 - zh: 1d4a0c622680f13f -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/alternatives-considered/keep-schema-omission-only-status-quo: - en: 0d4759646b19917a - zh: 1a1bef7617c52f9c -/agent-note-ptc-mode-collapses-the-executor-not-just-the-wire/consequences: - en: 9120e68ba25bd30a - zh: 6ec4bea5df8668a1 diff --git a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml deleted file mode 100644 index e86b334e45..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-10-child-agents-join-their-parent-preset.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-10-child-agents-join-their-parent-preset.md -/agent-note-child-agents-join-their-parent-s-preset-composition: - en: ad623e2fef93c033 - zh: a37abf059f39aefa -/agent-note-child-agents-join-their-parent-s-preset-composition/problem: - en: 0b8d70c073523bc1 - zh: 6bba76a467508149 -/agent-note-child-agents-join-their-parent-s-preset-composition/decision: - en: 13a3452527f35bc6 - zh: 6c319f554184e578 -/agent-note-child-agents-join-their-parent-s-preset-composition/alternatives-considered: - en: 88a48e6bba3d30e2 - zh: 842d835a7a9db434 -/agent-note-child-agents-join-their-parent-s-preset-composition/testing: - en: 458a2aa1ae046253 - zh: 59fad52e881989ed -/agent-note-child-agents-join-their-parent-s-preset-composition/consequences: - en: 09fd94b5ceaa95e6 - zh: bf8e4e22f5b70b45 diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml deleted file mode 100644 index 63f61fbc54..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-17-durable-web-queue-recovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-17-durable-web-queue-recovery.md -/agent-note-recover-the-web-queue-from-durable-inbox-state: - en: df261933e13adb0a - zh: 369772acd6fe0e48 -/agent-note-recover-the-web-queue-from-durable-inbox-state/problem: - en: 79fe907a6201ed27 - zh: 55d0de6b6518f946 -/agent-note-recover-the-web-queue-from-durable-inbox-state/decision: - en: 2e64d021a2009eec - zh: b3f7d5ba2a6f6322 -/agent-note-recover-the-web-queue-from-durable-inbox-state/verification: - en: bab880ec7de69673 - zh: 769f2641856fde60 -/agent-note-recover-the-web-queue-from-durable-inbox-state/alternatives-considered: - en: df29962ab5d779ee - zh: 2593f0973b725d54 -/agent-note-recover-the-web-queue-from-durable-inbox-state/consequences: - en: ffa16214aa1f0efd - zh: 7915524221acac0c diff --git a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml deleted file mode 100644 index fcd7dd96b2..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-17-subagent-message-settlement-ordering.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-17-subagent-message-settlement-ordering.md -/agent-note-child-agent-messages-precede-their-settlement-notices: - en: b46c628c107fbdfe - zh: 829b71dae5e73e0f -/agent-note-child-agent-messages-precede-their-settlement-notices/problem: - en: 82e6c4d5db64a818 - zh: 3610573f1521409e -/agent-note-child-agent-messages-precede-their-settlement-notices/decision: - en: 94a44265545ff53e - zh: 06db7c1bacb6b120 -/agent-note-child-agent-messages-precede-their-settlement-notices/decision/ordering-across-parent-states: - en: 2bceedd85283e6ed - zh: 6c11a3519fe9b941 -/agent-note-child-agent-messages-precede-their-settlement-notices/decision/verification: - en: b2d6b562739ea61c - zh: 33f56b3220b00953 -/agent-note-child-agent-messages-precede-their-settlement-notices/alternatives-considered: - en: a81f4efd6c32125e - zh: d38898c6b3352ad8 -/agent-note-child-agent-messages-precede-their-settlement-notices/consequences: - en: 3b8532d48efb656a - zh: 971bced6f6bed59d diff --git a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml deleted file mode 100644 index d82479170d..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-24-alpha-routed-image-quality-ladders.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-24-alpha-routed-image-quality-ladders.md -/agent-note-alpha-routed-image-quality-ladders-replace-colour-count-codec-routing: - en: b2c958d45911a787 - zh: b826c61174b3640f -/agent-note-alpha-routed-image-quality-ladders-replace-colour-count-codec-routing/problem: - en: bcb73f4711d40348 - zh: 6e26853ea4728e4d -/agent-note-alpha-routed-image-quality-ladders-replace-colour-count-codec-routing/decision: - en: aabf20046a7a15e9 - zh: dba0c5261a3af1e5 -/agent-note-alpha-routed-image-quality-ladders-replace-colour-count-codec-routing/alternatives-considered: - en: 95a42e3e2313790b - zh: 116043c295d7aa12 -/agent-note-alpha-routed-image-quality-ladders-replace-colour-count-codec-routing/consequences: - en: 3ce6acd960671b52 - zh: 1ec81d76130d338d diff --git a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml deleted file mode 100644 index 3ffc1ac876..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-31-win32-picker-path-string-read.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-31-win32-picker-path-string-read.md -/agent-note-read-the-win32-picker-path-without-a-fixed-size-unmanaged-view: - en: abd03a3bff03600e - zh: 8a4a21ecc7cae78b -/agent-note-read-the-win32-picker-path-without-a-fixed-size-unmanaged-view/problem: - en: 1835aa6f42229a1f - zh: 928cb408e879fc41 -/agent-note-read-the-win32-picker-path-without-a-fixed-size-unmanaged-view/decision: - en: 831ea941c84193ef - zh: b7a1842d9843bf0d -/agent-note-read-the-win32-picker-path-without-a-fixed-size-unmanaged-view/alternatives-considered: - en: 8c0a9a1620504f50 - zh: 7f439ecb6ca5a618 -/agent-note-read-the-win32-picker-path-without-a-fixed-size-unmanaged-view/consequences: - en: 616bc4293a0b1856 - zh: ed8cf574d7a126cc diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml deleted file mode 100644 index 40c0a993a5..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-03-normalized-unread-fs-tool-diagnostic.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-03-normalized-unread-fs-tool-diagnostic.md -/agent-note-normalized-unread-filesystem-tool-diagnostic: - en: 103e786a598d2ee3 - zh: 13d637140685f63a -/agent-note-normalized-unread-filesystem-tool-diagnostic/problem: - en: 89cfcff2f9895623 - zh: dd1cd21d431e5161 -/agent-note-normalized-unread-filesystem-tool-diagnostic/decision: - en: 0c59c5193131ca44 - zh: da48f204aef62c99 -/agent-note-normalized-unread-filesystem-tool-diagnostic/alternatives-considered: - en: dbb9cbdafd0f2764 - zh: ed8bef943f12b21d -/agent-note-normalized-unread-filesystem-tool-diagnostic/consequences: - en: 20abbdbfa3aa4edc - zh: d0555aac68cb7a1f diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml deleted file mode 100644 index 9789836b6d..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-03-root-marker-metadata-failures.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-03-root-marker-metadata-failures.md -/agent-note-root-marker-metadata-failures: - en: c351be96177b2ea5 - zh: a094d77aaffd1fbe -/agent-note-root-marker-metadata-failures/problem: - en: 3d88594600ede55b - zh: fd9e36f5b72d8aee -/agent-note-root-marker-metadata-failures/decision: - en: 8131fbf06980774f - zh: 21a3c48edbe9ab99 -/agent-note-root-marker-metadata-failures/alternatives-considered: - en: 2070e244fd8ffc91 - zh: 31324cd0d5ace0a9 -/agent-note-root-marker-metadata-failures/consequences: - en: a459fc1a7a8353a3 - zh: 144b373b0988b633 -/agent-note-root-marker-metadata-failures/verification: - en: 4c912e5effdaedb7 - zh: 1b28ba3dea9170dd diff --git a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml deleted file mode 100644 index c75a62877c..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-03-user-owned-goal-pause-activation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-03-user-owned-goal-pause-activation.md -/agent-note-user-owned-goal-pause-exposes-live-activation: - en: 07749eb6ac180964 - zh: b9c1165739fc45bf -/agent-note-user-owned-goal-pause-exposes-live-activation/problem: - en: 9f044f22e6782773 - zh: 6c17c53aa75280fd -/agent-note-user-owned-goal-pause-exposes-live-activation/decision: - en: 2cc4ab097350a88f - zh: db10825a6285f3c5 -/agent-note-user-owned-goal-pause-exposes-live-activation/alternatives-considered: - en: 640afdb17164b06b - zh: 92d33c139c40b2a1 -/agent-note-user-owned-goal-pause-exposes-live-activation/consequences: - en: d73e412f853612ee - zh: 96630d28e3c431e1 -/agent-note-user-owned-goal-pause-exposes-live-activation/testing: - en: 9d103a90036f5d06 - zh: df5440c8a4640e93 diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml deleted file mode 100644 index 4248c352c7..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-04-busy-send-button-follows-enter-setting.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md -/agent-note-the-busy-send-button-follows-the-enter-setting: - en: d0e9f804958ee8ce - zh: e5cd102fbdd12ff1 -/agent-note-the-busy-send-button-follows-the-enter-setting/problem: - en: c378dfa9efcc0758 - zh: 785c3b9ee99c66a4 -/agent-note-the-busy-send-button-follows-the-enter-setting/decision: - en: 101e21100f8afd7d - zh: 76979af8aee15976 -/agent-note-the-busy-send-button-follows-the-enter-setting/verification: - en: eb886930b09445bd - zh: 1e671f8f81868490 -/agent-note-the-busy-send-button-follows-the-enter-setting/alternatives-considered: - en: d47aa278a2103890 - zh: 313f75c7fb8ccd5c -/agent-note-the-busy-send-button-follows-the-enter-setting/consequences: - en: a067e427615a682a - zh: d72074056e1def4a diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md deleted file mode 100644 index 2db0848e3c..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: The busy Send button follows the Enter setting - -Status: implemented - -English | [中文](2026-09-04-busy-send-button-follows-enter-setting.zh.md) - -## Problem - -The Web composer offers one user-facing choice for submitting while the agent is running: the `ui-conversation.busyEnter` setting selects Queue or Steer. [Running drafts take the primary Send action](../../archived/bug-fix/2026-08-20-running-draft-primary-send.md) (archived) gave a running draft a pointer Send button, deliberately kept it off the preference to avoid an invisible mode on a button labeled only Send, and routed every click through the public `InputActions.submit()` face, which `SessionInputShell.actions` fixes to `'queue'`. A user who chose Steer in Settings got Steer from Enter and Queue from the button beside the same draft, with the button labeled only "Send message". Nothing in the composer explained the divergence, and the Settings row's title and description named only the Enter key, so the setting looked broken rather than deliberately partial. - -## Decision - -The running Send button delivers through the same mode as plain Enter. `InputBar` computes `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)` once per render, where `steeringAvailable` is the same ordinary-Session-or-continuable-child predicate the keyboard path uses, applies it to the primary click through `ComposerKeyboard.submit(mode)`, and applies it to the primary label exactly when the click would deliver a plain message: the composer is running and steer-capable, the button is enabled (no file upload still pending), and the draft is non-empty, unclaimed, and not a `/` line headed for command adjudication. That state shows `input.send.queue` ("Queue message" / "排队发送") or `input.send.steer` ("Steer message" / "插话发送") as both the tooltip and the accessible name; every other state in which the seat is a Send button — idle sessions, one-shot children, locked composers, a continuable child's empty draft, drafts with a pending upload, and command drafts whose click executes the command rather than delivering a message — keeps `input.send` ("Send message"); an ordinary running session with an empty or owner-blocked draft shows Stop in that seat instead. Cmd/Ctrl+Enter still resolves to the opposite mode, and the empty-draft accelerated gesture still steers the whole queue. The [continuable subagent interrupt note](../feature/2026-08-06-continuable-subagent-interrupt.md) describes the child's Send with this delivery. - -The composer bar's inject face carries the live preference instead of a resolver closure. `ComposerBarInjected.hooks.busyEnter` publishes `ComposerSubmissionPolicy.busyEnter`, so the bar receives a `useBusyEnter` selector hook and re-renders the label when the Settings row or a Host settings update changes the value. `resolveSubmitMode` is a pure exported function in `submission-policy.ts` taking the preference explicitly; the policy class keeps only the store and its Host adoption and write-through. - -The Settings row is retitled to cover both inputs: "Send behavior while busy" / "繁忙时的发送行为", described as what Enter and the Send button do while the agent is running, with the Cmd/Ctrl+Enter opposite-mode note retained. The `busyEnter` field name, its `queue` default, and the Host schema are unchanged, so existing `settings.yaml` documents keep their meaning. - -## Verification - -`input-bar.client.spec.tsx` asserts that a running draft's button is labeled by mode and submits with that mode under both preferences, that flipping the preference store re-labels the mounted button before the next click, that idle Send keeps the plain label and Queue delivery regardless of the preference, that a continuable subagent's Send follows the same mode and label as an ordinary Session while its empty-draft disabled button and a one-shot child keep plain Send, and that a `/` line, a claimed command, and a draft with a still-uploading file keep plain Send while running. `submission-policy.client.spec.ts` pins `resolveSubmitMode` for every preference, running, gesture, and steering-availability combination. `enter-behavior-row.client.spec.tsx` and the `settings-chrome` ARIA goldens carry the new Settings copy. The keyless `live-interactions` Web scenario waits for "Queue message" on the parked running draft and asserts that no "Send message" button exists at that moment, and its `running-draft.expected.md` golden records the new name. - -## Alternatives considered - -**Keep the button on Queue and only reword the Settings row.** This preserves the earlier decision but leaves the composer with two submission paths for one draft under one setting. A user who prefers Steer still cannot get it by pointer, and the reworded row would have to document a keyboard-only scope that no other composer control shares. - -**Add a second running button, one per mode.** Both delivery modes become reachable by pointer without a hidden state, but the ordinary session has one primary seat that already alternates between Stop and Send; a permanent second control spends space and introduces a hierarchy the draft itself does not need. The single setting already expresses the user's default, and Cmd/Ctrl+Enter remains the per-message override. - -**Thread the mode through `InputActions.submit(mode)`.** Widening the public provide-channel face would let any session-scope slot pick a delivery mode, which no other consumer needs, and would move a composer presentation decision into the machine's public contract. The package-private `ComposerKeyboard.submit(mode)` already exists for exactly this purpose, so the button uses it. - -**Keep `resolveSubmitMode` as a closure on the inject face and add a separate `busyEnter` hook only for the label.** Two sources for one fact invite drift between what the label says and what the click does. Publishing the preference once and resolving it in the bar keeps label and delivery derived from the same value in the same render. - -## Consequences - -The setting governs every busy-state submission a user can trigger with a message, and the button announces which delivery it performs, so choosing Steer no longer produces a Queue row from the button beside the draft. Users who relied on the button as an always-Queue escape while their setting selected Steer now use Cmd/Ctrl+Enter for that. The running Send label changes for every user, including under the default Queue preference, which the Web e2e scenarios that click Send during a running turn account for; idle-session flows and one-shot subagent composers see no change. The archived running-draft note's clause that the pointer action ignores the preference is reversed here; its primary-seat, owner-block, and subagent-control decisions stand as shipped and are described by the `ui-conversation` README. diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md deleted file mode 100644 index 7062f60320..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: 繁忙态 Send 按钮跟随 Enter 设置 - -Status: implemented - -[English](2026-09-04-busy-send-button-follows-enter-setting.md) | 中文 - -## 问题 - -Web composer 为 agent(智能体)运行期间的提交只提供一个面向用户的选择:`ui-conversation.busyEnter` 设置在 Queue 与 Steer 之间选择。[运行中草稿取得主 Send 操作](../../archived/bug-fix/2026-08-20-running-draft-primary-send.md)(已归档)为运行中的草稿提供了指针 Send 按钮,有意让它不跟随该偏好,以避免一个只标注为 Send 的按钮携带不可见模式,并把每次点击都路由到公共的 `InputActions.submit()` 接口,而 `SessionInputShell.actions` 把该接口固定为 `'queue'`。用户在设置中选择 Steer 后,Enter 得到 Steer,同一草稿旁的按钮却得到 Queue,且按钮只标注为"发送消息"。composer 中没有任何内容解释这一分歧,设置行的标题和描述也只提到 Enter 键,因此该设置看起来像是失效,而不是有意只覆盖一部分。 - -## 决策 - -运行中的 Send 按钮按与 plain Enter 相同的模式投递。`InputBar` 每次渲染计算一次 `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)`,其中 `steeringAvailable` 与键盘路径使用同一个"普通 Session 或可继续 child"判定;用它通过 `ComposerKeyboard.submit(mode)` 执行主按钮点击,并且仅在点击会投递一条普通消息时用它决定主按钮标签:composer 运行中且可 steering、按钮可用(没有仍在上传的文件)、草稿非空、未被认领且不是将进入命令 adjudication 的 `/` 行。该状态把 `input.send.queue`("Queue message" / "排队发送")或 `input.send.steer`("Steer message" / "插话发送")同时用作 tooltip 与可访问名称;该位置仍为 Send 按钮的其余所有状态——空闲会话、one-shot child、锁定的 composer、可继续 child 的空草稿、带待上传附件的草稿,以及点击会执行命令而非投递消息的命令草稿——保留 `input.send`("Send message");普通运行中会话在空草稿或 owner block 时该位置显示的是 Stop。Cmd/Ctrl+Enter 仍解析为相反模式,空草稿下的加速手势仍对整个队列执行 steering(中途引导)。[可继续 subagent 中断 Agent Note](../feature/2026-08-06-continuable-subagent-interrupt.zh.md)以此投递方式描述 child 的 Send。 - -composer bar 的 inject 接口携带实时偏好,而不是解析闭包。`ComposerBarInjected.hooks.busyEnter` 发布 `ComposerSubmissionPolicy.busyEnter`,因此 bar 获得 `useBusyEnter` 选择器 hook,并在设置行或 Host 设置更新改变该值时重新渲染标签。`resolveSubmitMode` 是 `submission-policy.ts` 中导出的纯函数,显式接收偏好值;policy 类只保留 store 及其 Host 采纳与写回。 - -设置行重新命名以覆盖两种输入:"Send behavior while busy" / "繁忙时的发送行为",描述为 agent 运行时 Enter 与 Send 按钮的行为,并保留 Cmd/Ctrl+Enter 使用相反模式的说明。`busyEnter` 字段名、其 `queue` 默认值和 Host schema 均未改变,因此现有 `settings.yaml` 文档保持原有含义。 - -## 验证 - -`input-bar.client.spec.tsx` 断言运行中草稿的按钮在两种偏好下都按模式标注并以该模式提交,切换偏好 store 会在下一次点击前重新标注已挂载的按钮,空闲 Send 无论偏好如何都保留普通标签与 Queue 投递,可继续 subagent 的 Send 与普通 Session 遵循同一模式与标签,而其空草稿下的禁用按钮与 one-shot child 保留普通 Send,运行中的 `/` 行、已认领命令与带仍在上传文件的草稿也保留普通 Send。`submission-policy.client.spec.ts` 钉住 `resolveSubmitMode` 在偏好、运行状态、手势与 steering 可用性所有组合下的结果。`enter-behavior-row.client.spec.tsx` 与 `settings-chrome` ARIA golden 携带新的设置文案。无密钥的 `live-interactions` Web 场景在停住的运行中草稿上等待"Queue message",并断言此刻不存在"Send message"按钮,其 `running-draft.expected.md` golden 记录了新名称。 - -## 备选方案 - -**保持按钮使用 Queue,只改写设置行文案。** 这保留了先前决策,但让同一设置下的同一草稿拥有两条提交路径。偏好 Steer 的用户仍无法通过指针得到它,而改写后的设置行必须记录一种其他 composer 控件都不具备的仅键盘生效范围。 - -**增加第二个运行中按钮,每种模式一个。** 两种投递模式都可以通过指针到达且没有隐藏状态,但普通会话只有一个主操作位置,且已在 Stop 与 Send 之间交替;永久增加第二个控件会占用空间,并引入草稿本身不需要的层级。单一设置已经表达了用户默认值,Cmd/Ctrl+Enter 仍是逐条消息的覆盖手段。 - -**通过 `InputActions.submit(mode)` 传递模式。** 拓宽公共 provide 通道接口会让任何 session 作用域的 slot 都能选择投递模式,而没有其他消费者需要它,并且会把 composer 的呈现决策推入机器的公共契约。包内私有的 `ComposerKeyboard.submit(mode)` 正是为此存在,因此按钮直接使用它。 - -**在 inject 接口上保留 `resolveSubmitMode` 闭包,仅为标签另加一个 `busyEnter` hook。** 同一事实有两个来源,会让标签所说与点击所做之间产生偏差。只发布一次偏好并在 bar 中解析,可以让标签与投递在同一次渲染中源自同一个值。 - -## 影响 - -该设置约束用户能以消息触发的每一种繁忙态提交,且按钮会声明它执行哪种投递,因此选择 Steer 后不再会从草稿旁的按钮产生 Queue 行。此前在设置为 Steer 时依赖按钮作为始终 Queue 逃生口的用户,现在改用 Cmd/Ctrl+Enter。运行中的 Send 标签对每位用户都会变化,包括默认的 Queue 偏好下,运行中点击 Send 的 Web e2e 场景已相应处理;空闲会话流程和 one-shot subagent composer 没有变化。已归档的运行中草稿 Agent Note 中"指针操作忽略偏好"的条款在此被反转;其主操作位置、owner block 与 subagent 控件决策按已交付状态继续有效,并由 `ui-conversation` README 描述。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml deleted file mode 100644 index f08907aa62..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-continuous-client-recovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.md -/agent-note-continuous-client-connection-recovery: - en: f0fee6b344250d4c - zh: 713ea72ca8d6565a -/agent-note-continuous-client-connection-recovery/problem: - en: 70259bdedbf6b449 - zh: b0a3014c27483a09 -/agent-note-continuous-client-connection-recovery/decision: - en: db9ac3c08a3eaf0b - zh: 68c9ce30d8d5c7e2 -/agent-note-continuous-client-connection-recovery/alternatives-considered: - en: 2e94feca66d912af - zh: 8fc4e81b797d4553 -/agent-note-continuous-client-connection-recovery/consequences: - en: cd99c771043a23d5 - zh: 8559757fa8098fbe -/agent-note-continuous-client-connection-recovery/testing: - en: 39fdcfb5c6b52a96 - zh: 276de310ed1bbc39 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml deleted file mode 100644 index e0fdd0b674..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-nested-terminal-cards.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-nested-terminal-cards.md -/agent-note-nested-terminal-cards: - en: acc1dadeee5e1aeb - zh: a589c28cf1aff307 -/agent-note-nested-terminal-cards/problem: - en: 1bd9adbd90b66871 - zh: 0a87672dffc07bae -/agent-note-nested-terminal-cards/decision: - en: 075b10a41974411e - zh: cc3341afff14f61b -/agent-note-nested-terminal-cards/alternatives-considered: - en: c585eb14efb10514 - zh: c5a1cc87fc4edacc -/agent-note-nested-terminal-cards/consequences: - en: a7abcb5b95a020cc - zh: 27fe3216944de8c3 -/agent-note-nested-terminal-cards/verification: - en: 0133972e958fccce - zh: 52d4d3e29b7346e0 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml deleted file mode 100644 index 3bcf021adf..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-patch-plugin-file-urls.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-patch-plugin-file-urls.md -/agent-note-file-urls-for-inserted-patch-plugins: - en: 0da1360de4441566 - zh: aa617df23150096d -/agent-note-file-urls-for-inserted-patch-plugins/problem: - en: 68de71db96c5e4b5 - zh: c88db125cddb54a4 -/agent-note-file-urls-for-inserted-patch-plugins/decision: - en: 09bb824857b8fb60 - zh: 859da30ff296688c -/agent-note-file-urls-for-inserted-patch-plugins/alternatives-considered: - en: e53a86bc1c71a6ff - zh: 514b48c8734fca17 -/agent-note-file-urls-for-inserted-patch-plugins/consequences: - en: 9b9628919e52c617 - zh: 195935e5d905d81d diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml deleted file mode 100644 index 88960b1f51..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-pi-ai-upgrade-compatibility.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-pi-ai-upgrade-compatibility.md -/agent-note-pi-ai-upgrade-compatibility: - en: faa0207498bd36c7 - zh: 2c6c209849dcce32 -/agent-note-pi-ai-upgrade-compatibility/problem: - en: 0a106a9d74115ce5 - zh: a65945e32455944e -/agent-note-pi-ai-upgrade-compatibility/decision: - en: 978b93bba58cfaf9 - zh: 847360ba75d67abc -/agent-note-pi-ai-upgrade-compatibility/alternatives-considered: - en: bcfd595bb17c3cd6 - zh: b7d4346e9c50f95d -/agent-note-pi-ai-upgrade-compatibility/consequences: - en: 0b634c9d7567aa57 - zh: c8ae82694e5fae12 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml deleted file mode 100644 index 57f613df55..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-session-reference-model-budget.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-session-reference-model-budget.md -/agent-note-model-relative-session-reference-budgets: - en: 6d4d5e9c678c93cb - zh: 7023bca0480a27df -/agent-note-model-relative-session-reference-budgets/problem: - en: 2bc7bc4083a96c5d - zh: d0d290871def4abf -/agent-note-model-relative-session-reference-budgets/decision: - en: e686c4ed659fc44c - zh: 41484c0431a53177 -/agent-note-model-relative-session-reference-budgets/alternatives-considered: - en: 77148c41d109895a - zh: dba534347f0e0877 -/agent-note-model-relative-session-reference-budgets/consequences: - en: 6ee6a4b3229ff378 - zh: 9e30e71ac15da8c4 diff --git a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml deleted file mode 100644 index 05f653cf57..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-session-reference-spill-reuse.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-05-session-reference-spill-reuse.md -/agent-note-reuse-spill-storage-for-truncated-session-references: - en: 9691f4ba51c41f8b - zh: 5c1719f1ef901639 -/agent-note-reuse-spill-storage-for-truncated-session-references/problem: - en: 50a1ef10604acb5f - zh: 128abfd3e43740dc -/agent-note-reuse-spill-storage-for-truncated-session-references/decision: - en: 058598a9e6c65fcf - zh: 7760170bbc53396d -/agent-note-reuse-spill-storage-for-truncated-session-references/alternatives-considered: - en: fccf1c25876b92a0 - zh: 7d5c7c3d240132c3 -/agent-note-reuse-spill-storage-for-truncated-session-references/consequences: - en: 2bff957549afe59b - zh: 30c6be54168fe183 -/agent-note-reuse-spill-storage-for-truncated-session-references/verification: - en: e015a599fc71a65f - zh: 0812b5b9a64a8673 -/agent-note-reuse-spill-storage-for-truncated-session-references/related-decisions: - en: 7a53914026e82df7 - zh: 3c802bbf63117485 diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml deleted file mode 100644 index 1e5d9065c2..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-environment-prompt-suffix.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-06-environment-prompt-suffix.md -/agent-note-environment-facts-follow-reusable-prompt-instructions: - en: 8677e5d20233627f - zh: 684772490164fbdf -/agent-note-environment-facts-follow-reusable-prompt-instructions/problem: - en: 7b20207cd4dc1583 - zh: ca5014aa437a5239 -/agent-note-environment-facts-follow-reusable-prompt-instructions/decision: - en: 0f28a61d659c0f4d - zh: d2ef431d15a54d57 -/agent-note-environment-facts-follow-reusable-prompt-instructions/alternatives-considered: - en: 9fb4ff8a8786ce2d - zh: 25820720365b489f -/agent-note-environment-facts-follow-reusable-prompt-instructions/consequences: - en: 212b29160c254854 - zh: e6a1ea253e8f7780 -/agent-note-environment-facts-follow-reusable-prompt-instructions/testing: - en: d5132186efc6afcd - zh: e5cc036c7c8ed68e diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.i18n.yaml new file mode 100644 index 0000000000..0b35f92fb4 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-09-06-packed-install-private-npm-cache.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.md +/agent-note-packed-install-verification-owns-its-npm-cache: + en: 54d54ee850476cb9 + zh: 8d666e40eb2372f0 +/agent-note-packed-install-verification-owns-its-npm-cache/problem: + en: 5cd500387242a7cc + zh: 75de2c0049631634 +/agent-note-packed-install-verification-owns-its-npm-cache/decision: + en: 8a9ead906e1148d6 + zh: 6a5b911fc5849be2 +/agent-note-packed-install-verification-owns-its-npm-cache/alternatives-considered: + en: b2637475da22cec0 + zh: 5c0e3979caf44461 +/agent-note-packed-install-verification-owns-its-npm-cache/consequences: + en: a123744738fac02f + zh: 54bda8e161cb6e85 +/agent-note-packed-install-verification-owns-its-npm-cache/verification: + en: e37107020148c23e + zh: 94227a591fad9f17 diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.md b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.md new file mode 100644 index 0000000000..ce25230c8f --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.md @@ -0,0 +1,29 @@ +# Agent Note: Packed-install verification owns its npm cache + +Status: implemented + +English | [中文](2026-09-06-packed-install-private-npm-cache.zh.md) + +## Problem + +The [packed-install verifier](../../../../scripts/release/verify-packed-install.ts) removes its temporary consumer but an npm install using the ambient cache leaves tarball content and logs outside that cleanup. Repeated release rehearsals can therefore grow the shared runner user cache even when every consumer is removed. A full root filesystem can fail unrelated CI jobs. + +## Decision + +The verifier passes `--cache /.npm-cache` only to its npm install invocation. The explicit argument overrides npm environment and user-configuration cache settings without changing the environment of the installed executable. The existing `finally` removes this cache together with the consumer after success, an install failure, or an executable failure. + +The [release sequence decision](../process/2026-08-10-npm-release-sequences.md) still owns artifact selection and publication. This decision only adds cache ownership to the verifier; it does not change other npm consumers. + +## Alternatives considered + +- **Delete the shared user cache:** unrelated jobs also own its contents. The verifier must not delete shared host state. +- **Change global `TMPDIR` or npm configuration:** that affects processes outside this invocation and is unnecessary when the consumer already has an owned temporary directory. +- **Keep a persistent verifier cache:** that retains cross-run downloads but requires a separate retention policy. Invocation ownership accepts repeated downloads instead. + +## Consequences + +This prevents this verifier from adding content to the ambient npm cache; it neither reclaims existing host cache contents nor guarantees that disk exhaustion from other producers stops. External dependencies can require fresh downloads on each invocation. A forced termination that prevents `finally` from running can leave the consumer and its cache behind; this change adds no crash-recovery cleanup. + +## Verification + +[The focused regression](../../../../scripts/release/verify-packed-install.spec.ts) runs the real verifier and npm offline against a tiny local tarball. A lifecycle script records actual private `_cacache` files before cleanup. The tests verify consumer removal after success, npm install failure, and installed-entry failure, while inherited environment and user-config cache canaries remain unchanged. Removing the cache argument makes the private-cache assertion fail. The executable test follows the POSIX release runner path; it does not exercise Windows command-shim resolution. diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.zh.md b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.zh.md new file mode 100644 index 0000000000..7e495439e4 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-06-packed-install-private-npm-cache.zh.md @@ -0,0 +1,29 @@ +# Agent Note: 打包安装验证拥有独立 npm 缓存 + +Status: implemented + +[English](2026-09-06-packed-install-private-npm-cache.md) | 中文 + +## 问题 + +[打包安装验证器](../../../../scripts/release/verify-packed-install.ts) 会删除临时消费目录,但使用环境配置缓存的 npm install 会把 tarball 内容和日志留在清理范围之外。因此,即使每个消费目录均被删除,重复的发布演练仍可能扩大共享 runner 用户缓存。根文件系统满载会使无关 CI 作业失败。 + +## 决策 + +验证器仅向自己的 npm install 调用传入 `--cache /.npm-cache`。显式参数覆盖 npm 环境变量和用户配置中的缓存设置,不改变已安装可执行文件的环境。现有 `finally` 在成功、安装失败或可执行文件失败后将此缓存与消费目录一起删除。 + +[发布序列决策](../process/2026-08-10-npm-release-sequences.zh.md) 仍负责产物选择与发布。本决策仅为验证器增加缓存所有权,不改变其他 npm 消费方。 + +## 曾考虑的替代方案 + +- **删除共享用户缓存:**其他作业也拥有其中的内容。验证器不得删除共享宿主状态。 +- **修改全局 `TMPDIR` 或 npm 配置:**这会影响本次调用之外的进程,而消费目录已有独立临时目录,因此没有必要。 +- **保留持久化验证缓存:**这能跨运行复用下载,但需要独立保留策略。调用级所有权选择接受重复下载。 + +## 后果 + +这会阻止该验证器向环境配置的 npm 缓存添加内容;既不会回收现有宿主缓存,也不保证其他来源导致的磁盘耗尽停止。外部依赖可能需要在每次调用时重新下载。若强制终止使 `finally` 无法运行,消费目录及其缓存仍可能残留;本改动不增加崩溃恢复清理。 + +## 验证 + +[定向回归测试](../../../../scripts/release/verify-packed-install.spec.ts) 使用微型本地 tarball 离线运行真实验证器和 npm。生命周期脚本在清理前记录私有 `_cacache` 中的实际文件。测试验证成功、npm 安装失败和已安装入口失败后消费目录均被删除,同时继承环境和用户配置指定缓存中的探针文件保持不变。移除缓存参数会使私有缓存断言失败。可执行测试沿用 POSIX 发布 runner 路径,不覆盖 Windows 命令垫片解析。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml deleted file mode 100644 index be90b83299..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-windows-python-console-spawn-wait.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-06-windows-python-console-spawn-wait.md -/agent-note-wait-for-the-windows-python-console-runtime: - en: 97ffbd17152279cc - zh: 9a223670f273471b -/agent-note-wait-for-the-windows-python-console-runtime/problem: - en: 26ea75714d6fbbea - zh: 49441436e362a868 -/agent-note-wait-for-the-windows-python-console-runtime/decision: - en: 6ef15038eb5e0786 - zh: 92e319fbb8133d52 -/agent-note-wait-for-the-windows-python-console-runtime/alternatives-considered: - en: 2cc100a4b8724d40 - zh: 6ba47c76f304a460 -/agent-note-wait-for-the-windows-python-console-runtime/consequences: - en: ec5c2a7082012ed5 - zh: 32027ea20bd32acf diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml deleted file mode 100644 index 130a3812c0..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-pi-ai-settings-catalog-recovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-07-pi-ai-settings-catalog-recovery.md -/agent-note-repairable-pi-ai-settings-after-catalog-changes: - en: 69bf6edd0515636c - zh: cb5039a98dc8b736 -/agent-note-repairable-pi-ai-settings-after-catalog-changes/problem: - en: acc6a5afd829fde0 - zh: a2173b59b670efe2 -/agent-note-repairable-pi-ai-settings-after-catalog-changes/decision: - en: 37034d76d7542f46 - zh: a6423ba488fe272c -/agent-note-repairable-pi-ai-settings-after-catalog-changes/alternatives-considered: - en: 1dcabe193953fa1b - zh: 02263349cdbdc843 -/agent-note-repairable-pi-ai-settings-after-catalog-changes/consequences: - en: d2f89ce8cec55c6e - zh: 20fab7854366ae05 -/agent-note-repairable-pi-ai-settings-after-catalog-changes/testing: - en: 30c8172b88231251 - zh: 54aee9d1dffa8566 diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml deleted file mode 100644 index 53a10eeede..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-typert-package-local-forwarding-imports.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-07-typert-package-local-forwarding-imports.md -/agent-note-follow-package-local-forwarding-modules-in-typert-references: - en: ebacf2fc1c764589 - zh: c14b1752a01b2775 -/agent-note-follow-package-local-forwarding-modules-in-typert-references/problem: - en: 0ed0703cdd876132 - zh: ab759d1a2137bdd8 -/agent-note-follow-package-local-forwarding-modules-in-typert-references/decision: - en: e828847b069ef3f8 - zh: 95e9425c02dd8215 -/agent-note-follow-package-local-forwarding-modules-in-typert-references/alternatives-considered: - en: 49a0984c31c2b993 - zh: fb6d093474130a75 -/agent-note-follow-package-local-forwarding-modules-in-typert-references/consequences: - en: 7e196e1cffcb31e1 - zh: 7d096507578c1731 diff --git a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml deleted file mode 100644 index 6088c5898e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-win32-picker-foreground-alt-key.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-07-win32-picker-foreground-alt-key.md -/agent-note-foreground-activation-for-the-win32-picker-via-a-synthesized-alt-press: - en: bbd08341a635d6a8 - zh: 94d4ea2412237630 -/agent-note-foreground-activation-for-the-win32-picker-via-a-synthesized-alt-press/problem: - en: b21a34182868057e - zh: 54cb143a2a11479b -/agent-note-foreground-activation-for-the-win32-picker-via-a-synthesized-alt-press/decision: - en: d4cff7cc9b5c9379 - zh: a8f3e238799a18ce -/agent-note-foreground-activation-for-the-win32-picker-via-a-synthesized-alt-press/alternatives-considered: - en: dd3491b3b760f330 - zh: 81ca7b6603379713 -/agent-note-foreground-activation-for-the-win32-picker-via-a-synthesized-alt-press/consequences: - en: 6e7c7c46cc48a95d - zh: bbed46fb61e53700 diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml deleted file mode 100644 index 7fc8e02b71..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-built-bundle-css-exemption.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md -/agent-note-built-bundle-exemption-for-a-failing-stylesheet: - en: 7eea80c07cea261d - zh: 936523023c0b527d -/agent-note-built-bundle-exemption-for-a-failing-stylesheet/problem: - en: ca5c97dd0ad51b66 - zh: 8491ee495b0c625e -/agent-note-built-bundle-exemption-for-a-failing-stylesheet/decision: - en: cd272aa1bfd34a14 - zh: d184be63053f73b8 -/agent-note-built-bundle-exemption-for-a-failing-stylesheet/alternatives-considered: - en: 7f0eb995b7f00d73 - zh: aaf7b18f7bb2c20d -/agent-note-built-bundle-exemption-for-a-failing-stylesheet/consequences: - en: 767857f0bbd93ca8 - zh: f49bf34e304a7dee diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml deleted file mode 100644 index f98968b567..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-deepseek-image-token-calculator-v41.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-10-deepseek-image-token-calculator-v41.md -/agent-note-deepseek-image-token-estimator-on-the-v41-calculator: - en: 3e0b96f522d5e2c9 - zh: a05009e8f9cbe5d4 -/agent-note-deepseek-image-token-estimator-on-the-v41-calculator/problem: - en: c47a183a3192d1ae - zh: bbc157ff44423dd3 -/agent-note-deepseek-image-token-estimator-on-the-v41-calculator/decision: - en: 17f8c045d3222266 - zh: aead3e0de01980d9 -/agent-note-deepseek-image-token-estimator-on-the-v41-calculator/alternatives-considered: - en: 8f5bb52220ead193 - zh: f267fda075b19963 -/agent-note-deepseek-image-token-estimator-on-the-v41-calculator/consequences: - en: 0a41bb795e221c3f - zh: b4840b79c78691cf diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml deleted file mode 100644 index eb27fa1ee7..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-deepseek-v41-request-image-projection.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-10-deepseek-v41-request-image-projection.md -/agent-note-deepseek-request-images-on-the-published-token-grid: - en: 67401dae42c79c06 - zh: 19ab8d82fec9ec25 -/agent-note-deepseek-request-images-on-the-published-token-grid/problem: - en: 3c86cfcbd6895dc5 - zh: 68cb8bd4c1ef2d24 -/agent-note-deepseek-request-images-on-the-published-token-grid/decision: - en: 0c1257b4fd77d7ad - zh: b1134f24c8f58ded -/agent-note-deepseek-request-images-on-the-published-token-grid/alternatives-considered: - en: c5d3470b62129fde - zh: 26c9abf1e4a4f49f -/agent-note-deepseek-request-images-on-the-published-token-grid/consequences: - en: 95bc8d0d09928dee - zh: 090292fa154e854f diff --git a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml deleted file mode 100644 index aa5f93f3c7..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-incremental-terminal-retention.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-11-incremental-terminal-retention.md -/agent-note-incremental-terminal-retention: - en: b00300b3ace789da - zh: c267be719b3c8814 -/agent-note-incremental-terminal-retention/problem: - en: 0c3232a4a2429315 - zh: c6b3c6224bc9031d -/agent-note-incremental-terminal-retention/decision: - en: 43bb3c2d0d561231 - zh: 8dacb0720f28d490 -/agent-note-incremental-terminal-retention/measurement-design: - en: 925815f8ae326179 - zh: 188ee3f695ece8de -/agent-note-incremental-terminal-retention/measurement-design/local-reference-measurements: - en: a049c0a118648de7 - zh: 2c31044ff11b9029 -/agent-note-incremental-terminal-retention/alternatives-considered: - en: 0af1fcbc28320b5b - zh: 8e1cfc843821dd77 -/agent-note-incremental-terminal-retention/consequences: - en: c60008ba652dffd2 - zh: 3210329182096f39 diff --git a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml deleted file mode 100644 index 192ea7ceec..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-linux-scope-direct-kill-settlement.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-12-linux-scope-direct-kill-settlement.md -/agent-note-join-direct-termination-before-rejecting-linux-scope-cleanup: - en: 6a72b66b6d0cf21b - zh: be432bfebb8e9919 -/agent-note-join-direct-termination-before-rejecting-linux-scope-cleanup/problem: - en: be042a28c1154171 - zh: 7b8356c9dcb159be -/agent-note-join-direct-termination-before-rejecting-linux-scope-cleanup/decision: - en: 75b071e48715ff29 - zh: 3f1d710f54ed3b1b -/agent-note-join-direct-termination-before-rejecting-linux-scope-cleanup/alternatives-considered: - en: ea3f33dd6c9bab88 - zh: 6f274fcd86b231a7 -/agent-note-join-direct-termination-before-rejecting-linux-scope-cleanup/consequences: - en: 4f7e7535b49b22fe - zh: 10447b3c42a341a2 diff --git a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml deleted file mode 100644 index baae5ed39f..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-13-partitioned-coverage-location-canonicalization.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-13-partitioned-coverage-location-canonicalization.md -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge: - en: 9346bcf9fc59bea8 - zh: 170ebd969ac038bd -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/problem: - en: c2f8792fb6ec46d1 - zh: cc01e2e2ff14f367 -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/decision: - en: 07f37eb8430ef8ee - zh: bc2910040b90291c -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/divergent-statement-maps-across-vite-environments: - en: 4f6d088fd7e272fc - zh: d44463ee8485b060 -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/testing: - en: 59a8515f97eb4c63 - zh: 86d655d454da2c63 -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/alternatives-considered: - en: 8c7efd287cf0e56b - zh: 2ed0477918e10f4e -/agent-note-canonicalize-partition-coverage-locations-before-the-blob-merge/consequences: - en: c7a2e99922697cb6 - zh: 0c98a57b00263bf8 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.i18n.yaml deleted file mode 100644 index e671a8aa27..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-chat-presentation-defaults.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.md -/agent-note-keep-chat-presentation-independent-of-trajectory-inspection: - en: 72d5bc0911e0bc3f - zh: 1fc58f89bdc53b56 -/agent-note-keep-chat-presentation-independent-of-trajectory-inspection/problem: - en: 2102a58c1baafe54 - zh: 939872d86e013f94 -/agent-note-keep-chat-presentation-independent-of-trajectory-inspection/decision: - en: f77e394352fe8d8e - zh: 7c4c9b066874dc9b -/agent-note-keep-chat-presentation-independent-of-trajectory-inspection/alternatives-considered: - en: f4c1b9c9998e3355 - zh: f684e39ffabce5e6 -/agent-note-keep-chat-presentation-independent-of-trajectory-inspection/consequences: - en: bb15d68e46b10b8e - zh: 8506ab5775d04fe8 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.md b/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.md deleted file mode 100644 index 6703a14e2a..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.md +++ /dev/null @@ -1,25 +0,0 @@ -# Agent Note: Keep Chat presentation independent of Trajectory inspection - -Status: implemented - -English | [中文](2026-09-14-chat-presentation-defaults.zh.md) - -## Problem - -Trajectory inspection benefits from exposing complete recorded reasoning. Applying that default to Chat expands the live transcript during reasoning and changes its height when an answer or Tool call arrives. The Trajectory inspection change also added historical first-token recovery to Chat without a separate Chat behavior decision. - -## Decision - -[Chat](../../../../packages/client/ui-chat/README.md#turn-process-folding) starts each reasoning row collapsed and retains the reader's manual disclosure choice through subsequent output and settlement. Settlement retires the observed live chunks and rebuilds Chat reply nodes from durable events without recovering first-token time from embedded streams. Consequently, completed-turn TTFT and decoding speed are absent after live settlement as well as after reopening history. Turn-level process folding remains independently owned. - -[Trajectory inspection](../feature/2026-09-09-ptc-trajectory-code-inspection.md) keeps its expanded reasoning default, recorded timing, JSON controls, and PTC code inspector. The [compact stream readers](../architecture/2026-09-06-embedded-stream-record-readers.md) remain available to Trajectory and other consumers. These decisions partially supersede the Chat presentation additions while preserving both notes' independent rationale. - -## Alternatives considered - -**Keep Chat's automatic expansion and historical timing recovery.** These change Chat behavior beyond the requested Trajectory inspection work. Reintroducing either requires a separate Chat product decision and its own verification. - -**Revert the entire inspection change.** That would remove the requested Trajectory behavior along with the unintended Chat changes. - -## Consequences - -Chat reasoning requires a click to inspect in full. Elapsed turn time and the independently projected Session Stats remain available. Assembler tests distinguish transient retirement at live settlement from reopening durable history; browser replay verifies the completed-turn timing dialog before and after reload. Component tests cover collapsed streaming and reasoning-only replies and manual disclosure across answer and Tool-call arrival. Trajectory tests retain its separate defaults and timing. diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.zh.md b/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.zh.md deleted file mode 100644 index 67ca433339..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-14-chat-presentation-defaults.zh.md +++ /dev/null @@ -1,25 +0,0 @@ -# Agent Note: 保持 Chat 展示与 Trajectory 查看独立 - -Status: implemented - -[English](2026-09-14-chat-presentation-defaults.md) | 中文 - -## 问题 - -Trajectory 查看适合直接展示完整的已记录思考。将该默认行为应用到 Chat 会在思考期间展开实时对话,并在回答或工具调用到来时改变其高度。Trajectory 查看改动还为 Chat 增加了历史首 token 计时恢复,却没有独立的 Chat 行为决策。 - -## 决策 - -[Chat](../../../../packages/client/ui-chat/README.zh.md#turn-process-folding) 的每个思考行初始都折叠,后续输出和结算保留用户手动选择的展开状态。结算会移除观测到的实时 chunk,并从持久事件重建 Chat 回复节点,不从内嵌流恢复首 token 时间。因此,实时结算后和重新打开历史后,已完成轮次都不显示 TTFT 和解码速度。轮次级过程折叠仍独立维护。 - -[Trajectory 查看](../feature/2026-09-09-ptc-trajectory-code-inspection.zh.md) 保留思考默认展开、已记录计时、JSON 控件和 PTC 代码查看器。[紧凑流读取器](../architecture/2026-09-06-embedded-stream-record-readers.zh.md) 仍供 Trajectory 和其他消费方使用。这些决策部分取代了 Chat 展示增量,同时保留两份记录各自独立的理由。 - -## 考虑过的替代方案 - -**保留 Chat 自动展开和历史计时恢复。** 这些改变了所请求的 Trajectory 查看工作之外的 Chat 行为。重新引入任一行为都需要独立的 Chat 产品决策及相应验证。 - -**撤回整个查看改动。** 这会在撤回意外 Chat 改动的同时移除所请求的 Trajectory 行为。 - -## 影响 - -Chat 思考需要点击才能查看全文。轮次总耗时和独立投影的 Session Stats 仍可用。组装器测试区分实时结算时移除临时数据与重新打开持久历史;浏览器回放验证重载前后的已完成轮次计时对话框。组件测试覆盖流式与仅思考回复的默认折叠,以及回答和工具调用到来时的手动展开状态。Trajectory 测试保留其独立默认值和计时。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.i18n.yaml deleted file mode 100644 index 8a44818fd8..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-web-diff-context.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-14-web-diff-context.md -/agent-note-web-diff-cards-compare-contextual-content: - en: 2dc5ae1577ba6173 - zh: 0b580f4e70eddc72 -/agent-note-web-diff-cards-compare-contextual-content/problem: - en: 73aa63782984835f - zh: 41982214968dcca8 -/agent-note-web-diff-cards-compare-contextual-content/decision: - en: d8c9e2af5843c87d - zh: 5487925fa1a66da8 -/agent-note-web-diff-cards-compare-contextual-content/alternatives-considered: - en: b5d00cc913db27c9 - zh: ef72c91cff5ebcce -/agent-note-web-diff-cards-compare-contextual-content/measurement: - en: 839be054edf51392 - zh: 3d2a51e634b39eb2 -/agent-note-web-diff-cards-compare-contextual-content/consequences: - en: 1900b6984a59765c - zh: 8f3755fcdf8f2562 diff --git a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml deleted file mode 100644 index 4816998bc8..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-windows-lock-release-probe.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-14-windows-lock-release-probe.md -/agent-note-windows-lock-release-before-contention-probing: - en: ec7e0474bd96df26 - zh: 20a70a6284cb0269 -/agent-note-windows-lock-release-before-contention-probing/problem: - en: cc9630ce8e232e8a - zh: 16f3541a58eac0ef -/agent-note-windows-lock-release-before-contention-probing/decision: - en: 9230417f2af237d4 - zh: 6fbef7a6d6e1e4d0 -/agent-note-windows-lock-release-before-contention-probing/alternatives-considered: - en: ba6d9e4c01c8f25b - zh: 8899bcf11d45b258 -/agent-note-windows-lock-release-before-contention-probing/consequences: - en: fc268a94ceeb3a96 - zh: b14be62df4a4bfad diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.i18n.yaml index 196679568b..d21312201c 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.i18n.yaml @@ -21,5 +21,5 @@ en: 4c16904319ae2745 zh: 06bf9126b7f504ec /agent-note-keep-accepted-session-presentation-across-list-refreshes/related-decisions: - en: 99d6b7e450a8dc09 - zh: 46a6df4730514668 + en: ac8ca7380845aa3f + zh: 12980beee33ad336 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.md b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.md index 7213636e32..d38ba4aedc 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.md +++ b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.md @@ -40,4 +40,4 @@ Manager tests cover refresh without a turn, running-to-idle updates, object repl ## Related decisions -The [scope and provisioning decision](../architecture/2026-07-25-web-client-session-scope-and-provide-channel.md) owns Session scope, creation/reuse, and the visible blank row. The [Session/Conversation ownership decision](../architecture/2026-08-20-client-session-conversation-ownership.md) owns transport, assembly, and snapshot contents. +The [scope and provisioning reference](../architecture/2026-09-15-client-session-references.md) owns Session scope, creation/reuse, and the visible blank row. The [Session/Conversation ownership decision](../architecture/2026-08-20-client-session-conversation-ownership.md) owns transport, assembly, and snapshot contents. diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.zh.md b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.zh.md index 4b50340fea..51272673a3 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-15-client-session-blank-reconciliation.zh.md @@ -40,4 +40,4 @@ Manager 测试覆盖无轮次刷新、运行转空闲、对象替换、迟到受 ## 相关决策 -[作用域与供给决策](../architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md) 拥有 Session 作用域、创建/复用及可见 blank 行。[Session/Conversation 归属决策](../architecture/2026-08-20-client-session-conversation-ownership.zh.md) 拥有传输、装配及快照内容。 +[作用域与供给参考](../architecture/2026-09-15-client-session-references.zh.md) 拥有 Session 作用域、创建/复用及可见 blank 行。[Session/Conversation 归属决策](../architecture/2026-08-20-client-session-conversation-ownership.zh.md) 拥有传输、装配及快照内容。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml deleted file mode 100644 index a4a8a32d4e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-linear-session-list-cache.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-15-linear-session-list-cache.md -/agent-note-linear-session-list-cache-reconciliation: - en: 7b6289de90982e71 - zh: 0c020de607402bbd -/agent-note-linear-session-list-cache-reconciliation/problem: - en: b94ff168ba0310a8 - zh: 8d9fe4af8b047c86 -/agent-note-linear-session-list-cache-reconciliation/decision: - en: 1d85980590014837 - zh: 2e03befc8ac84f0e -/agent-note-linear-session-list-cache-reconciliation/alternatives-considered: - en: 2085e3ccec11084a - zh: 55f5f5156d03e20c -/agent-note-linear-session-list-cache-reconciliation/consequences: - en: 54f1788954da5704 - zh: dad3478c7f3b46f6 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml deleted file mode 100644 index 312f8cf353..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-messages-v1-base-url.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-15-messages-v1-base-url.md -/agent-note-exact-v1-recognition-for-messages-base-urls: - en: 8e65836ea970f866 - zh: 54ef0765d2c39be8 -/agent-note-exact-v1-recognition-for-messages-base-urls/problem: - en: d6d0baca328ce1cd - zh: 152d5c7793674ed2 -/agent-note-exact-v1-recognition-for-messages-base-urls/decision: - en: 037fe11400e2e666 - zh: 5941d8f7364e45b8 -/agent-note-exact-v1-recognition-for-messages-base-urls/alternatives-considered: - en: 5615cd72ec115068 - zh: 772d9a557d46c43d -/agent-note-exact-v1-recognition-for-messages-base-urls/consequences: - en: 90a3b42b98cf99e0 - zh: 50e45634f1bd5ecb diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.i18n.yaml deleted file mode 100644 index da4262d0ea..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-trajectory-attachment-presentation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.md -/agent-note-consistent-trajectory-attachment-presentation: - en: 44651b6372b8e74e - zh: 8e1df22f6c969427 -/agent-note-consistent-trajectory-attachment-presentation/problem: - en: 3a02d475408db611 - zh: 49ef7f0e91917e13 -/agent-note-consistent-trajectory-attachment-presentation/decision: - en: 9a37a61d84813db3 - zh: 546cf3d1734289ee -/agent-note-consistent-trajectory-attachment-presentation/decision/ownership: - en: d453afd765988e76 - zh: eeb4c7d40fe578dc -/agent-note-consistent-trajectory-attachment-presentation/alternatives-considered: - en: f32a7ff301caf058 - zh: fbea3a171a300aef -/agent-note-consistent-trajectory-attachment-presentation/consequences: - en: da70caa5b98ac85d - zh: 52a5379c3adaa62f -/agent-note-consistent-trajectory-attachment-presentation/verification: - en: 145819ab133854c3 - zh: f9a440d57c12a257 diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.md b/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.md deleted file mode 100644 index 03d737d67e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.md +++ /dev/null @@ -1,55 +0,0 @@ -# Agent Note: Consistent Trajectory attachment presentation - -Status: implemented - -English | [中文](2026-09-15-trajectory-attachment-presentation.zh.md) - -## Problem - -A user message can contain text, images, and ordinary files. Trajectory omitted image counts beside text, left ordinary files out of Preview, and mixed file JSON with rendered images in Raw. Readers could not identify the complete attachment set consistently across the ledger and inspector (Issue #4356). - -## Decision - -Trajectory presents every attachment occurrence while keeping visual inspection separate from inspection of recorded fields. - -| Location | Behavior | -|---|---| -| Ledger | Separate nonzero image and ordinary-file counts accompany the text excerpt, including messages without text. Each record stays on one compact line. | -| Summary and Preview | Message text precedes one ordered attachment list. Both tabs use the same renderer; Summary retains its compact spacing and scroll container. | -| Attachment list | Equal-width compact rows share spacing and corners. A file-type icon or contained image thumbnail accompanies the recorded name and available metadata. Repeated references retain their occurrence order. | -| Image interaction | The complete image fits inside a 48px square thumbnail without cropping. Activation opens the existing image viewer, with keyboard access. Loading and retry icons fit the thumbnail and expose localized tooltips and accessible names. | -| Raw | Image and file blocks use initially collapsed metadata disclosures with complete recorded fields. Original content-block order and unrendered text remain intact; generated display labels do not enter raw fields. | - -Recorded filenames take precedence over localized numbered image labels. Metadata comes from recorded fields or the filename extension; zero-byte sizes remain visible. Visually truncated names retain their complete accessible text and tooltip. Messages without text still expose all attachments inside the inspector's scroll area. - -### Ownership - -[ui-trajectory](../../../../packages/client/ui-trajectory/README.md) owns counts, ordered attachment projection, and inspector composition. Its view data carries typed image and file references separately from the serialized content used by Raw, so display code does not recover file metadata by parsing JSON. - -[ui-attachment](../../../../packages/client/ui-attachment/README.md) owns image loading and the viewer through the existing image slot. [ui-conversation](../../../../packages/client/ui-conversation/README.md) owns the per-session image cache: Chat and Trajectory share one authorized read and browser URL per attachment. Thumbnail sizing and optional display labels affect presentation only; loading still receives the original reference. Chat and tool-result gallery sizing retain their existing behavior. - -This decision uses existing durable attachment references without changing upload, storage, model requests, or the Session format. Ordinary files expose metadata without a content viewer or download action. Product labels use typed locale dictionaries, and styles use the existing semantic tokens. - -The [generic file upload decision](../feature/2026-08-26-generic-file-upload.md) owns upload, storage, admission, and ordered message attachments; this record owns Trajectory presentation. The [unreadable attachment quarantine proposal](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.md) concerns model-request recovery independently of thumbnail loading feedback. - -## Alternatives considered - -**Keep large inline images as the default inspector presentation.** This makes one image immediately readable, but a tall image displaces attachment names and message text in the narrow inspector. Compact rows with explicit image activation provide a consistent overview of mixed attachments. - -**Use two-column attachment cards.** A grid can make thumbnails more prominent on wide panels, but it leaves less room for long filenames and requires a different narrow-panel arrangement. A single equal-width list accommodates both panel widths. - -**Keep file JSON and image previews together in Raw.** This assigns different meanings to the same tab for two attachment kinds. Metadata disclosures let readers inspect recorded fields while Preview owns visual image presentation. - -## Consequences - -The shared attachment renderer keeps names, order, metadata, and image actions consistent between Summary and Preview. Grouping attachments after message text makes the complete set easy to scan; Raw preserves interleaving when readers need the original block sequence. Repeated references remain separate rows even when image loading shares a cache entry. - -Compact thumbnails require opening the viewer to read detailed diagrams. Missing metadata stays absent, and image-read failures retain a retry control without hiding ordinary files. Changes to the shared image slot also affect Chat and tool results, so their gallery behavior and cache reuse remain regression requirements. - -## Verification - -[Projection tests](../../../../packages/client/ui-trajectory/tests/layout.client.spec.tsx) cover mixed, attachment-only, empty-text, and repeated occurrences. [Inspector tests](../../../../packages/client/ui-trajectory/tests/table.client.spec.tsx) cover both locales, unnamed images, long filenames, zero-byte files, ordered lists, and complete Raw fields. [Image tests](../../../../packages/client/ui-attachment/tests/message-image.client.spec.tsx) cover thumbnail labels, loading, retry, and the existing viewer. - -The recorded [mixed-upload browser scenario](../../../../apps/web/tests/file-upload-round.e2e.ts) compares Summary and Preview, verifies keyboard activation and focus restoration, checks collapsed and expanded Raw content, and repeats inspection after reload. Its [Trajectory expectation](../../../../snapshots/web/file-upload-round/trajectory.expected.md) accompanies the unchanged Session fixture. The [assembled image-cache test](../../../../apps/web/tests/trajectory-image-display.expected.e2e.ts) also checks reuse between Chat and Trajectory. - -Layout acceptance covers narrow and wide panels, both themes and locales, complete thumbnail containment, and reachable final attachments. The [GIF workflow](../../../skills/record-browser-gif/SKILL.md) requires review evidence from a clean committed tree and one real-server, real-model session showing mixed submission, ledger counts, Summary, Preview image activation, and expanded Raw metadata. Issue #4356 and its linked PR track executed checks and demonstration evidence. diff --git a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.zh.md b/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.zh.md deleted file mode 100644 index 55c63bf3d9..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-15-trajectory-attachment-presentation.zh.md +++ /dev/null @@ -1,55 +0,0 @@ -# Agent Note: 统一 Trajectory 附件展示 - -Status: implemented - -[English](2026-09-15-trajectory-attachment-presentation.md) | 中文 - -## 问题 - -用户消息可以包含文本、图片和普通文件。Trajectory 曾在文本旁省略图片数量,在预览中遗漏普通文件,并在原始内容中混排文件 JSON 与渲染后的图片。读者无法在列表和详情面板中一致地确认完整附件集合(Issue #4356)。 - -## 决定 - -Trajectory 展示每次附件出现的记录,并分别提供视觉预览与已记录字段检查。 - -| 位置 | 行为 | -|---|---| -| 列表 | 文本摘要旁分别显示非零的图片数量与普通文件数量,无正文的消息也显示计数。每条记录保持为紧凑单行。 | -| 概述与预览 | 消息正文之后展示一个有序附件列表。两个标签页使用相同的渲染器;概述保留紧凑间距和滚动容器。 | -| 附件列表 | 等宽的紧凑行使用一致的间距和圆角。文件类型图标或完整缩放的图片缩略图旁展示已记录的名称与可用元数据。重复引用保留各次出现的顺序。 | -| 图片交互 | 图片完整缩放到 48px 方形缩略图内,不裁剪。激活时打开现有图片查看器,保留键盘访问。加载与重试图标适配缩略图尺寸,并提供本地化的工具提示和可访问名称。 | -| 原始内容 | 图片块和文件块使用默认收起的元数据折叠区,包含完整记录字段。原始内容块顺序与未渲染文本保持完整;生成的显示标签不进入原始字段。 | - -已记录文件名优先于本地化的编号图片标签。元数据来自记录字段或文件扩展名,零字节大小仍然可见。视觉上截断的名称保留完整的无障碍文本和工具提示。无正文的消息仍在详情面板滚动区域内展示全部附件。 - -### 职责 - -[ui-trajectory](../../../../packages/client/ui-trajectory/README.zh.md)负责计数、有序附件投影和详情面板组合。其视图数据将带类型的图片与文件引用和原始内容使用的序列化文本分别保存,展示代码因此无需解析 JSON 来恢复文件元数据。 - -[ui-attachment](../../../../packages/client/ui-attachment/README.zh.md)通过现有图片 slot 负责图片加载与查看器。[ui-conversation](../../../../packages/client/ui-conversation/README.zh.md)拥有会话级图片缓存:Chat 与 Trajectory 对每个附件共享一次授权读取和一个浏览器 URL。缩略图尺寸和可选显示标签只影响展示,加载仍接收原始引用。Chat 和工具结果图库保留现有的尺寸行为。 - -本决定使用现有持久附件引用,不改变上传、存储、模型请求或会话格式。普通文件展示元数据,不提供内容查看器或下载操作。产品标签使用带类型的本地化字典,样式使用现有语义 token。 - -[通用文件上传决定](../feature/2026-08-26-generic-file-upload.zh.md)负责上传、存储、准入和有序消息附件;本记录负责 Trajectory 展示。[不可读附件隔离提案](../../proposed/bug-fix/2026-08-20-attachment-read-quarantine.zh.md)处理模型请求恢复,与缩略图加载反馈相互独立。 - -## 考虑过的替代方案 - -**默认在详情面板中内联展示大图。** 这样可以立即阅读单张图片,但高图片会在窄面板中挤走附件名称和消息正文。紧凑行加显式图片激活入口能一致地概览混合附件。 - -**使用双列附件卡片。** 网格能在宽面板上突出缩略图,但会压缩长文件名空间,并要求窄面板采用不同排布。单列等宽列表适用于两种面板宽度。 - -**在原始内容中混排文件 JSON 和图片预览。** 这让同一个标签页对两种附件具有不同含义。元数据折叠区用于检查已记录字段,预览负责图片的视觉展示。 - -## 影响 - -共享附件渲染器使概述与预览中的名称、顺序、元数据和图片操作保持一致。将附件集中在消息正文之后便于浏览完整集合;需要原始块顺序时,原始内容保留交错关系。即使图片加载共享缓存项,重复引用仍各占一行。 - -紧凑缩略图需要打开查看器才能阅读详细图表。缺失的元数据保持缺失,图片读取失败保留重试控件,并且不隐藏普通文件。共享图片 slot 的修改也会影响 Chat 和工具结果,因此其图库行为和缓存复用仍需回归验证。 - -## 验证 - -[投影测试](../../../../packages/client/ui-trajectory/tests/layout.client.spec.tsx)覆盖混合附件、纯附件、空文本和重复引用。[详情面板测试](../../../../packages/client/ui-trajectory/tests/table.client.spec.tsx)覆盖两种语言、无名称图片、长文件名、零字节文件、有序列表和完整的原始字段。[图片测试](../../../../packages/client/ui-attachment/tests/message-image.client.spec.tsx)覆盖缩略图标签、加载、重试和现有查看器。 - -已录制的[混合上传浏览器场景](../../../../apps/web/tests/file-upload-round.e2e.ts)比较概述与预览,验证键盘激活和焦点恢复,检查原始内容收起与展开状态,并在刷新后重复检查。其 [Trajectory 预期输出](../../../../snapshots/web/file-upload-round/trajectory.expected.md)配合未改动的会话 fixture(测试前置数据)使用。[组装应用图片缓存测试](../../../../apps/web/tests/trajectory-image-display.expected.e2e.ts)也检查 Chat 与 Trajectory 之间的复用。 - -布局验收覆盖窄版与宽版面板、两种主题和语言、缩略图完整缩放及末尾附件可达性。[GIF 工作流](../../../skills/record-browser-gif/SKILL.md)要求评审证据基于干净的已提交代码树,在同一个真实服务、真实模型会话中展示混合提交、列表计数、概述、预览中的图片激活和展开的原始元数据。Issue #4356 及其关联 PR(Pull Request)记录已执行的检查和演示证据。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml deleted file mode 100644 index fdf261af26..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-desktop-window-menus.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md -/agent-note-desktop-standard-macos-window-menus: - en: 7a3fc575043bc839 - zh: 5ab70cfa35509b3e -/agent-note-desktop-standard-macos-window-menus/problem: - en: 75c201a0a687b59d - zh: 34593038f673d06f -/agent-note-desktop-standard-macos-window-menus/decision: - en: 0ce30c3a1e6202d7 - zh: badac0021fae4a6f -/agent-note-desktop-standard-macos-window-menus/alternatives-considered: - en: 957382dbfb6cac48 - zh: 2db98b7dbf3e01c0 -/agent-note-desktop-standard-macos-window-menus/consequences: - en: 1fa44c82ca450bcd - zh: b618f5ec0a90ed13 -/agent-note-desktop-standard-macos-window-menus/testing: - en: 0c05c0044532f891 - zh: cff254b44b3c7a2d diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md deleted file mode 100644 index d8f8bdb9cc..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: Desktop standard macOS window menus - -Status: implemented - -English | [中文](2026-09-16-desktop-window-menus.zh.md) - -## Problem - -The Desktop shell replaces Electron's default application menu with a custom template that listed only the application and Edit menus. Electron builds only the roles a template declares, so macOS lost the File and Window menus and the application hide commands the default template supplies, including Close Window (⌘W), Minimize (⌘M), and Hide (⌘H). None of those shortcuts did anything in the Desktop application while every comparable macOS application responds to them (issue #4374). - -## Decision - -On macOS the template declares `{ role: 'windowMenu' }` after the Edit menu, and a separator-delimited run of `hide`, `hideOthers`, and `unhide` before Quit in the application submenu. Those roles contribute only the standard items; no Services submenu, window list, or other macOS default is declared. Windows and Linux keep the application and Edit menus. The [page-close decision](../architecture/2026-09-21-desktop-page-close-shortcuts.md) owns the custom File menu and close behavior. - -Electron supplies English defaults for the Window and Edit roles. Explicit labels override role defaults while retaining native actions and shortcuts; the localized application commands are documented in the [Desktop README](../../../../apps/desktop/README.md). Minimize and hide execute through Electron's roles without custom handlers. Close and activation behavior is partly superseded by [close-to-background](../architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.md); the menu declarations here remain in effect. - -## Alternatives considered - -**Bind ⌘W to minimizing or hiding the window.** macOS reserves Minimize for ⌘M and Hide for ⌘H, and comparable applications close the front window with ⌘W. Binding another command to the reported shortcut would contradict the platform convention the change follows. - -**Declare only the Window menu.** That menu supplies Minimize and Zoom but no Close, leaving ⌘W unbound. - -**Add a bare `{ role: 'close' }` item to the application submenu.** It avoids a File menu containing a single command, but places a window command among the app-wide Plugins, Updates, Hide, and Quit items where no macOS application puts it. - -**Declare the File and Window menus on every platform.** The same template declares Ctrl+W there; with one window, closing it invokes `window-all-closed` and quits the application. The menu-restoration task did not request this quit path; the page-close decision explicitly specifies the Desktop fallback. - -**Recreate the main window on Dock activation regardless of other windows.** The plugin window can outlive the main window, so gating `activate` on `mainWindow` instead of `BrowserWindow.getAllWindows()` would let a Dock click always restore the main window. That changes window lifecycle beyond restoring the suppressed platform commands, so the existing `activate` condition stays. - -## Consequences - -macOS retains native Window and application hide commands through four menu roles. Window and Edit retain Electron’s default English labels; application commands use the Desktop locale. - -## Testing - -`apps/desktop/tests/main-startup.spec.ts` verifies the declared menu roles per platform and records English and Chinese application-label snapshots. Role-based menu items execute natively, so a programmatic `click()` and the Vitest Electron mock cannot exercise the shortcuts. The isolated Electron 44 demo confirms that explicit application labels appear in native menus; the template snapshots do not qualify native shortcut execution. diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md b/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md deleted file mode 100644 index c85ab23df1..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-desktop-window-menus.zh.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: Desktop standard macOS window menus - -Status: implemented - -[English](2026-09-16-desktop-window-menus.md) | 中文 - -## 问题 - -Desktop shell 用自定义模板替换了 Electron 的默认应用菜单,该模板此前只列出应用菜单和 Edit 菜单。Electron 只构建模板中声明的 role,因此 macOS 失去了默认模板提供的 File、Window 菜单和应用隐藏命令,包括 Close Window(⌘W)、Minimize(⌘M)和 Hide(⌘H)。这些快捷键在 Desktop 应用中都没有任何效果,而同类的 macOS 应用都会响应它们(#4374)。 - -## 决策 - -macOS 上的模板在 Edit 菜单之后声明 `{ role: 'windowMenu' }`,并在应用子菜单的 Quit 之前声明由分隔符隔开的 `hide`、`hideOthers` 和 `unhide`。这些 role 只提供标准菜单项;不声明 Services 子菜单、窗口列表或其他 macOS 默认项。Windows 和 Linux 保留应用菜单和 Edit 菜单。[页面关闭决策](../architecture/2026-09-21-desktop-page-close-shortcuts.zh.md)负责自定义 File 菜单和关闭行为。 - -Electron 为 Window 和 Edit role 提供英文默认标签。显式标签会覆盖 role 的默认标签,同时保留原生命令和快捷键;本地化的应用命令见 [Desktop README](../../../../apps/desktop/README.zh.md)。最小化和隐藏通过 Electron 的 role 执行,没有自定义处理器。 关闭与激活行为已被[关闭时隐藏窗口](../architecture/2026-09-23-desktop-close-to-background-and-quit-confirmation.zh.md)部分取代;这里的菜单声明仍然有效。 - -## 考虑过的替代方案 - -**把 ⌘W 绑定为最小化或隐藏窗口。** macOS 把最小化留给 ⌘M、把隐藏留给 ⌘H,同类应用都用 ⌘W 关闭最前窗口。给这个快捷键绑定其他命令会违背本改动所遵循的平台惯例。 - -**只声明 Window 菜单。** 该菜单提供 Minimize 和 Zoom,但不提供关闭,⌘W 仍然没有绑定。 - -**在应用子菜单里直接加一个 `{ role: 'close' }` 项。** 这样可以避免只有一个命令的 File 菜单,但会把窗口命令放到 Plugins、Updates、Hide、Quit 这些应用级命令中间,没有 macOS 应用这样排布。 - -**在所有平台都声明 File 和 Window 菜单。** 同一份模板在那些平台会把 Ctrl+W 声明为关闭;应用只有一个窗口,关闭它会触发 `window-all-closed` 并退出应用。恢复菜单的任务没有要求这条退出路径;页面关闭决策明确规定了 Desktop 回退。 - -**让 Dock 激活时无论其他窗口是否存在都重建主窗口。** 插件窗口可以比主窗口存活更久,因此把 `activate` 的判断从 `BrowserWindow.getAllWindows()` 改为 `mainWindow` 能让点击 Dock 总可以恢复主窗口。但这超出了恢复被压掉的平台命令,属于窗口生命周期的行为变更,因此保留现有的 `activate` 判断。 - -## 影响 - -macOS 通过四个菜单 role 保留原生 Window 和应用隐藏命令。Window 和 Edit 保留 Electron 的默认英文标签;应用命令使用 Desktop 的语言。 - -## 测试 - -`apps/desktop/tests/main-startup.spec.ts` 验证各平台声明的菜单 role,并记录中英文应用标签快照。基于 role 的菜单项由本机执行,因此程序化的 `click()` 和 Vitest 的 Electron mock 都无法触达这些快捷键。独立 Electron 44 demo 确认显式应用标签会显示在原生菜单中;模板快照不能验证原生快捷键的执行。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml deleted file mode 100644 index d0f3da21b3..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-messages-historical-tool-input.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-messages-historical-tool-input.md -/agent-note-replay-malformed-historical-tool-input-through-messages: - en: 88cdfca30918b47f - zh: 1203d380805f81e4 -/agent-note-replay-malformed-historical-tool-input-through-messages/problem: - en: b9f848905bb31969 - zh: 7c56aab1a15cfc85 -/agent-note-replay-malformed-historical-tool-input-through-messages/decision: - en: ee7c586820c28e54 - zh: fced4d6c50c7d227 -/agent-note-replay-malformed-historical-tool-input-through-messages/alternatives-considered: - en: c7d7275282a83780 - zh: c0c570f156ac546f -/agent-note-replay-malformed-historical-tool-input-through-messages/consequences: - en: 4d1447eb8ea0f7f4 - zh: cf232f6bb1d60d4e diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.i18n.yaml deleted file mode 100644 index e64fb33fd5..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-present-delayed-windows-installer.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.md -/agent-note-present-delayed-windows-installer: - en: 4451eeb66010bc3d - zh: 7b5e60d779a9eb41 -/agent-note-present-delayed-windows-installer/problem: - en: 7835e64ac8ea1825 - zh: aac3b323204b08b9 -/agent-note-present-delayed-windows-installer/decision: - en: adea34850b8df240 - zh: da68a90a4144092f -/agent-note-present-delayed-windows-installer/alternatives-considered: - en: 66f936fe39452162 - zh: 4d0e192fb37afffe -/agent-note-present-delayed-windows-installer/consequences: - en: 6431f3c88199ecaf - zh: 9f30f647fdab4e64 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.md b/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.md deleted file mode 100644 index d4ffcfdc11..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.md +++ /dev/null @@ -1,23 +0,0 @@ -# Agent Note: Present delayed Windows installer - -Status: implemented - -English | [中文](2026-09-16-present-delayed-windows-installer.zh.md) - -## Problem - -The native installer hides its window while preparing resources. If the user activates another application during that interval, the welcome page can appear behind that application and seem absent. - -## Decision - -The first welcome-page display moves the installer above ordinary windows without activating it. When another window owns the foreground, the installer also flashes its taskbar button until foreground interaction. Later page changes do not repeat the move. The window never becomes permanently topmost. - -## Alternatives considered - -**Force foreground focus.** Windows may reject a background process's foreground request, and taking focus after the user switches applications interrupts their current action. - -**Keep the installer topmost.** A persistent topmost window obscures applications the user intentionally opens during installation. - -## Consequences - -The welcome page becomes visible when preparation completes, while the user retains control of focus and can cover the installer again. A native window-order test and the signed installer smoke test exercise the first display. diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.zh.md b/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.zh.md deleted file mode 100644 index bf97ed8a4f..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-present-delayed-windows-installer.zh.md +++ /dev/null @@ -1,23 +0,0 @@ -# Agent Note: 延迟显示的 Windows 安装窗口 - -Status: implemented - -[English](2026-09-16-present-delayed-windows-installer.md) | 中文 - -## Problem - -原生安装程序准备资源时会隐藏窗口。如果用户在此期间激活其他应用,欢迎页可能出现在该应用后面,看起来像没有启动。 - -## Decision - -欢迎页首次显示时,安装窗口会移到普通窗口前方,但不抢占焦点。如果其他窗口位于前台,安装程序还会闪烁任务栏按钮,直至用户进行前台交互。后续页面切换不会重复置前。安装窗口不会永久置顶。 - -## Alternatives considered - -**强制获取前台焦点。** Windows 可能拒绝后台进程的前台请求,而且用户切换应用后再抢回焦点会打断当前操作。 - -**让安装窗口始终置顶。** 持续置顶会遮挡用户在安装期间主动打开的其他应用。 - -## Consequences - -准备完成时欢迎页可见,用户仍能控制焦点,也可以再次用其他窗口覆盖安装程序。原生窗口顺序测试和签名安装程序冒烟测试覆盖首次显示。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml deleted file mode 100644 index 3d896f268f..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-session-writer-held-feedback.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-session-writer-held-feedback.md -/agent-note-session-writer-contention-feedback: - en: 31fc83d11f79fbac - zh: ec2e62f56a83ff3c -/agent-note-session-writer-contention-feedback/problem: - en: ce6f2ed854986c2a - zh: 74b28d3282f610e8 -/agent-note-session-writer-contention-feedback/decision: - en: a2c50e7614d183fd - zh: c4057682bc25cb23 -/agent-note-session-writer-contention-feedback/alternatives-considered: - en: 43f74d015db77ab1 - zh: 38c7ffda0a33ecac -/agent-note-session-writer-contention-feedback/consequences: - en: 230c23cceae0ad46 - zh: 8ec04813b825b509 diff --git a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml deleted file mode 100644 index 879cde4ff0..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-windows-subprocess-console-visibility.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-16-windows-subprocess-console-visibility.md -/agent-note-hide-windows-subprocess-console-windows-at-creation: - en: be47c56d2dd977d1 - zh: 3ab8a9a18e7f545d -/agent-note-hide-windows-subprocess-console-windows-at-creation/problem: - en: b76c3b25b8616076 - zh: d14facfdf3948bc4 -/agent-note-hide-windows-subprocess-console-windows-at-creation/decision: - en: 56b951a948c6d007 - zh: 301031638a642517 -/agent-note-hide-windows-subprocess-console-windows-at-creation/alternatives-considered: - en: 9dcf5702a5032d69 - zh: 493c2709d441d52c -/agent-note-hide-windows-subprocess-console-windows-at-creation/consequences: - en: 0bfe83068c6fe3ee - zh: 2bdc9eb64c6f7b66 diff --git a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.i18n.yaml deleted file mode 100644 index 9290c75e26..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-thinking-markdown.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.md -/agent-note-compact-markdown-for-thinking: - en: 2b71ec7a5294e982 - zh: 0c33248c5d07ec74 -/agent-note-compact-markdown-for-thinking/problem: - en: c14bd1618741736a - zh: dc1fff84736cb4f7 -/agent-note-compact-markdown-for-thinking/decision: - en: adb4f711ecf2011a - zh: 791f7bf3b8d23458 -/agent-note-compact-markdown-for-thinking/alternatives-considered: - en: d5bf82e0de012900 - zh: a639fd02e00b5d09 -/agent-note-compact-markdown-for-thinking/consequences: - en: d69b13246a34047c - zh: 4c1a6520053261ef diff --git a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.md b/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.md deleted file mode 100644 index 6990fc6277..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Compact Markdown for Thinking - -Status: implemented - -English | [中文](2026-09-17-thinking-markdown.zh.md) - -## Problem - -Chat Thinking contains model-authored Markdown, but literal rendering exposes headings, emphasis markers, and code fences. The shared answer typography makes headings larger and more prominent than the secondary reasoning text. Trajectory applies that answer typography to the same reasoning. - -## Decision - -[MarkdownText](../../../../packages/client/ui-primitives/src/markdown/MarkdownText.tsx) owns a compact presentation variant used by Chat Thinking and Trajectory thinking details. It keeps the secondary content font size, line height, and tertiary color throughout. Heading levels retain semantic elements but share the same 600 weight and size; paragraphs, lists, quotes, and code use compact spacing. Links keep a resting dotted underline with tertiary color as a secondary-content exception to the [shared link styling](../../../../docs/web-styling.md); code retains its monospace font and background. - -Tables and formulas remain enabled through the existing parser. Their containers constrain horizontal overflow, and formula text inherits the secondary font size. Inline formulas retain native KaTeX baselines; enclosing text blocks own overflow so short formulas do not create scrollbars. Compact code banners stay in normal flow, keeping the [Thinking disclosure header](../../../../packages/client/ui-chat/src/client/chat/ReasoningRow.module.css) above the scrolling content without a second sticky band. - -Chat passes its running state to the existing incremental Markdown renderer. The collapsed summary remains a separate single-line text projection. The parser, frozen-block cache, stored reasoning, and Session format do not change. Trajectory pins Thinking to its inspector’s fixed 13px/20px tier, independent of Chat’s content-size setting. Its answer output retains its existing typography and its [Thinking disclosure behavior](../feature/2026-09-09-ptc-trajectory-code-inspection.md). - -## Alternatives considered - -**Use answer typography unchanged.** Large headings and generous block spacing give reasoning more visual weight than the answer. - -**Style Markdown separately in each consumer.** Chat and Trajectory would need to track the same renderer elements independently; the primitive owns those elements and their compact presentation. - -**Disable tables and formulas.** The existing renderer already supports them. Bounded overflow and inherited font sizing preserve useful content without another parsing mode. - -## Consequences - -Thinking supports structured reading while retaining its secondary emphasis. Semantic headings remain available to assistive technology even though their visible hierarchy is flattened. The shared variant applies to both views; future Markdown element changes must preserve its typography as well as the default answer typography. - -Markdown soft line breaks collapse like answer prose; hard breaks and separate paragraphs require Markdown syntax. Streaming freezing operates on completed blocks, so an unfinished long paragraph remains in the mutable tail and is parsed again as it grows. diff --git a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.zh.md b/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.zh.md deleted file mode 100644 index 963626088e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Thinking 的紧凑 Markdown - -Status: implemented - -[English](2026-09-17-thinking-markdown.md) | 中文 - -## 问题 - -Chat Thinking 包含模型编写的 Markdown,但纯文本渲染直接显示标题、强调标记和代码围栏。共享回答排版会让标题比次级推理文本更大、更醒目。Trajectory 对同一推理内容使用了回答排版。 - -## 决策 - -[MarkdownText](../../../../packages/client/ui-primitives/src/markdown/MarkdownText.tsx) 拥有紧凑展示变体,供 Chat Thinking 和 Trajectory 思考详情使用。内容沿用次级字号、行高和 tertiary 颜色。各级标题保留语义元素,但统一使用 600 字重和相同字号;段落、列表、引用和代码采用紧凑间距。链接保留默认点状下划线与 tertiary 颜色,作为[共享链接样式](../../../../docs/web-styling.zh.md)的次级内容例外;代码保留等宽字体和底色。 - -表格和公式继续通过既有解析器启用。其容器限制横向溢出,公式文本继承次级字号。行内公式保留 KaTeX 原生基线,由外层文本块承担溢出处理,短公式不会产生滚动条。紧凑代码栏保持正常文档流,让 [Thinking 折叠标题](../../../../packages/client/ui-chat/src/client/chat/ReasoningRow.module.css) 位于滚动内容上方,无需第二条 sticky 栏。 - -Chat 将运行状态传给既有增量 Markdown 渲染器。折叠摘要仍是独立的单行文本投影。解析器、冻结块缓存、存储的推理内容和 Session 格式均保持不变。Trajectory 将 Thinking 固定在检查器的 13px/20px 层级,与 Chat 的内容字号设置无关。其回答输出保留既有排版及其 [Thinking 折叠行为](../feature/2026-09-09-ptc-trajectory-code-inspection.zh.md)。 - -## 曾考虑的替代方案 - -**直接使用回答排版。** 大标题和宽松块间距让推理比回答更醒目。 - -**由每个使用方单独设置 Markdown 样式。** Chat 和 Trajectory 将需要分别跟踪相同的渲染元素;基元拥有这些元素及其紧凑展示。 - -**禁用表格和公式。** 既有渲染器已支持二者。限制溢出和继承字号可以保留有用内容,无需增加另一种解析模式。 - -## 后果 - -Thinking 支持结构化阅读,同时保持次级强调。虽然视觉层级被统一,辅助技术仍可使用语义标题。共享变体同时用于两个视图;后续 Markdown 元素变更必须同时保留紧凑排版与默认回答排版。 - -Markdown 软换行与回答正文一样折叠;硬换行和独立段落需要相应 Markdown 语法。流式冻结以完整块为单位,因此尚未结束的长段落仍处于可变尾部,并随增长重新解析。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml deleted file mode 100644 index 4d97265632..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-messages-input-history-compatibility.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-18-messages-input-history-compatibility.md -/agent-note-replay-assistant-blocks-in-messages-user-input: - en: 2928aedee63465ba - zh: 0492dcc6604e2292 -/agent-note-replay-assistant-blocks-in-messages-user-input/problem: - en: d42b7147df73f498 - zh: f77ef429363c2da8 -/agent-note-replay-assistant-blocks-in-messages-user-input/decision: - en: 0b5629b321be7181 - zh: 3449958cde376d59 -/agent-note-replay-assistant-blocks-in-messages-user-input/alternatives-considered: - en: 3801041c0f9c4d8a - zh: 71d80b08183e8b30 -/agent-note-replay-assistant-blocks-in-messages-user-input/consequences: - en: c1770c7aa4097c42 - zh: 80ab7f2b66408ad7 diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml deleted file mode 100644 index dc30220e34..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-schedule-task-tab-restore.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-18-schedule-task-tab-restore.md -/agent-note-task-tab-recovery-after-a-reload: - en: d7edcc16307d350a - zh: 63301b138b2a203b -/agent-note-task-tab-recovery-after-a-reload/problem: - en: 244f309423fff24e - zh: ddd2ab66a3f4fd5b -/agent-note-task-tab-recovery-after-a-reload/decision: - en: 16e1bcb4a0860e91 - zh: 587715df43d5c96f -/agent-note-task-tab-recovery-after-a-reload/alternatives-considered: - en: 2fb8e2e0688d22ac - zh: a584b183723aace7 -/agent-note-task-tab-recovery-after-a-reload/consequences: - en: 20b7e4c551df0bc0 - zh: c17426fb981f0b06 -/agent-note-task-tab-recovery-after-a-reload/testing: - en: 3581ad83c52ee3b0 - zh: fbffd886a46b76c6 diff --git a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml deleted file mode 100644 index 9a2f10699e..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-windows-directory-junction-listing.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-18-windows-directory-junction-listing.md -/agent-note-list-directory-links-in-the-workspace-file-tree: - en: 70fbeeae3fa4a6e6 - zh: 6ed08f09c9d1d02e -/agent-note-list-directory-links-in-the-workspace-file-tree/problem: - en: eec5c2e8edd0e0ff - zh: 03c0f3490bb891c0 -/agent-note-list-directory-links-in-the-workspace-file-tree/decision: - en: ad36ceb185d15d69 - zh: 71847eaa4641a9d7 -/agent-note-list-directory-links-in-the-workspace-file-tree/alternatives-considered: - en: 8a9daf7bd2d78b65 - zh: 69f46ee1108c2747 -/agent-note-list-directory-links-in-the-workspace-file-tree/consequences: - en: 3cbc15d04a639292 - zh: 4a9c94a1a1e8aeca diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml deleted file mode 100644 index a1c2b57fdb..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-declared-session-attachment-carriers.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-declared-session-attachment-carriers.md -/agent-note-declared-session-attachment-carriers: - en: ec696589b6d29694 - zh: fca0e8292a840d47 -/agent-note-declared-session-attachment-carriers/problem: - en: 2c3544ea223fd86c - zh: fee865c0ce374115 -/agent-note-declared-session-attachment-carriers/decision: - en: 7538fa848fbbeff8 - zh: 6fd10f3683cadef0 -/agent-note-declared-session-attachment-carriers/alternatives-considered: - en: 3307cfd109d19377 - zh: a0051b257807e6d0 -/agent-note-declared-session-attachment-carriers/consequences: - en: 9d028f075b96f121 - zh: c5da5f89f7b75efc -/agent-note-declared-session-attachment-carriers/verification: - en: f297ee8e56e7e69e - zh: e6bc44cb7711b778 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml deleted file mode 100644 index 727bb883f4..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-failed-step-tool-results.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-failed-step-tool-results.md -/agent-note-record-unanswered-tool-results-before-closing-a-failed-step: - en: 8fd2a50a041b1b18 - zh: 01aed07a6425a322 -/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/problem: - en: 1fa7525f83af9035 - zh: 401ce968e3dd5434 -/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/decision: - en: 3d58367a05183b4a - zh: f9ccc3a7011a6240 -/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/alternatives-considered: - en: 37da4d9c8533b736 - zh: e52a59763ae2b62e -/agent-note-record-unanswered-tool-results-before-closing-a-failed-step/consequences: - en: 1ea72421a98551d8 - zh: c01e02ddac8a3454 diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml deleted file mode 100644 index 5618368313..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-session-local-subagent-migration.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-session-local-subagent-migration.md -/agent-note-complete-parent-catalogs-without-migrating-child-catalogs: - en: 352c234841bc0905 - zh: 89be7b695aec5931 -/agent-note-complete-parent-catalogs-without-migrating-child-catalogs/problem: - en: 5ca0c3232895a9dd - zh: cb89e5464d4a5f99 -/agent-note-complete-parent-catalogs-without-migrating-child-catalogs/decision: - en: 7a368a5ad8dc3e0c - zh: 11ca2af33de30d8f -/agent-note-complete-parent-catalogs-without-migrating-child-catalogs/alternatives-considered: - en: 7272107ab68ceefd - zh: 8b8bba44c9e0c402 -/agent-note-complete-parent-catalogs-without-migrating-child-catalogs/consequences: - en: 7caecc8516419375 - zh: cc966c325896737d diff --git a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml deleted file mode 100644 index 3b1c733004..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-v3-incomplete-child-catalog-evidence.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-19-v3-incomplete-child-catalog-evidence.md -/agent-note-preserve-v3-sessions-with-incomplete-child-catalog-evidence: - en: ff7fbd846f8edf2f - zh: 8f4703ea0b0d7cc9 -/agent-note-preserve-v3-sessions-with-incomplete-child-catalog-evidence/problem: - en: a5d524599f02e77f - zh: b79687f999f4024c -/agent-note-preserve-v3-sessions-with-incomplete-child-catalog-evidence/decision: - en: cfd781300b663010 - zh: 6f7865d5619a1e52 -/agent-note-preserve-v3-sessions-with-incomplete-child-catalog-evidence/alternatives-considered: - en: b7ce0d1b6b280d7f - zh: 3deeaef73a3a7856 -/agent-note-preserve-v3-sessions-with-incomplete-child-catalog-evidence/consequences: - en: 9f8b6446f6cdea60 - zh: d0a07674bf8434c4 diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.i18n.yaml index 270547a0cb..6b9b8fad84 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.i18n.yaml @@ -33,5 +33,5 @@ en: 13437882143e2025 zh: 586bcd68eaa03969 /agent-note-session-opening-cache-identity-and-read-ordering/relationship-to-existing-records: - en: 8b479aee5c808180 - zh: aa0220e664f9e9a6 + en: 1621a7c781ebb432 + zh: 92dbac3c96da2403 diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.md b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.md index c00f8fa7a3..814d53651a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.md +++ b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.md @@ -84,5 +84,5 @@ Focused TypeScript builds and bundles were run for affected packages during deve - [Session observations and projection-owned Client state](../architecture/2026-08-25-session-observations-and-projection-owned-client-state.md) retains ownership of read cuts and shared values. This record adds cache-producer identity and opening-snapshot consumption order; the older claim of sharing unfinished cold loads does not match the current reader and is not evidence that this change implements singleflight. - [Client Session references, reference sources, and UI state](../architecture/2026-09-15-client-session-references.md) retains ownership of `retain`, `ready`, `using`, and explicit-address semantics. This record limits temporary references to four classes of auxiliary reads as independent asynchronous operations, rather than adding references to every UI action. -- [Web subagent catalogs consume shared projections](../simplification/2026-09-08-web-subagent-catalog-projections.md) retains ownership of the generic projection store, explicit catalog reads, and control updates. This implementation applies its rule that opening a conversation uses the follow baseline, without replacing the catalog mechanism. -- [Web subagent catalogs and human continuation](../feature/2026-07-27-web-subagent-conversations.md) retains ownership of presentation and continuation authorization. This record changes only prerequisite navigation reads and root-menu refresh timing, not the child's own Host validation; the older description of interactive loading for missing root catalogs must be reconciled with the coverage gap recorded here. +- [Web subagent catalogs consume shared projections](../../../../packages/client/ui-subagent/README.md) retains ownership of the generic projection store, explicit catalog reads, and control updates. This implementation applies its rule that opening a conversation uses the follow baseline, without replacing the catalog mechanism. +- [Web subagent catalogs and human continuation](../../../../packages/client/ui-subagent/README.md) retains ownership of presentation and continuation authorization. This record changes only prerequisite navigation reads and root-menu refresh timing, not the child's own Host validation; the older description of interactive loading for missing root catalogs must be reconciled with the coverage gap recorded here. diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.zh.md b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.zh.md index ce9eac01db..4d20f7be40 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-20-session-open-read-coordination.zh.md @@ -84,5 +84,5 @@ Header 根目录缺失仍是明确的覆盖缺口:若它属于未打开的 par - [Session observation 与 projection 所有的客户端状态](../architecture/2026-08-25-session-observations-and-projection-owned-client-state.zh.md)继续拥有读取切面和共享值的职责划分。本记录补充缓存生产者身份与首帧消费顺序;旧记录中“包括未完成冷加载的共享”描述与当前 reader 实现不一致,不能作为本次 singleflight 已实现的依据。 - [Client Session 引用、引用来源与 UI 状态](../architecture/2026-09-15-client-session-references.zh.md)继续拥有 `retain`、`ready`、`using` 与显式地址语义。本记录限定四类辅助读取作为独立异步操作持有临时引用,不把额外引用扩展到所有 UI 动作。 -- [Web 子代理目录消费共享 projection](../simplification/2026-09-08-web-subagent-catalog-projections.zh.md)继续拥有通用 projection store、显式目录读取和 control 更新。本次实现落实“打开会话使用 follow baseline”的规则,不替换整个目录机制。 -- [Web subagent 目录与用户继续交互](../feature/2026-07-27-web-subagent-conversations.zh.md)继续拥有展示和 continuation 授权。本记录只调整导航前置读取及根菜单刷新时机,不取消子会话自身的 Host 校验;旧记录对缺失根目录交互加载的描述需与这里的覆盖缺口一起核对。 +- [Web 子代理目录消费共享 projection](../../../../packages/client/ui-subagent/README.zh.md)继续拥有通用 projection store、显式目录读取和 control 更新。本次实现落实“打开会话使用 follow baseline”的规则,不替换整个目录机制。 +- [Web subagent 目录与用户继续交互](../../../../packages/client/ui-subagent/README.zh.md)继续拥有展示和 continuation 授权。本记录只调整导航前置读取及根菜单刷新时机,不取消子会话自身的 Host 校验;旧记录对缺失根目录交互加载的描述需与这里的覆盖缺口一起核对。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml deleted file mode 100644 index 48e79b967c..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-20-windows-embedded-mandatory-update.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-20-windows-embedded-mandatory-update.md -/agent-note-windows-embedded-mandatory-update: - en: fb52005dc1f55b5d - zh: dda7e46555da6d03 -/agent-note-windows-embedded-mandatory-update/problem: - en: df8f2127905913d7 - zh: 64d27253ce39e031 -/agent-note-windows-embedded-mandatory-update/decision: - en: f238482836ae80da - zh: df856cba1b715056 -/agent-note-windows-embedded-mandatory-update/alternatives-considered: - en: 9564f979909f0743 - zh: 6730e42a123b707a -/agent-note-windows-embedded-mandatory-update/consequences: - en: 64cd6585a98f01cd - zh: 8bca9eb88aa55d8e diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml deleted file mode 100644 index 82aabefd0d..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-21-multimodal-tool-result-retention.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-21-multimodal-tool-result-retention.md -/agent-note-multimodal-tool-result-retention: - en: aa04c4148785f8ca - zh: b08c44c8484ea5d2 -/agent-note-multimodal-tool-result-retention/problem: - en: 9e8765ea40441f36 - zh: 3ca2616499d3c9fc -/agent-note-multimodal-tool-result-retention/decision: - en: a65eeedad585fc4b - zh: 82aae7ef0148282b -/agent-note-multimodal-tool-result-retention/alternatives-considered: - en: c670e96a46c93540 - zh: efccc4d3423e5ac0 -/agent-note-multimodal-tool-result-retention/consequences: - en: 7fdf51600bb0ffd7 - zh: 0ad7f06887b1c859 diff --git a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml deleted file mode 100644 index 48e34b2079..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-21-persistent-pwsh-keeps-controlled-prompt.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-21-persistent-pwsh-keeps-controlled-prompt.md -/agent-note-persistent-pwsh-keeps-the-backend-s-controlled-prompt: - en: 78c6e7e3340581dc - zh: 64e16ab608caa610 -/agent-note-persistent-pwsh-keeps-the-backend-s-controlled-prompt/problem: - en: 0cd51c293e96d9eb - zh: 6eb89ee3f1b7a851 -/agent-note-persistent-pwsh-keeps-the-backend-s-controlled-prompt/decision: - en: edd5cbb51ddad62f - zh: d7c41f09a9453992 -/agent-note-persistent-pwsh-keeps-the-backend-s-controlled-prompt/alternatives-considered: - en: a3920fa51dd5ac79 - zh: 29c3f8364f64115e -/agent-note-persistent-pwsh-keeps-the-backend-s-controlled-prompt/consequences: - en: d43b72fb5aa0c6da - zh: c4c9a27d5c7c6338 diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml deleted file mode 100644 index e10cbc61b1..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-chat-scroll-follow-and-footer-geometry.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-22-chat-scroll-follow-and-footer-geometry.md -/agent-note-separate-chat-clipping-and-independent-follow-intent: - en: a95aae5eecac228c - zh: 01b7df574c3d010f -/agent-note-separate-chat-clipping-and-independent-follow-intent/problem: - en: 8e4310ee05cf535c - zh: bd57ba36ebe8c201 -/agent-note-separate-chat-clipping-and-independent-follow-intent/decision: - en: afe3cf3bb5a92039 - zh: bb11f9f4bcd8f329 -/agent-note-separate-chat-clipping-and-independent-follow-intent/alternatives-considered: - en: 014e7974ce1cadae - zh: 63c1743c598413e4 -/agent-note-separate-chat-clipping-and-independent-follow-intent/verification: - en: 235dc4e251fa8124 - zh: 2c08ab5e14213b54 -/agent-note-separate-chat-clipping-and-independent-follow-intent/consequences: - en: aa1367b399806ab7 - zh: f98a8b416d9a70c8 diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml deleted file mode 100644 index 6f58c8d30c..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-input-echo-admission-ownership.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-22-input-echo-admission-ownership.md -/agent-note-input-echoes-retain-display-ownership-through-admission: - en: 987905631fdf4244 - zh: 2b2a1db249903717 -/agent-note-input-echoes-retain-display-ownership-through-admission/problem: - en: a56f673056a7d913 - zh: a06acdb9f3e1afd3 -/agent-note-input-echoes-retain-display-ownership-through-admission/decision: - en: b570ae20a428f554 - zh: b3cf1ebc56fbe78c -/agent-note-input-echoes-retain-display-ownership-through-admission/alternatives-considered: - en: 9ee59841301a61f4 - zh: 7ad35e485294dce1 -/agent-note-input-echoes-retain-display-ownership-through-admission/verification: - en: a1373e299a536929 - zh: 13405143c3bb9331 -/agent-note-input-echoes-retain-display-ownership-through-admission/consequences: - en: 027d6bae744c47e0 - zh: c9f397bc631298b9 diff --git a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml deleted file mode 100644 index 3a1ef4f137..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-windows-open-through-shell-resolution.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-22-windows-open-through-shell-resolution.md -/agent-note-windows-opens-go-through-explorer-exe: - en: 315c5e87180365ba - zh: 4bc29f09bb3900d3 -/agent-note-windows-opens-go-through-explorer-exe/problem: - en: bc2c92e4247ea71e - zh: ca20fdeccc58e883 -/agent-note-windows-opens-go-through-explorer-exe/decision: - en: 4cf76bcb52a7beef - zh: a55a5bd9a4b85063 -/agent-note-windows-opens-go-through-explorer-exe/alternatives-considered: - en: 64712f720fca44a1 - zh: aad98e46f0c7a6bb -/agent-note-windows-opens-go-through-explorer-exe/consequences: - en: d06af93b608037c9 - zh: 38b1a4c840b0ce51 -/agent-note-windows-opens-go-through-explorer-exe/verification: - en: c7ff7cb42072737c - zh: 9bbf5ff6596938a0 diff --git a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml deleted file mode 100644 index 1978750d50..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-23-bounded-pnpm-runs.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-23-bounded-pnpm-runs.md -/agent-note-bounding-a-pnpm-run-that-stops-progressing: - en: c2bd52dec2a41d84 - zh: 80dae83761a09a60 -/agent-note-bounding-a-pnpm-run-that-stops-progressing/problem: - en: 227a287fe7127eae - zh: 5d7eae54a4a75f8c -/agent-note-bounding-a-pnpm-run-that-stops-progressing/decision: - en: 6a574e5b6d536b5b - zh: 92a9bb15d983e7db -/agent-note-bounding-a-pnpm-run-that-stops-progressing/why-a-silence-bound-rather-than-a-duration-bound: - en: 31287e4e71e7f10f - zh: ff57c96539d51972 -/agent-note-bounding-a-pnpm-run-that-stops-progressing/alternatives-considered: - en: 125710ae84911ed4 - zh: 5f35dbcda4447bea -/agent-note-bounding-a-pnpm-run-that-stops-progressing/testing: - en: eb3c4907aec48db3 - zh: 56b7168b732c782a -/agent-note-bounding-a-pnpm-run-that-stops-progressing/related: - en: a9c6d0a071ea9834 - zh: 0702183c645d03b9 -/agent-note-bounding-a-pnpm-run-that-stops-progressing/consequences: - en: c13a64cd3dd381e9 - zh: 83d2db47153e3d8c diff --git a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml deleted file mode 100644 index ad4ccdf4ab..0000000000 --- a/.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-24-exited-holder-lock-takeover.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.md -/agent-note-taking-over-a-writer-lock-whose-holder-exited: - en: ecf6fb35c9eed3ca - zh: 59c29b64e23608e4 -/agent-note-taking-over-a-writer-lock-whose-holder-exited/problem: - en: cf143bc56530fe17 - zh: a4e3ee8fa3b8c465 -/agent-note-taking-over-a-writer-lock-whose-holder-exited/decision: - en: af22bae72ff97ef8 - zh: 428c09871bba95d1 -/agent-note-taking-over-a-writer-lock-whose-holder-exited/alternatives-considered: - en: ee800ed269cfc4a2 - zh: df983b552ef25273 -/agent-note-taking-over-a-writer-lock-whose-holder-exited/testing: - en: 7eb7c5818049415a - zh: f84c39c55bb3bc5a -/agent-note-taking-over-a-writer-lock-whose-holder-exited/consequences: - en: 03f4cda9290188e5 - zh: 07f800776c061218 diff --git a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.i18n.yaml index d0742687e2..b4f5f12a7b 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.i18n.yaml @@ -6,8 +6,8 @@ en: 5e8c5673a0669d9c zh: c8442a6186288a2a /agent-note-a-seen-prompt-marker-keeps-its-own-tail-grace/problem: - en: 906f8707987ff36c - zh: 40430ec1e03ee340 + en: 2f5e9297c5fd4853 + zh: 8168c90bc6fa13b5 /agent-note-a-seen-prompt-marker-keeps-its-own-tail-grace/decision: en: a998f5c6abdd68eb zh: a7cdd13e1e4de6c6 diff --git a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.md b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.md index 375ba521d5..68d22a753a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.md +++ b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.md @@ -6,7 +6,7 @@ English | [中文](2026-09-27-pwsh-prompt-tail-grace.zh.md) ## Problem -Windows sessions on the self-hosted lane settled one or two of seven sends on the controlled prompt and charged the silence tier for the rest (2026-09-25/26, issue #2487). The exact readiness evidence is the controlled prompt's printable tail after the OSC `133;D` marker, and the bound on waiting for it was `idleSilenceMs + handoffGraceMs`. A native Windows probe shows the render arrives as two pty chunks: the marker alone, then the five-byte `dsh> ` 2-28 ms later (56 renders under Windows PowerShell 5.1, the same two-chunk render under pwsh 7.6.6), with nothing printable after any prompt and no terminal queries in the output. The marker is written by the shell's own prompt function and the tail by the same render, so a tail that arrives past the bound means the host stalled that delivery, not that the prompt is absent. The session settled `inferred_idle` anyway, and each such send cost the tool call roughly three seconds. The lane's own failures are not that state: the first master run carrying this field (run 36326153388, 2026-09-27 14:30Z) took the same 20.8 s as the run before it (36309006133), so its degraded sends settled at the plain bound with the tolerance inert; the [Windows-lane observation note](../testing/2026-09-27-observation-waits-on-observed-state.md) records the open investigation. +Windows sessions on the self-hosted lane settled one or two of seven sends on the controlled prompt and charged the silence tier for the rest (2026-09-25/26, issue #2487). The exact readiness evidence is the controlled prompt's printable tail after the OSC `133;D` marker, and the bound on waiting for it was `idleSilenceMs + handoffGraceMs`. A native Windows probe shows the render arrives as two pty chunks: the marker alone, then the five-byte `dsh> ` 2-28 ms later (56 renders under Windows PowerShell 5.1, the same two-chunk render under pwsh 7.6.6), with nothing printable after any prompt and no terminal queries in the output. The marker is written by the shell's own prompt function and the tail by the same render, so a tail that arrives past the bound means the host stalled that delivery, not that the prompt is absent. The session settled `inferred_idle` anyway, and each such send cost the tool call roughly three seconds. The lane's own failures are not that state: the first master run carrying this field (run 36326153388, 2026-09-27 14:30Z) took the same 20.8 s as the run before it (36309006133), so its degraded sends settled at the plain bound with the tolerance inert; the [Windows-lane observation note](../testing/2026-09-27-observation-waits-on-observed-state.md) records that investigation, and the [bundled console host note](2026-09-28-pty-bundled-console-host.md) measures that pool's tail arriving already malformed (`\ndsh>\x1b[1C`), which no bound on a well-formed tail can cover. This field still owns genuinely delayed tails. ## Decision diff --git a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.zh.md b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.zh.md index 511cedf728..5efebc7137 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-27-pwsh-prompt-tail-grace.zh.md @@ -6,7 +6,7 @@ Status: implemented ## Problem -自托管 lane 上的 Windows 会话在七次 send 中只有一到两次按受控提示符结算,其余都要支付静默层(2026-09-25/26,issue #2487)。精确的就绪证据是 OSC `133;D` 标记之后受控提示符的可打印尾部,而等待它的上界是 `idleSilenceMs + handoffGraceMs`。原生 Windows 探测表明该渲染以两个 pty 分块到达:先是标记本身,2–28 毫秒后是 5 字节的 `dsh> `(Windows PowerShell 5.1 下 56 次渲染,pwsh 7.6.6 下同样是两分块渲染),任何提示符之后都没有可打印文本,输出中也没有终端查询。标记由 shell 自己的 prompt 函数写出、尾部由同一次渲染写出,因此尾部晚于上界到达意味着宿主机拖慢了这次投递,而不是提示符缺失。会话此前仍会按 `inferred_idle` 结算,每次这样的 send 让工具调用多付约三秒。该 lane 自身的失败不是这种状态:首个带着该字段的 master run(run 36326153388,2026-09-27 14:30Z)与其前一个 run(36309006133)同样耗时 20.8 秒,因此其退化的 send 是在宽限不起作用的情况下按普通上界结算的;[Windows lane 观测笔记](../testing/2026-09-27-observation-waits-on-observed-state.zh.md)记录了这项仍未关闭的调查。 +自托管 lane 上的 Windows 会话在七次 send 中只有一到两次按受控提示符结算,其余都要支付静默层(2026-09-25/26,issue #2487)。精确的就绪证据是 OSC `133;D` 标记之后受控提示符的可打印尾部,而等待它的上界是 `idleSilenceMs + handoffGraceMs`。原生 Windows 探测表明该渲染以两个 pty 分块到达:先是标记本身,2–28 毫秒后是 5 字节的 `dsh> `(Windows PowerShell 5.1 下 56 次渲染,pwsh 7.6.6 下同样是两分块渲染),任何提示符之后都没有可打印文本,输出中也没有终端查询。标记由 shell 自己的 prompt 函数写出、尾部由同一次渲染写出,因此尾部晚于上界到达意味着宿主机拖慢了这次投递,而不是提示符缺失。会话此前仍会按 `inferred_idle` 结算,每次这样的 send 让工具调用多付约三秒。该 lane 自身的失败不是这种状态:首个带着该字段的 master run(run 36326153388,2026-09-27 14:30Z)与其前一个 run(36309006133)同样耗时 20.8 秒,因此其退化的 send 是在宽限不起作用的情况下按普通上界结算的;[Windows lane 观测笔记](../testing/2026-09-27-observation-waits-on-observed-state.zh.md)记录了该调查,而[自带控制台宿主说明](2026-09-28-pty-bundled-console-host.zh.md)实测该池的尾部以已经畸形的形态到达(`\ndsh>\x1b[1C`),任何针对良构尾部的上界都覆盖不了。本字段仍然负责真正迟到的尾部。 ## Decision diff --git a/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.i18n.yaml new file mode 100644 index 0000000000..817b227486 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-09-28-pty-bundled-console-host.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.md +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships: + en: 5d0101a130154663 + zh: 9fe1948abd4b6576 +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships/problem: + en: d5e9c94b6a210a8e + zh: e2e15aac77dd34ee +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships/decision: + en: 1b4f56354db760c5 + zh: f467d13f84872e66 +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships/alternatives-considered: + en: c65cbf2fa36636a3 + zh: fdf846dd4c8a39cb +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships/consequences: + en: 5af486852c78125e + zh: 68215646a34b4412 +/agent-note-windows-persistent-ptys-use-the-console-host-node-pty-ships/testing: + en: eebdb2d7c7e61d9e + zh: da8dd1df06180dee diff --git a/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.md b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.md new file mode 100644 index 0000000000..6caee6d9a3 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.md @@ -0,0 +1,39 @@ +# Agent Note: Windows persistent PTYs use the console host node-pty ships + +Status: implemented + +English | [中文](2026-09-28-pty-bundled-console-host.zh.md) + +## Problem + +The master `serial / windows (self-hosted standby)` drill has been red since 2026-09-15: its `test:coverage` gate fails on `packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts`, either with `settleReasons` reporting one `stdin_read` among six `inferred_idle` settlements or with the case exhausting its 120 s budget. The same case passes in 5.3 s on the Blacksmith Windows image. + +Windows has no exact stdin-wait tier: `WindowsProcessInspector.isStdinWaiting()` always returns false and `foregroundPgid()` returns the shell pid, so `inspectForeground()` can only report `inputWaiting: false`. The persistent-pwsh readiness path therefore settles `stdin_read` only after it has seen both the OSC `133;D;` marker and the printable `dsh> ` tail written by the same prompt render (`terminal-bash/src/session.ts`). + +Probing the pool machine directly (node-pty 1.2.0-beta.15, `pwsh -NoLogo -NoProfile`, 160x40, `TERM=dumb`) shows why that tail never matches there: once a command's output scrolls the viewport, the console host Windows itself provides replays the prompt line as `\ndsh>\x1b[1C` — a leading newline, the prompt text without its trailing space, and a cursor-forward escape in place of that space. `TerminalSanitizer` accumulates the printable text after the marker, so the tail becomes `\ndsh>`, which neither equals `dsh> ` nor prefixes it, and every later send waits out the silence tier. Five of seven prompts took that form on the pool machine; the same command sequence on Windows 11 25H2 (conhost 10.0.26100.1) renders `dsh> ` verbatim. The [seen-marker tail grace](2026-09-27-pwsh-prompt-tail-grace.md) recorded the same red signal as a late tail and shipped `promptTailGraceMs`; this measurement replaces that attribution for these hosts, because a tail that has already arrived in a non-prefixing form never satisfies the condition the grace extends. + +## Decision + +`subprocess-local` allocates Windows terminals with node-pty's `useConptyDll: true`, so the PTY is backed by the OpenConsole the node-pty package ships instead of the console host the operating system provides. The option is set only when the runtime platform is `win32`; POSIX allocation is unchanged. + +The same command sequence on the pool machine through the bundled console host rendered `dsh> ` for all seven prompts. + +## Alternatives considered + +**Rely on `promptTailGraceMs`, the grace shipped for late tails.** Rejected: the extended bound applies only while the arrived tail still prefixes `dsh> ` (`tailPending` in `terminal-bash/src/session.ts`). A pool prompt renders `\ndsh>`, which prefixes nothing in that comparison, so the grace is never reached and the sends keep settling on the plain silence bound. + +**Teach `TerminalSanitizer` to tolerate the operating system console host's rendering.** Rejected: normalizing the leading newline and `CSI nC` into the tail keeps the readiness path working, but it writes one console host's quirk into the sanitizer and leaves any other repaint form to reintroduce the same divergence. The console host is a replaceable component, and the package already ships a current one. + +**Keep the operating system console host and accept the red drill.** Rejected: the drill is the only continuous evidence that the in-house Windows pool can take over the required lanes, and it has been red for two weeks. + +**Upgrade the pool machines' Windows.** Rejected as this repository's fix: it requires rebuilding production CI hosts (Server 2022, Windows 10 22H2), and the rendering difference returns on any host that is not current. + +## Consequences + +Windows PTY allocation now depends on node-pty's bundled OpenConsole binaries. The desktop runtime file policy already retains `node-pty/prebuilds/win32-x64/conpty/conpty.dll` and `OpenConsole.exe`, and the package resolves them relative to its own addon, so the packaged desktop runtime keeps working; that policy is now load-bearing for terminal allocation rather than only for keeping the source distribution intact. + +Console signals keep their documented behavior: SIGINT is still delivered as a `\x03` input write that the console host turns into a console-wide CTRL_C event, and the Windows inspector is unchanged. The allocated process id still names the shell: on the pool machine `pty.pid` resolves to `pwsh.exe` and equals the shell's own `$PID` under both console hosts. node-pty marks `useConptyDll` experimental, so a node-pty upgrade can change it; a unit test pins the option as present for `win32` and absent elsewhere. Both teardown paths still publish node-pty's exit event on the pool machine under the bundled host (about 1.1 s for a `taskkill` tree kill and for a bare `kill()`), and an unresolvable `conpty/conpty.dll` fails loud: node-pty throws instead of falling back to the operating system host. + +## Testing + +`packages/subprocess/subprocess-local/tests/local.spec.ts` pins the option per platform: present for `win32`, absent for a POSIX platform. The end-to-end signal for the pool is the next master push's `serial / windows (self-hosted standby)` run, whose sends settle on the prompt path instead of the silence bound. No e2e, snapshot, or sandbox case applies: this change selects a PTY backend and produces no model- or product-user-visible output of its own. The packaged runtime keeps the assets this option resolves, pinned by the retained-file assertions in `apps/desktop/tests/runtime-file-policy.spec.ts`, and the Electron payload smoke passes the option when it allocates a PTY from that tree on Windows, so the packaged path and the separately pinned retained-file list are both exercised. diff --git a/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.zh.md b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.zh.md new file mode 100644 index 0000000000..c8d2907b6f --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-28-pty-bundled-console-host.zh.md @@ -0,0 +1,39 @@ +# Agent Note: Windows 持久 PTY 使用 node-pty 自带的控制台宿主 + +Status: implemented + +[English](2026-09-28-pty-bundled-console-host.md) | 中文 + +## 问题 + +master 的 `serial / windows (self-hosted standby)` 演练自 2026-09-15 起一直红:`test:coverage` gate 在 `packages/shell/tool-pwsh-persistent/tests/loader-composition.spec.ts` 上失败,形态要么是 `settleReasons` 报出六次 `inferred_idle` 结算里只有一次 `stdin_read`,要么是用例耗尽 120 s 预算。同一个用例在 Blacksmith 的 Windows 镜像上 5.3 s 通过。 + +Windows 没有精确的 stdin-wait 档:`WindowsProcessInspector.isStdinWaiting()` 恒为 false、`foregroundPgid()` 返回 shell pid,因此 `inspectForeground()` 只能报 `inputWaiting: false`。持久 pwsh 的就绪路径只有在同时看到 OSC `133;D;` 标记与同一次 prompt 渲染写出的可打印 `dsh> ` 尾部之后,才结算 `stdin_read`(`terminal-bash/src/session.ts`)。 + +直接在池机器上探测(node-pty 1.2.0-beta.15、`pwsh -NoLogo -NoProfile`、160x40、`TERM=dumb`)说明了那个尾部为何在池上匹配不上:命令输出把视口滚动一次之后,Windows 自带的控制台宿主把提示符行重绘成 `\ndsh>\x1b[1C`——一个前导换行、去掉尾随空格的提示符文本,以及代替那个空格的 cursor-forward 转义。`TerminalSanitizer` 累积标记之后的非转义文本,于是尾部成了 `\ndsh>`,既不等于 `dsh> ` 也不构成它的前缀,此后每次 send 都要等满静默档。池机器上七个提示符里有五个是这个形态;同一串命令在 Windows 11 25H2(conhost 10.0.26100.1)上原样渲染出 `dsh> `。[已见标记的尾部宽限](2026-09-27-pwsh-prompt-tail-grace.zh.md)把同一个红信号记为尾部迟到,并为此交付了 `promptTailGraceMs`;本次测量取代了该归因在这类宿主上的适用范围——已经以非前缀形态到达的尾部永远不满足宽限所延长的那个条件。 + +## 决策 + +`subprocess-local` 分配 Windows 终端时传 node-pty 的 `useConptyDll: true`,PTY 因此由 node-pty 包自带的 OpenConsole 承载,而不是操作系统提供的控制台宿主。该选项仅在运行时平台为 `win32` 时设置;POSIX 分配不变。 + +同一串命令在池机器上经由自带控制台宿主时,七个提示符全部渲染出 `dsh> `。 + +## 备选方案 + +**依赖为迟到尾部交付的 `promptTailGraceMs`。** 否决:延长的上界只在已到达的尾部仍构成 `dsh> ` 前缀时生效(`terminal-bash/src/session.ts` 的 `tailPending`)。池上的提示符渲染成 `\ndsh>`,在这个比较里不构成任何前缀,因此宽限永远到不了,send 继续按普通静默上界结算。 + +**让 `TerminalSanitizer` 容忍操作系统控制台宿主的渲染形态。** 否决:把前导换行与 `CSI nC` 归一进尾部能让就绪路径继续工作,但它把一个控制台宿主的怪癖写进 sanitizer,之后任何别的重绘形态都会重新引入同一类偏离。控制台宿主是可替换组件,而包里已经带了当前版本。 + +**保留操作系统控制台宿主并接受演练长期红。** 否决:这条演练是「自有 Windows 池能接管必需 lane」的唯一持续证据,它已经红了两周。 + +**升级池机器的 Windows。** 作为本仓库的修法否决:它需要重建生产 CI 主机(Server 2022、Windows 10 22H2),而且任何非最新宿主上这个渲染差异都会回来。 + +## 后果 + +Windows PTY 分配现在依赖 node-pty 自带的 OpenConsole 二进制。桌面端运行时文件策略已经保留 `node-pty/prebuilds/win32-x64/conpty/conpty.dll` 与 `OpenConsole.exe`,且包按自身 addon 位置解析它们,因此打包后的桌面运行时仍然可用;该策略现在对终端分配是承重的,不只是为了保留源码分发内容。 + +控制台信号保持文档化的行为:SIGINT 仍以 `\x03` 输入写入投递、由控制台宿主转成控制台级 CTRL_C 事件,Windows inspector 未变。分配到的进程 id 仍然指向 shell:在池机器上 `pty.pid` 解析为 `pwsh.exe`,且与 shell 自己的 `$PID` 在两种控制台宿主下都相等。node-pty 把 `useConptyDll` 标为实验性,因此 node-pty 升级可能改变它;一条单测钉住该选项:`win32` 上存在、其它平台不存在。两种拆卸路径在池机器上、自带控制台宿主下都会发出 node-pty 的退出事件(`taskkill` 树杀与裸 `kill()` 各约 1.1 s);解析不到 `conpty/conpty.dll` 时是硬失败——node-pty 直接抛错,不会回退到操作系统控制台宿主。 + +## 测试 + +`packages/subprocess/subprocess-local/tests/local.spec.ts` 按平台钉住该选项:`win32` 上存在、POSIX 平台上不存在。池侧的端到端信号是下一次 master push 的 `serial / windows (self-hosted standby)` run——它的 send 将按提示符路径结算,而不是等满静默上界。e2e、snapshot、sandbox 三类不适用:本改动只选择 PTY 后端,本身不产生模型可见或产品用户可见的输出。打包运行时保留了该选项要解析的资产,由 `apps/desktop/tests/runtime-file-policy.spec.ts` 的保留清单断言钉住;Electron payload smoke 在 Windows 上从该树分配 PTY 时也传该选项,因此打包路径与单独钉住的保留清单都会被覆盖。 diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml index 9808841e3a..c0741def43 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml @@ -9,8 +9,8 @@ en: b3d0591cc559e466 zh: aa4c385e84f7eaa1 /agent-note-persist-owned-job-lifecycle-facts/decision: - en: 7748eabcc29afd20 - zh: 08e5cd283bc2bcba + en: 42bc368642abf66d + zh: df21d7f36c6946a7 /agent-note-persist-owned-job-lifecycle-facts/alternatives-considered: en: e95b3dd9a87d0805 zh: 39edb0eb03c26f9c diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md index 740eb52663..83453c9981 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md +++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md @@ -16,7 +16,7 @@ The terminal record distinguishes producer acknowledgement from forced bookkeepi Facts are captured at the registry commit and published in microtask order before completion waiters resume. This avoids reentering Session append when a Session observer starts a job. Recording errors warn independently: already-started work still has its control id, and a failed journal append cannot prevent cleanup or release of waiters. The Session's ordinary persistence and SDK notification paths own delivery after append. -The live registry and resource ownership decisions remain in the [generic runtime note](../architecture/2026-06-20-generic-long-running-tool-runtime.md) and [seam consolidation note](../architecture/2026-09-03-jobs-seam-consolidation.md). Those notes remain active; this journal adds durable observations without replacing their execution or event-routing responsibilities. +The [registry contract](../../../../packages/jobs/jobs/README.md) and [seam consolidation note](../architecture/2026-09-03-jobs-seam-consolidation.md) own live execution, resource ownership, and event routing. This journal adds durable observations without replacing those responsibilities. ## Alternatives considered diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md index 00b597bc3f..e611b02566 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md @@ -16,7 +16,7 @@ SDK 消费方接收 Session 事件,而 job 注册和结算此前只存在于 事实在注册表提交时捕获,并在完成等待方恢复前按微任务顺序发布。这避免 Session 观察者启动 job 时重入 Session append。记录错误单独发出警告:已启动的工作仍有可控制的 id,日志追加失败不能阻止清理或释放等待方。追加后的投递由 Session 既有的持久化和 SDK 通知路径负责。 -运行期注册表与资源归属决策仍由[通用运行时说明](../architecture/2026-06-20-generic-long-running-tool-runtime.zh.md)和[seam 收敛说明](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)负责。这些记录保持有效;本日志增加持久化观测,不替代其执行或事件路由职责。 +[注册表约定](../../../../packages/jobs/jobs/README.zh.md)和[seam 收敛说明](../architecture/2026-09-03-jobs-seam-consolidation.zh.md)负责运行期执行、资源归属和事件路由。本日志增加持久化观测,不替代这些职责。 ## Alternatives considered diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.i18n.yaml new file mode 100644 index 0000000000..817413b901 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.i18n.yaml @@ -0,0 +1,22 @@ +# Bilingual-pair consistency record for 2026-09-30-session-list-host-fairness.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.md +/agent-note-time-sliced-session-list-work: + en: 0e8f0cce65f887bc + zh: 1f3b669c1eef811f +/agent-note-time-sliced-session-list-work/problem: + en: 5ee132d1d44f463b + zh: 45589b49771d49e1 +/agent-note-time-sliced-session-list-work/decision: + en: 95b04ae405a5299f + zh: 53f1d8bf5e29e3ca +/agent-note-time-sliced-session-list-work/measurement-and-calibration: + en: e12fe17a32ede555 + zh: 6b16a72863073e47 +/agent-note-time-sliced-session-list-work/alternatives-considered: + en: a2f441a550c0ed7e + zh: 05e8bfd584217103 +/agent-note-time-sliced-session-list-work/consequences: + en: 5fd10d559a120ae6 + zh: a56f45b77ce1f4e6 diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.md b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.md new file mode 100644 index 0000000000..d0b6520de1 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.md @@ -0,0 +1,69 @@ +# Agent Note: Time-sliced Session-list work + +Status: implemented + +English | [中文](2026-09-30-session-list-host-fairness.zh.md) + +## Problem + +Large cached projection states can monopolize the Host while a Session list is assembled. Yielding after every row avoids that batch stall but adds thousands of event-loop round trips when a list contains many cheap rows. Row count alone is not a useful scheduling budget. + +## Decision + +[ApiSessionList](../../../../packages/api/session-controller/src/list.ts) uses one monotonic deadline for classification and summary generation. It yields only after elapsed work exhausts the configured `listWorkSliceMs`, then starts a new deadline after the wait. The optional deployment setting defaults to 16 positive integral milliseconds. Small lists have no forced final yield or no-op asynchronous helper. Cancellation is checked per row, after each yield, and before returning; no partial list is returned. + +Every row remains synchronous. Live rows precede queued cold rows before the stable activity sort; the list promises no cross-Session atomic cut. A cold row already queued before an attachment remains cached, and Client mutation replay plus [cached/sequenced precedence](../architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.md) reconcile the newer live state. No projection data or validation is omitted. + +## Measurement and calibration + +The [production-entry benchmark](../../../../benchmarks/session-corpus/projection-list.bench.ts) measures the real Session Controller, query engine, persistence, projection registry/cache and storage domain. Persisted fixtures use `generationLogPath`, `toHeaderLine` and `compressZstdFrame` from the persistence owner. All content is deterministic generated data. Setup and explicit GC are outside timing; complete results and JSON remain reachable during memory sampling. + +| Workload | Sessions | State observations | Request records | State JSON | +|---|---:|---:|---:|---:| +| Cheap live | 3,000 | 0 | 0 | 277,890 bytes | +| Modest persisted | 50 | 4,925 | 641 | 1,813,852 bytes | +| Heavy persisted | 300 | 300,000 | 37,575 | 111,255,440 bytes | + +The cheap live case avoids disk enumeration masking scheduling cost. Persisted row counts use 50/75/100/175% strata of 100 or 1,000 generated observations. Each fresh child measures one first call and three repeats with a single fixed-cost immediate probe per call. A separate fifth call re-arms at most one pending probe to measure the worst queue delay over the complete list. Full-probe timings are not throughput evidence: per-row yielding would otherwise execute proportionally more diagnostic callbacks. Yield counts exclude post-measurement GC helpers. Peak RSS includes setup and the entire process lifetime. + +Local Node 26.5.0/macOS arm64 comparisons alternate implementations and use three fresh children per workload. Repeat metrics first take each child's median, then the median across children. Queue delay takes the maximum in each separate diagnostic call, then the median across children. + +| Repeated metric | Synchronous | Per-row yield | 16 ms slices | +|---|---:|---:|---:| +| Cheap live: list + JSON | 61.60 ms | 111.65 ms | 65.68 ms | +| Cheap live: yield calls | 0 | 3,000 | 3 | +| Cheap live: maximum queue delay | 54.35 ms | 0.54 ms | 16.54 ms | +| Modest persisted: list + JSON | 19.76 ms | 18.23 ms | 18.26 ms | +| Modest persisted: yield calls | 0 | 50 | 0 | +| Heavy persisted: list + JSON | 253.22 ms | 291.36 ms | 232.18 ms | +| Heavy persisted: yield calls | 0 | 300 | 10 | +| Heavy persisted: maximum queue delay | 172.39 ms | 0.80 ms | 16.67 ms | + +For cheap lists, slicing removes 99.9% of per-row yields and reduces measured repeat time by 41.2%; the synchronous reference remains slightly faster. Heavy-list wall time varies substantially on the shared host, so no universal throughput speedup is asserted. Retained heap stays about 35 MB for cheap lists and 186–188 MB for heavy lists, without a decoded-state cache. + +| Raw child aggregates | Synchronous | Per-row yield | 16 ms slices | +|---|---|---|---| +| Cheap repeat ms | [63.10, 61.60, 59.08] | [108.19, 115.61, 111.65] | [65.68, 71.75, 65.17] | +| Cheap maximum queue ms | [112.69, 54.35, 49.67] | [0.54, 0.76, 0.41] | [16.41, 16.54, 41.15] | +| Heavy repeat ms | [277.06, 253.22, 236.22] | [291.36, 302.76, 282.01] | [212.35, 232.18, 304.38] | +| Heavy maximum queue ms | [297.76, 172.39, 164.17] | [0.56, 1.34, 0.80] | [16.39, 16.67, 16.76] | + +Separate 4/8/16 ms experiments measured cheap-list repeat medians of 58.39/58.57/52.34 ms and heavy-list medians of 279.81/253.85/216.67 ms. Their heavy-list maximum queued-work delay medians were 5.19/8.45/16.70 ms. The 16 ms default chooses fewer round trips while retaining a short work target; deployments can select a smaller budget. + +CI uses elapsed queue-delay and coarse throughput limits, not an exact batch size. The shared scale 2 and headroom 1.25 turn a 30 ms queue reference allowance into 75 ms, leaving room for one-row overshoot and scheduling variance while rejecting an unyielded heavy batch. Throughput reference allowances are 50/500/150 ms for modest/heavy/cheap, producing 125/1,250/375 ms bounds. These are safety ceilings, not claims of fine-grained non-regression. The earlier Linux Node 24.21 EPYC run measured 102.16/779.57 ms first calls for modest/heavy cases; the larger heavy allowance accounts for the observed hosted/reference ratio exceeding 2. Heap uses 240 MiB and only variance headroom, giving 300 MiB. + +The existing [3,000-Session corpus lane](../testing/2026-09-28-session-corpus-performance.md) remains the stored-corpus throughput authority. Its earlier per-row CI run measured first-call samples 3,205.3/3,166.2/3,165.1 ms and repeat samples 2,643.5/2,577.8/2,589.6 ms, within its existing calibrated limits. The projection benchmark adds a distinct cheap-live and large-state dimension rather than replacing that corpus. On the same local machine, the stored-corpus per-row/sliced first medians are 885.5/859.4 ms and repeat medians are 700.3/692.1 ms; that small difference is within ordinary run variation, with no observed throughput regression. The new three-workload file takes 23.98 s locally, including fixture setup and all nine children; its previously measured two-workload CI version took 36.80 s on EPYC Node 24.21.0. + +## Alternatives considered + +**Unconditional per-row yielding.** It minimizes individual queue waits but charges an event-loop round trip for every cheap row. The many-live-Session comparison exposes that cost; a fixed number of rows would still ignore variable row cost. + +**Larger or smaller time slices.** Smaller slices offer shorter queue waits at more scheduling cost. Larger slices reduce switching but extend the period other Host work waits. Configuration exposes this tradeoff without changing row semantics. + +**Caching decoded state or wire views.** Decoded-state reuse retains another graph per stored row and changes schema-evaluation ownership. Wire views can depend on current runtime state. Scheduling needs neither cache and preserves all schema checks and dynamic views. + +## Consequences + +[Controlled-clock tests](../../../../packages/api/session-controller/tests/list-scheduling.host.spec.ts) require cheap rows to remain in one synchronous slice, budget exhaustion and post-wait reset to govern yields, and cancellation to stop work even when no yield is due. They also preserve stable ties and attachment/removal/status interleavings. The time-based benchmark guard rejects unyielded or first-only-yield heavy work without forbidding efficient batches. + +A single row, provider enumeration, GC, final sorting and serialization can exceed the target. Concurrent lists and operating-system scheduling also affect latency. The budget is not a hard global bound, and these measurements do not claim browser paint or model/network latency improvements. diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.zh.md b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.zh.md new file mode 100644 index 0000000000..3e52f5503c --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-09-30-session-list-host-fairness.zh.md @@ -0,0 +1,69 @@ +# Agent Note: 按时间片执行 Session 列表工作 + +Status: implemented + +[English](2026-09-30-session-list-host-fairness.md) | 中文 + +## 问题 + +较大的缓存投影状态会在组装 Session 列表时独占 Host。每行都让出可以避免整批停顿,却会在包含大量低开销行的列表上增加数千次事件循环往返。行数本身不是合适的调度预算。 + +## 决策 + +[ApiSessionList](../../../../packages/api/session-controller/src/list.ts) 对分类和摘要生成共享一个单调时钟截止时间。仅当经过的工作时间耗尽配置的 `listWorkSliceMs` 时才让出,并在等待结束后建立新的截止时间。这个可选部署设置默认为 16 毫秒,要求正整数。小列表没有强制的最后一次让出,也不经过空操作异步 helper。每行、每次让出之后以及返回前检查取消;不会返回部分列表。 + +每行仍同步计算。live 行先于已进入队列的 cold 行,随后按活动时间稳定排序;列表不承诺跨 Session 的原子 cut。接入前已经排入队列的冷行仍是缓存行,Client 变更重放与 [cached/sequenced 优先级](../architecture/2026-09-19-projection-cache-listing-identity-and-cached-rows.zh.md)协调较新的 live 状态。不省略任何投影数据或校验。 + +## 测量与校准 + +[生产入口基准](../../../../benchmarks/session-corpus/projection-list.bench.ts)测量真实 Session Controller、查询引擎、持久化、投影注册表与缓存,以及 storage domain。持久化夹具使用持久化拥有方的 `generationLogPath`、`toHeaderLine` 和 `compressZstdFrame`。全部内容都是确定性生成数据。准备和显式 GC 不计入耗时;内存采样时完整结果与 JSON 仍可达。 + +| 负载 | Session 数 | 状态观测数 | 请求记录数 | 状态 JSON | +|---|---:|---:|---:|---:| +| 低开销驻留 | 3,000 | 0 | 0 | 277,890 bytes | +| 适中持久化 | 50 | 4,925 | 641 | 1,813,852 bytes | +| 大状态持久化 | 300 | 300,000 | 37,575 | 111,255,440 bytes | + +低开销驻留场景避免磁盘枚举掩盖调度成本。持久化行按 100 或 1,000 条生成观测的 50/75/100/175% 分层。每个全新子进程测量一次首次调用和三次重复调用,每次只使用一个固定成本的 immediate 探针。独立的第五次调用以最多一个待处理探针反复采样,测量完整列表中的最差排队延迟。完整探针模式的耗时不作为吞吐量证据,否则逐行让出会按比例执行更多诊断回调。yield 次数不包含测量后 GC helper 的调用。峰值 RSS 包含准备阶段及整个进程生命周期。 + +本地比较使用 Node 26.5.0/macOS arm64,交替运行实现,每种负载使用三个全新子进程。重复指标先取每个子进程的中位数,再取子进程间的中位数。排队延迟先取独立诊断调用的最大值,再取子进程间的中位数。 + +| 重复读取指标 | 同步 | 逐行让出 | 16 ms 时间片 | +|---|---:|---:|---:| +| 低开销驻留:列表与 JSON | 61.60 ms | 111.65 ms | 65.68 ms | +| 低开销驻留:yield 次数 | 0 | 3,000 | 3 | +| 低开销驻留:最大排队延迟 | 54.35 ms | 0.54 ms | 16.54 ms | +| 适中持久化:列表与 JSON | 19.76 ms | 18.23 ms | 18.26 ms | +| 适中持久化:yield 次数 | 0 | 50 | 0 | +| 大状态持久化:列表与 JSON | 253.22 ms | 291.36 ms | 232.18 ms | +| 大状态持久化:yield 次数 | 0 | 300 | 10 | +| 大状态持久化:最大排队延迟 | 172.39 ms | 0.80 ms | 16.67 ms | + +在低开销列表中,时间片去掉了 99.9% 的逐行 yield,并使测得的重复耗时下降 41.2%;同步参考仍略快。共享 Host 上大状态列表的墙钟耗时波动明显,因此不宣称普遍提高吞吐量。常驻堆在低开销场景约为 35 MB,在大状态场景约为 186–188 MB,没有解码状态缓存。 + +| 原始子进程聚合值 | 同步 | 逐行让出 | 16 ms 时间片 | +|---|---|---|---| +| 低开销重复 ms | [63.10, 61.60, 59.08] | [108.19, 115.61, 111.65] | [65.68, 71.75, 65.17] | +| 低开销最大排队 ms | [112.69, 54.35, 49.67] | [0.54, 0.76, 0.41] | [16.41, 16.54, 41.15] | +| 大状态重复 ms | [277.06, 253.22, 236.22] | [291.36, 302.76, 282.01] | [212.35, 232.18, 304.38] | +| 大状态最大排队 ms | [297.76, 172.39, 164.17] | [0.56, 1.34, 0.80] | [16.39, 16.67, 16.76] | + +独立的 4/8/16 ms 实验测得,低开销列表重复耗时中位数分别为 58.39/58.57/52.34 ms,大状态列表为 279.81/253.85/216.67 ms。大状态列表最大排队延迟的中位数分别为 5.19/8.45/16.70 ms。16 ms 默认值在保持较短工作目标的同时减少往返;部署可以选择更小的预算。 + +CI 使用经过时间的排队延迟与粗粒度吞吐量上限,而不是精确批大小。共享的 2 倍时间系数和 1.25 倍余量将 30 ms 排队参考余量转换为 75 ms,为单行超时和调度波动留出空间,同时拒绝没有让出的大状态批次。适中、大状态、低开销三种负载的吞吐量参考余量分别为 50/500/150 ms,得到 125/1,250/375 ms 上限。这些是安全上限,不证明细粒度不回退。先前 Linux Node 24.21 EPYC 运行测得适中和大状态场景首次调用为 102.16/779.57 ms;较大的大状态余量考虑了实际 hosted/reference 比值超过 2。堆使用 240 MiB,并只乘波动余量,得到 300 MiB。 + +已有的 [3,000 Session 会话集合测试](../testing/2026-09-28-session-corpus-performance.zh.md)仍是持久化集合吞吐量的依据。先前逐行版本的 CI 首次调用样本为 3,205.3/3,166.2/3,165.1 ms,重复调用为 2,643.5/2,577.8/2,589.6 ms,均在既有校准上限内。投影基准补充低开销驻留和大状态维度,不替代该集合。在同一本地机器上,持久化集合的逐行/时间片首次中位数为 885.5/859.4 ms,重复中位数为 700.3/692.1 ms;这点差异处于普通运行波动内,未观察到吞吐量回退。新的三负载文件在本地耗时 23.98 s,包含夹具准备和全部九个子进程;先前测得的双负载 CI 版本在 EPYC Node 24.21.0 上耗时 36.80 s。 + +## 考虑过的替代方案 + +**无条件逐行让出。** 它尽量缩短单次排队等待,却对每个低开销行都收取一次事件循环往返成本。大量驻留 Session 的比较暴露了这项成本;固定行数同样忽略了行成本差异。 + +**更大或更小的时间片。** 更小的时间片缩短排队等待,但增加调度成本。更大的时间片减少切换,却延长其他 Host 工作的等待。配置显式提供这一取舍,不改变行语义。 + +**缓存解码状态或 wire view。** 解码状态复用会为每份存储行保留另一份对象图,并改变 schema 执行的所有权。wire view 可能依赖当前运行状态。调度不需要这两类缓存,且保留全部 schema 校验和动态视图。 + +## 后果 + +[受控时钟测试](../../../../packages/api/session-controller/tests/list-scheduling.host.spec.ts)要求低开销行保持在同一同步时间片内,由预算耗尽和等待后重置决定让出,并在无需让出时也能响应取消。测试还保留了稳定并列排序和接入、移除、状态变更的交错行为。按时间判断的基准保护会拒绝不让出或仅首次让出的大状态工作,同时允许高效批处理。 + +单行、提供方枚举、GC、最终排序和序列化都可能超出目标。并发列表和操作系统调度也会影响延迟。预算不是全局硬上限,这些测量也不宣称改善浏览器绘制或模型、网络延迟。 diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml index 4a99a03276..38cc4a8bf5 100644 --- a/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.i18n.yaml @@ -9,17 +9,17 @@ en: 4b312be894531bee zh: d655648ea5843ef5 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision: - en: 371abc68ce278a5c - zh: 3fa8d28213794932 + en: a1732e2b1b46cb39 + zh: ddfc2bdcffa099a5 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/per-program-execution-controls: en: da694641e1d009de zh: 9d7492d39039af66 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-registry-owns-the-mode: - en: 55287fedeaaee6d8 - zh: d2afe242e84acc43 + en: 091b7bc49b027353 + zh: 42dc77a810202694 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/the-run-code-tool-and-the-dispatch-bridge: - en: 8811a9c5e28488fd - zh: 6937645281b79fb9 + en: 6a0e2698b082978e + zh: fbb03503520b30e9 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/observability-tool-ptc-dispatch: en: b8e4cf9433c7ee18 zh: e5f4e8d7d03d3a7b @@ -33,8 +33,8 @@ en: d62b41a6a5c58e5f zh: ef72c5a9b32b370e /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/decision/what-the-model-sees: - en: 43653fd91ab9165b - zh: 6ddfa853e69b2d80 + en: 81c12f19f7a967c1 + zh: 29734ca964acdec2 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/consequences: en: 761eb6c1aeb7af18 zh: 5434456a3d0a1b65 @@ -42,8 +42,8 @@ en: f5d287f610962a48 zh: 88368e1df517b087 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/alternatives-considered: - en: 90110f91f6f6130f - zh: 25d28433840f91a4 + en: e78530994a88d14f + zh: 49f64c7f1421f507 /agent-note-ptc-mode-the-model-writes-typescript-against-the-tool-registry/risks: - en: 7743b1b621b8e6c7 - zh: 70c33a5b306ca71c + en: d58925bdb912de23 + zh: d3a70237d656e5a4 diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.md b/.agents/notes/implemented/feature/2026-06-15-ptc.md index d5ed3fc79d..c49b21ecee 100644 --- a/.agents/notes/implemented/feature/2026-06-15-ptc.md +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.md @@ -18,7 +18,7 @@ Tool presentation belongs to the registry that owns tool visibility: implementin Three decisions, each elaborated in its own section below: -1. **PTC mode is a first-class presentation mode of `ToolRuntime`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas), `'ptc'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt), or `'both'` (native schemas and the transport + SDK). The registry constructs its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. +1. **PTC mode is a first-class presentation mode of `ToolRuntime`** (`dsh-tools`), selected by a validated `mode` config: `'native'` (the default, contributing the visible capability schemas) or `'ptc'` (the registry contributes only its reserved `run_code` transport plus a generated SDK `.d.ts` in the system prompt). The registry constructs its canonical contribution at the source; the cooperative prompt-assembly result remains authoritative, and the logged request header records exactly that returned presentation. 2. **Code execution is a capability seam** — `packages/ptc-runtime/` contains the Service Definition package `@deepseek-ai/dsh-ptc-runtime`, which owns `ctx.ptcRuntime` ([capability seams](../architecture/2026-06-13-capability-seams.md); Consumer = `dsh-tools`, with core-consumes-a-seam precedent in `agent-loop` → `dsh-llm`). The runtime knows nothing about tools: it is handed a program and named async bindings, runs the program, and reports `{ value, logs, error? }`. Language and substrate are backend properties, so a future Python or container backend is another Service Provider package, not a redesign. 3. **The shipped implementation is `@deepseek-ai/dsh-ptc-runtime-node`**: each program runs in a fresh managed Node process under the calling Session's resolved sandbox policy. Direct Node APIs remain available within that policy; named host bindings cross a separately validated control channel. @@ -32,13 +32,13 @@ The [Node execution decision](../architecture/2026-09-11-sandboxed-node-ptc-runt ### The registry owns the mode -`ToolRuntime` gains a schemastery-validated config (`static Config`), its first: `mode: 'native' | 'ptc' | 'both'`, default `'native'`. A deployment flips it from `cordis.yml` (`tools: { mode: ptc }`) — no code edit, per the no-hardcoded-tunables convention. +`ToolRuntime` gains a schemastery-validated config (`static Config`), its first: `mode: 'native' | 'ptc'`, default `'native'`. A deployment flips it from `cordis.yml` (`tools: { mode: ptc }`) — no code edit, per the no-hardcoded-tunables convention. -**Wire tool list.** The registry contributes visible capabilities in `'native'`, only `run_code` in `'ptc'`, and both in `'both'`. The final `PromptAssembly.tools` list is logged in the request header. `run_code` is a reserved presentation transport outside registration and restriction layers; direct prompt providers and the assembly waterfall remain responsible for their own contributions. +**Wire tool list.** The registry contributes visible capabilities in `'native'` and only `run_code` in `'ptc'`. The [presentation simplification](../simplification/2026-10-05-native-or-ptc-presentation.md) owns the removal of mixed presentation. The final `PromptAssembly.tools` list is logged in the request header. `run_code` is a reserved presentation transport outside registration and restriction layers; direct prompt providers and the assembly waterfall remain responsible for their own contributions. **Interaction with `toolOrder`:** a configured `systemPrompt.toolOrder` naming native capabilities rejects every assembly under `mode: 'ptc'`, because those names are outside that mode's wire-validation universe. This is correct behavior, not a bug: a deployment using PTC mode updates its order config or drops it. -**SDK prompt section.** In `'ptc'` and `'both'`, the lazy `tools:sdk` section in the tool-guidance order band renders the loaded runtime's language declarations plus fixed usage instructions for the scope's visible capabilities (TypeScript by default; the [language-dispatch note](../../archived/feature/2026-07-31-ptc-language-dispatch.md) added Python and the `ctx.ptcRuntime.language` renderer table). It shares lookup and execution visibility, excludes `run_code`, and sorts tools lexicographically for byte-stable output. +**SDK prompt section.** In `'ptc'`, the lazy `tools:sdk` section in the tool-guidance order band renders the loaded runtime's language declarations plus fixed usage instructions for the scope's visible capabilities (TypeScript by default; the [language-dispatch note](../../archived/feature/2026-07-31-ptc-language-dispatch.md) added Python and the `ctx.ptcRuntime.language` renderer table). It shares lookup and execution visibility, excludes `run_code`, and sorts tools lexicographically for byte-stable output. **Assembly ownership.** `run_code` and `tools:sdk` enter the trusted `system-prompt/assemble` waterfall as normal assembly inputs. A scoped `tools:sdk` section may shadow the global default before dispatch, and a listener may remove or replace either contribution. The waterfall's returned assembly is final, so whoever changes these inputs owns preserving a viable PTC mode protocol when the deployment expects PTC mode to remain usable; no restoration pass overrides deliberate composition. @@ -46,15 +46,15 @@ The [Node execution decision](../architecture/2026-09-11-sandboxed-node-ptc-runt ### The run_code tool and the dispatch bridge -Under `'ptc'` and `'both'` the registry owns `run_code` as a reserved presentation transport with two required parameters, `{ code: string; description: string }` (the description labels the call in UIs, the bash precedent). It is represented by a normal `ToolDefinition` for dispatch but stays outside the filterable capability layers, so restrictions cannot accidentally remove PTC mode's only entry point. Calls traverse the complete tool pipeline — `tools/pre-execute` → monotonic guards → `tools/execute` around dispatch → `tools/post-execute` → optional definition-owned `finalizeContent` → immutable `tools/result` notification — exactly like native calls; a permission plugin can inspect the program text before it runs, and final-result observers see the normalized outer outcome. Its `execute(args, exec)`: +Under `'ptc'` the registry owns `run_code` as a reserved presentation transport with two required parameters, `{ code: string; description: string }` (the description labels the call in UIs, the bash precedent). It is represented by a normal `ToolDefinition` for dispatch but stays outside the filterable capability layers, so restrictions cannot accidentally remove PTC mode's only entry point. Calls traverse the complete tool pipeline — `tools/pre-execute` → monotonic guards → `tools/execute` around dispatch → `tools/post-execute` → optional definition-owned `finalizeContent` → immutable `tools/result` notification — exactly like native calls; a permission plugin can inspect the program text before it runs, and final-result observers see the normalized outer outcome. Its `execute(args, exec)`: -1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, enters the native-contract dispatch pool (the [live-parallel note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduling design), executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs the `tool/ptc-dispatch-start`/`tool/ptc-dispatch` pair, the settle side carrying the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline. +1. **Build bindings.** One run-scoped signal follows outer cancellation and is aborted whenever the run settles. Each visible tool binding snapshots lossless-JSON arguments, enters the native-contract dispatch pool (the [live-parallel reference](../../../../packages/core/tools/README.md) owns the scheduling design), executes with a deterministic call id and the outer token as `parent`, defers returned contexts through the outer execution, and logs the `tool/ptc-dispatch-start`/`tool/ptc-dispatch` pair, the settle side carrying the full rendered result content. Success returns the tool's final canonical JSON value; failure becomes the program-visible `ToolCallError`. Every sub-call retains its own immutable execution identity and traverses the full tool pipeline. 2. **Runs resolved inputs:** `ctx.ptcRuntime.run(ctx.ptcRuntime.resolve(request))`. The request carries the program, bindings, run-scoped signal, calling Session cwd and applicable resolved file policy. The run-scoped signal connects outer settlement to execution cancellation. 3. **Settle after quiescence.** When the runtime settles, the bridge aborts outstanding work and drains the dispatch queue before returning. Success returns captured logs and the completion value as canonical output; the registry renders that value into durable `tool/result.content`, which the result card reads directly. A runtime failure becomes `CodeRunFailedError`; backend rejection uses the registry's normal error boundary. Both produce structured error results, and no sub-call can append after `run_code` settles. **Sub-call contexts are deferred through the parent.** Injecting inside `run_code` would break parent call/result adjacency, so `ToolRunContext.deferContext()` collects every sub-result `additionalContexts` entry in dispatch order. The registry carries that array even when the program later throws, and the loop appends each entry only after the outer result and every sibling result in the step. An outer post-execute block discards tool-deferred entries and exposes only contexts explicitly attached by the blocking decision. -**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel Agent Note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduler that replaced it. +**Concurrency is bounded, not serialized.** Each run owns a dispatch queue that starts calls strictly in submission order and classifies each one through `registry.executionMode`, the same fail-closed `isConcurrencySafe` contract the native loop uses. Consecutive parallel-classified calls overlap up to `maxParallelSubCalls` (default 10; `1` restores serial dispatch); an exclusive call drains the pool and runs alone. Settlement abandons queued calls that have not started. This note shipped the serialized placeholder; the [live-parallel reference](../../../../packages/core/tools/README.md) owns the scheduler that replaced it. **Presentation.** `run_code`'s render intent is decided here per the [render-intent Agent Note](../architecture/2026-07-02-tool-render-intent-union.md): `presentCall` creates a `generic` card with `kind: 'execute'`, the program text as its title, and the same program text as `rawInput`; `run_code` intentionally declares no `presentResult`, so the TUI and host/client runtime (Web) complete that card through their generic raw-content fallback using the final durable `tool/result.content`, including captured logs plus the returned value, failure, or post-policy spill preview. This is not a `terminal` card: that card's semantics are "a shell command in a working directory", which a program is not. See the [result-card completeness note](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md). @@ -86,7 +86,7 @@ The selected OS backend constrains direct Node effects under the same policy use ### What the model sees -The SDK instructs the model to write an async body in the loaded runtime's language (an erasable-TypeScript body by default; a Python `async` body under a Python runtime — see the [language-dispatch note](../../archived/feature/2026-07-31-ptc-language-dispatch.md)), call tools through `await tools.name(args)`, catch rejected tool calls when needed, and return or log only the output that should re-enter context. Both flavors state the same contract in their own primitive: independent read-only calls MAY overlap under `Promise.all` (TypeScript) or `asyncio.gather` (Python), mutating calls run alone in submission order, and dependent work sequences with `await`. The declaration prefix can be as large as native schemas, especially in `'both'`, but remains stable for provider caching. +The SDK instructs the model to write an async body in the loaded runtime's language (an erasable-TypeScript body by default; a Python `async` body under a Python runtime — see the [language-dispatch note](../../archived/feature/2026-07-31-ptc-language-dispatch.md)), call tools through `await tools.name(args)`, catch rejected tool calls when needed, and return or log only the output that should re-enter context. Both flavors state the same contract in their own primitive: independent read-only calls MAY overlap under `Promise.all` (TypeScript) or `asyncio.gather` (Python), mutating calls run alone in submission order, and dependent work sequences with `await`. The declaration prefix can be as large as native schemas, but remains stable for provider caching. The transport's own `description` and both SDK instruction flavors open by naming `code` and `description` as the call's two required arguments. Prose that describes the call as passing a program leaves the second argument discoverable only through the parameter schema, and a model that emits `{code}` alone loses the whole written program to an `INVALID_ARGS` rejection. @@ -113,7 +113,7 @@ Deployments switching to `'ptc'` must update any native-only `toolOrder`. Assemb **Always-exclusive (Cloudflare-faithful, no mode).** Rejected for this SDK's primary consumer: a coding agent's bread-and-butter single calls (`bash`, `read`, `edit`) are already ideal as native calls, and forcing every edit through a program taxes the common case. The mode config keeps the faithful form (`'ptc'`) one line away without imposing it. -**Per-tool visibility tiers (this tool native, that tool ptc-only).** Deferred: it needs per-tool metadata and a presentation split that `'native' | 'ptc' | 'both'` does not, and its design depends on evidence about how models split usage under `'both'`. +**Per-tool visibility tiers (this tool native, that tool ptc-only).** Deferred: it needs per-tool metadata and separate admission rules for native and program-only operations. Whole-agent native or PTC presentation keeps one calling convention per agent. **Sanitized identifier aliases in the SDK** (`my-tool` → `my_tool`, Cloudflare's approach). Rejected: quoted keys on a `declare const` make every name reachable with zero alias-collision logic; models handle `tools["my-tool"](…)` fine. @@ -125,12 +125,12 @@ Deployments switching to `'ptc'` must update any native-only `toolOrder`. Assemb **`stripTypeScriptTypes` is marked experimental.** It is the same engine (amaro/swc) behind Node's own native `.ts` execution, exposed as an API across this repo's whole engines range. Mitigations: the runtime's unit suite checks position preservation and the required parts of the erasable-only rejection message, the call sits behind one private function, and `amaro`/`sucrase` are direct replacements if the API shifts. The erasable-only subset is a model-facing input restriction, and the error tells the model how to correct the program. -**Prompt cost of the SDK, especially under `'both'`.** The `.d.ts` can rival the native schemas it complements; `'both'` carries two representations. Prefix stability + provider caching amortize per-session cost; the mode is per-deployment; the Agent Note makes no unconditional-savings claim. Measured guidance (when to prefer which mode) is explicitly post-ship learning. +**Prompt cost of the SDK.** The `.d.ts` can rival the native schemas it replaces. Prefix stability + provider caching amortize per-session cost; the mode is per-deployment; the Agent Note makes no unconditional-savings claim. Measured guidance (when to prefer which mode) is explicitly post-ship learning. **Registry scope growth.** `dsh-tools` absorbs codegen, a tool, a bridge, and an event. Package modules separate these responsibilities (`ts-types.ts` and `ptc.ts` beside `schema.ts`, `json-schema.ts`, and `presentation.ts`), while `ctx.ptcRuntime` owns all ptc-runtime-specific implementation. **Large binding values can still exhaust memory.** The Node provider bounds control frames, outstanding arguments and queued writes, while a host binding can allocate its result before those checks. Lossless JSON transfer and the outer output ledger do not create a process-wide allocation quota. -**Sub-dispatch overlap is bounded by tool safety claims, not by the caller.** A program's `Promise.all` or `asyncio.gather` buys wall-clock parallelism only across calls the tool itself classifies concurrency-safe; a run of exclusive calls still costs its round-trips in sequence, and models may over-expect. Both flavors' SDK instructions state the real contract. This note shipped the serialized placeholder that made the risk absolute; the [live-parallel Agent Note](2026-07-26-ptc-live-parallel-dispatch.md) owns the scheduler and its overlap cap. +**Sub-dispatch overlap is bounded by tool safety claims, not by the caller.** A program's `Promise.all` or `asyncio.gather` buys wall-clock parallelism only across calls the tool itself classifies concurrency-safe; a run of exclusive calls still costs its round-trips in sequence, and models may over-expect. Both flavors' SDK instructions state the real contract. This note shipped the serialized placeholder that made the risk absolute; the [live-parallel reference](../../../../packages/core/tools/README.md) owns the scheduler and its overlap cap. **Elapsed deadlines include awaited work.** Nested tools and approval waits consume the program's elapsed budget. The host stops execution on expiry and awaits managed cleanup; it does not meter process-tree CPU consumption. The sandboxed Node decision records current defaults and deferred timeout questions. diff --git a/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md index 1434db5c35..669a63e991 100644 --- a/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md +++ b/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md @@ -18,7 +18,7 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 三项决策,各自在下方独立小节中展开: -1. **PTC mode 是 `ToolRuntime`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)、`'ptc'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)或 `'both'`(原生 schema 加传输通道 + SDK)。注册表在源头构建其规范贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 +1. **PTC mode 是 `ToolRuntime`(`dsh-tools`)的一等呈现模式**,通过经校验的 `mode` 配置选择:`'native'`(默认,贡献可见能力 schema)或 `'ptc'`(注册表仅贡献其保留的 `run_code` 传输通道加一份生成的 SDK `.d.ts` 到系统提示词中)。注册表在源头构建其规范贡献;协作式提示词组装的结果仍具权威性,记录在日志中的请求头精确反映该返回的呈现。 2. **代码执行是一个能力 seam**——`packages/ptc-runtime/` 包含 Service Definition 包 `@deepseek-ai/dsh-ptc-runtime`,拥有 `ctx.ptcRuntime`([能力 seam](../architecture/2026-06-13-capability-seams.zh.md);消费方 = `dsh-tools`,core 消费 seam 的先例见 `agent-loop` → `dsh-llm`)。运行时对工具一无所知:它接收一段程序和命名的异步绑定,执行程序,报告 `{ value, logs, error? }`。语言和基底是后端属性,因此未来的 Python 或容器后端只是另一个 Service Provider 包,而非重新设计。 3. **交付的实现是 `@deepseek-ai/dsh-ptc-runtime-node`**:每个程序在全新的受管 Node 进程中,按调用 Session 的已解析沙箱策略运行。直接 Node API 在该策略内仍可使用;具名 Host 绑定通过单独验证的控制通道调用。 @@ -32,13 +32,13 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 ### 注册表拥有模式 -`ToolRuntime` 获得一个经 schemastery 校验的配置(`static Config`),这是它的第一个配置:`mode: 'native' | 'ptc' | 'both'`,默认 `'native'`。部署通过 `cordis.yml` 翻转模式(`tools: { mode: ptc }`),无需改代码,遵循 no-hardcoded-tunables 约定。 +`ToolRuntime` 获得一个经 schemastery 校验的配置(`static Config`),这是它的第一个配置:`mode: 'native' | 'ptc'`,默认 `'native'`。部署通过 `cordis.yml` 翻转模式(`tools: { mode: ptc }`),无需改代码,遵循 no-hardcoded-tunables 约定。 -**协议工具列表。** 注册表在 `'native'` 下贡献可见能力,在 `'ptc'` 下仅贡献 `run_code`,在 `'both'` 下两者都贡献。最终的 `PromptAssembly.tools` 列表记录在请求头中。`run_code` 是一个保留的呈现传输通道,位于注册和限制层之外;直接提示词提供方和组装 waterfall 仍各自负责自己的贡献。 +**协议工具列表。** 注册表在 `'native'` 下贡献可见能力,在 `'ptc'` 下仅贡献 `run_code`。[呈现方式简化决策](../simplification/2026-10-05-native-or-ptc-presentation.zh.md) 记录混合呈现的移除。最终的 `PromptAssembly.tools` 列表记录在请求头中。`run_code` 是一个保留的呈现传输通道,位于注册和限制层之外;直接提示词提供方和组装 waterfall 仍各自负责自己的贡献。 **与 `toolOrder` 的交互:** 如果配置的 `systemPrompt.toolOrder` 引用了原生能力名称,在 `mode: 'ptc'` 下会拒绝所有组装,因为那些名称不在该模式的协议校验范围内。这是正确行为而非 bug:使用 PTC mode 的部署需要更新其 order 配置或移除它。 -**SDK 提示词段。** 在 `'ptc'` 和 `'both'` 下,tool-guidance order band 中的惰性 `tools:sdk` 段为当前 scope 的可见能力渲染所加载运行时语言的声明加固定的使用说明(默认 TypeScript;[语言分发 note](../../archived/feature/2026-07-31-ptc-language-dispatch.md) 加入了 Python 与按 `ctx.ptcRuntime.language` 选择的渲染器表)。它共享查找和执行可见性,排除 `run_code`,并按字典序排列工具以获得字节稳定的输出。 +**SDK 提示词段。** 在 `'ptc'` 下,tool-guidance order band 中的惰性 `tools:sdk` 段为当前 scope 的可见能力渲染所加载运行时语言的声明加固定的使用说明(默认 TypeScript;[语言分发 note](../../archived/feature/2026-07-31-ptc-language-dispatch.md) 加入了 Python 与按 `ctx.ptcRuntime.language` 选择的渲染器表)。它共享查找和执行可见性,排除 `run_code`,并按字典序排列工具以获得字节稳定的输出。 **组装所有权。** `run_code` 和 `tools:sdk` 作为正常的组装输入进入受信任的 `system-prompt/assemble` waterfall。一个 scoped 的 `tools:sdk` 段可以在分发前遮蔽全局默认值,监听器也可以移除或替换任一贡献。waterfall 返回的组装结果是最终的,因此修改这些输入的人有责任在部署期望 PTC mode 可用时保持协议面的完整性;没有恢复 pass 会覆盖有意的组合。 @@ -46,15 +46,15 @@ Cloudflare 的 [Code Mode](https://blog.cloudflare.com/code-mode/) 提出了一 ### run_code 工具与分发桥 -在 `'ptc'` 和 `'both'` 下,注册表拥有 `run_code` 作为保留的呈现传输通道,带两个必需参数 `{ code: string; description: string }`(description 为 UI 标注该调用,沿用 bash 的先例)。它由一个正常的 `ToolDefinition` 表示以供分发,但位于可过滤的能力层之外,因此限制规则不会意外移除 PTC mode 的唯一入口。调用遍历完整的工具流水线——`tools/pre-execute` → 单调性守卫 → `tools/execute` 包裹分发 → `tools/post-execute` → 由定义拥有的可选 `finalizeContent` → 不可变的 `tools/result` 通知——与原生调用完全一致;权限插件可以在程序运行前检查程序文本,最终结果观察者看到的是规范化的外层结果。其 `execute(args, exec)`: +在 `'ptc'` 下,注册表拥有 `run_code` 作为保留的呈现传输通道,带两个必需参数 `{ code: string; description: string }`(description 为 UI 标注该调用,沿用 bash 的先例)。它由一个正常的 `ToolDefinition` 表示以供分发,但位于可过滤的能力层之外,因此限制规则不会意外移除 PTC mode 的唯一入口。调用遍历完整的工具流水线——`tools/pre-execute` → 单调性守卫 → `tools/execute` 包裹分发 → `tools/post-execute` → 由定义拥有的可选 `finalizeContent` → 不可变的 `tools/result` 通知——与原生调用完全一致;权限插件可以在程序运行前检查程序文本,最终结果观察者看到的是规范化的外层结果。其 `execute(args, exec)`: -1. **构建绑定。** 一个 run 级别的 signal 跟随外层取消,并在 run 结算时被 abort。每个可见工具绑定都会对无损 JSON 参数创建快照,进入原生约定的分发池(调度设计由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责),以确定性的 call id 和外层 token 作为 `parent` 执行,通过外层 execution 延后返回的上下文,并记录 `tool/ptc-dispatch-start`/`tool/ptc-dispatch` 事件对,其中结算侧携带完整渲染后的结果内容。成功时返回工具最终的规范 JSON 值;失败则变为程序可见的 `ToolCallError`。每个子调用保留自己不可变的执行标识,并遍历完整的工具流水线。 +1. **构建绑定。** 一个 run 级别的 signal 跟随外层取消,并在 run 结算时被 abort。每个可见工具绑定都会对无损 JSON 参数创建快照,进入原生约定的分发池(调度设计由[实时并行 参考](../../../../packages/core/tools/README.zh.md) 负责),以确定性的 call id 和外层 token 作为 `parent` 执行,通过外层 execution 延后返回的上下文,并记录 `tool/ptc-dispatch-start`/`tool/ptc-dispatch` 事件对,其中结算侧携带完整渲染后的结果内容。成功时返回工具最终的规范 JSON 值;失败则变为程序可见的 `ToolCallError`。每个子调用保留自己不可变的执行标识,并遍历完整的工具流水线。 2. **运行已解析输入:** `ctx.ptcRuntime.run(ctx.ptcRuntime.resolve(request))`。请求携带程序、绑定、运行级信号、调用 Session 的 cwd 与适用的已解析文件策略。运行级信号将外层结算与执行取消关联。 3. **完全停稳后结算。** 运行时结算后,桥 abort 未完成的工作并排空分发队列后再返回。成功时返回捕获的日志和完成值,将其作为规范输出;注册表再把该值渲染为持久化的 `tool/result.content`,供结果卡片直接读取。运行时失败变为 `CodeRunFailedError`;后端拒绝使用注册表的正常错误边界。两者都产生结构化的错误结果,且 `run_code` 结算后不允许子调用追加。 **子调用上下文通过父调用延后。** 在 `run_code` 内部注入会破坏父调用/结果的相邻性,因此 `ToolRunContext.deferContext()` 按分发顺序收集每个子结果的 `additionalContexts` 条目。即使程序后来抛出异常,注册表仍携带该数组;循环只在外层结果与步骤中所有兄弟结果之后追加每个条目。外层 post-execute 阻止会丢弃工具延后的条目,只暴露阻止 decision 显式附加的上下文。 -**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责。 +**并发是有界的,而非被序列化。** 每次 run 拥有一个分发队列,严格按提交顺序启动调用,并通过 `registry.executionMode` 对每个调用分类——与原生循环所用的 fail-closed `isConcurrencySafe` 约定相同。连续的 parallel 类调用最多重叠 `maxParallelSubCalls` 个(默认 10;设为 `1` 恢复串行分发);exclusive 类调用会排空池并单独运行。结算时放弃尚未开始的排队调用。本 note 交付的是被序列化的占位实现;取代它的调度器由[实时并行 参考](../../../../packages/core/tools/README.zh.md) 负责。 **呈现。** `run_code` 的 render intent 按[呈现意图 Agent Note](../architecture/2026-07-02-tool-render-intent-union.zh.md)在此决定:`presentCall` 创建一个 `generic` 卡片,`kind: 'execute'`,以程序文本作为标题,并将同一程序文本作为 `rawInput`;`run_code` 有意不声明 `presentResult`,因此 TUI 和宿主/客户端运行时(Web)会通过通用原始内容回退机制,使用最终持久化的 `tool/result.content` 补全该卡片,其中包括捕获的日志,以及返回值、失败信息或 post-policy spill 预览。这不是 `terminal` 卡片:该卡片的语义是「工作目录中的 shell 命令」,程序不是。参见[结果卡片完整性说明](../../archived/bug-fix/2026-07-20-code-mode-result-card-completeness.md)。 @@ -86,7 +86,7 @@ Node 提供方擦除可擦除 TypeScript,在配置的文件系统/子进程 ### 模型看到的内容 -SDK 指示模型编写一个所加载运行时语言的异步函数体(默认可擦除 TypeScript;Python 运行时下为 Python `async` 函数体——见[语言分发 note](../../archived/feature/2026-07-31-ptc-language-dispatch.md)),通过 `await tools.name(args)` 调用工具,在需要时捕获被拒绝的工具调用,并仅 return 或 log 应重新进入上下文的输出。两种 flavor 用各自的原语陈述同一约定:相互独立的只读调用可以(MAY)在 `Promise.all`(TypeScript)或 `asyncio.gather`(Python)下重叠,有副作用的调用按提交顺序单独运行,有依赖的工作用 `await` 排序。声明前缀可能与原生 schema 一样大,尤其在 `'both'` 下,但对提供方缓存保持稳定。 +SDK 指示模型编写一个所加载运行时语言的异步函数体(默认可擦除 TypeScript;Python 运行时下为 Python `async` 函数体——见[语言分发 note](../../archived/feature/2026-07-31-ptc-language-dispatch.md)),通过 `await tools.name(args)` 调用工具,在需要时捕获被拒绝的工具调用,并仅 return 或 log 应重新进入上下文的输出。两种 flavor 用各自的原语陈述同一约定:相互独立的只读调用可以(MAY)在 `Promise.all`(TypeScript)或 `asyncio.gather`(Python)下重叠,有副作用的调用按提交顺序单独运行,有依赖的工作用 `await` 排序。声明前缀可能与原生 schema 一样大,但对提供方缓存保持稳定。 传输自身的 `description` 与两种 flavor 的 SDK 说明都以点名 `code` 和 `description` 这两个必填参数开头。把该调用描述成「传入一个程序」的散文会让第二个参数只能从参数 schema 中发现,而只发出 `{code}` 的模型会因 `INVALID_ARGS` 被拒,连同已写好的整个程序一起丢失。 @@ -113,7 +113,7 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 **始终排他(忠于 Cloudflare,无模式)。** 否决,因为本 SDK 的主要消费方是编码 agent:其日常的单次调用(`bash`、`read`、`edit`)作为原生调用已经是最优的,强制每次编辑都通过程序会给常见场景增加负担。mode 配置让忠实形式(`'ptc'`)只需一行配置即可启用,而不强加于人。 -**每工具可见性分层(此工具 native,彼工具 ptc-only)。** 推迟:它需要每工具元数据和 `'native' | 'ptc' | 'both'` 不提供的呈现拆分,且其设计取决于模型在 `'both'` 下如何分配使用的证据。 +**每工具可见性分层(此工具 native,彼工具 ptc-only)。** 推迟:它需要每工具元数据,以及针对原生操作和仅限程序操作的独立准入规则。整个 agent 采用原生或 PTC 呈现,可让每个 agent 保持一种调用约定。 **SDK 中的清洁化标识符别名**(`my-tool` → `my_tool`,Cloudflare 的做法)。否决:`declare const` 上的带引号键使每个名称可达,零别名碰撞逻辑;模型能正常处理 `tools["my-tool"](…)`。 @@ -125,12 +125,12 @@ SDK 指示模型编写一个所加载运行时语言的异步函数体(默认 **`stripTypeScriptTypes` 标记为 experimental。** 它与 Node 自身原生 `.ts` 执行背后的引擎(amaro/swc)相同,在本仓库的整个引擎范围内作为 API 暴露。缓解措施:运行时的单元测试套件会检查位置保持和可擦除限制拒绝消息中的必需部分;调用位于一个私有函数之后,且 `amaro`/`sucrase` 可在 API 变化时直接替换它。仅可擦除子集是面向模型的输入限制,错误消息会告诉模型如何修正程序。 -**SDK 的提示词成本,尤其在 `'both'` 下。** `.d.ts` 可能与它补充的原生 schema 体量相当;`'both'` 携带两种表示。前缀稳定性 + 提供方缓存摊销了每会话成本;mode 按部署配置;本 Agent Note 不做无条件节省的声明。何时优先使用哪种模式的量化指导明确属于上线后学习。 +**SDK 的提示词成本。** `.d.ts` 可能与它替代的原生 schema 体量相当。前缀稳定性 + 提供方缓存摊销了每会话成本;mode 按部署配置;本 Agent Note 不做无条件节省的声明。何时优先使用哪种模式的量化指导明确属于上线后学习。 **注册表 scope 增长。** `dsh-tools` 吸收了代码生成、一个工具、一个桥和一个事件。包内模块把这些职责分开(`ts-types.ts`、`ptc.ts` 与 `schema.ts`、`json-schema.ts`、`presentation.ts` 并列),所有 ptc-runtime 专用实现都由 `ctx.ptcRuntime` 提供。 **大型绑定值仍可能耗尽内存。** Node 提供方限制控制帧、未完成参数和排队写入,而 Host 绑定可以在这些检查前分配其结果。无损 JSON 传输与外层输出账本不构成进程级分配配额。 -**子分发的重叠由工具自身的安全声明限定,而非由调用方决定。** 程序里的 `Promise.all` 或 `asyncio.gather` 只在工具自己分类为并发安全的调用之间换来挂钟并行性;一串 exclusive 调用仍要按顺序付出各自的往返开销,模型可能过度期望。两种 flavor 的 SDK 说明都陈述了真实约定。本 note 交付的是使该风险绝对化的序列化占位实现;调度器及其重叠上限由[实时并行 Agent Note](2026-07-26-ptc-live-parallel-dispatch.zh.md) 负责。 +**子分发的重叠由工具自身的安全声明限定,而非由调用方决定。** 程序里的 `Promise.all` 或 `asyncio.gather` 只在工具自己分类为并发安全的调用之间换来挂钟并行性;一串 exclusive 调用仍要按顺序付出各自的往返开销,模型可能过度期望。两种 flavor 的 SDK 说明都陈述了真实约定。本 note 交付的是使该风险绝对化的序列化占位实现;调度器及其重叠上限由[实时并行 参考](../../../../packages/core/tools/README.zh.md) 负责。 **经过时间截止包括被等待的工作。** 嵌套工具和审批等待消耗程序的经过时间预算。Host 在截止到期时停止执行并等待受管清理;它不计量进程树 CPU 消耗。沙箱 Node 决策记录当前默认值与延后评估的 timeout 问题。 diff --git a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml deleted file mode 100644 index 4b48c194fe..0000000000 --- a/.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-18-compaction-capability-seam.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend: - en: 6808ccbe73762daa - zh: 11f3669e6ae4512c -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/problem: - en: ed3fddc1dce0f229 - zh: fab2663b344cd851 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/compaction-is-a-capability-seam-with-separate-service-definition-and-service-provider-roles: - en: 785c69730accbb4d - zh: ee9bd2a8a7dc3b72 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/the-contract-depends-on-dsh-session-and-dsh-llm-a-deliberate-deviation: - en: 5c07a5a6429f6752 - zh: 8bf83da6e0563a66 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/three-abstract-operations-algorithm-in-the-backend: - en: 62191e0e65c79673 - zh: cbaa5bece0f5b45a -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/automatic-pressure-runs-after-successful-durable-step-work: - en: b246f9837c9c9a6b - zh: c19c51456c8fd8b7 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/retention-is-turn-agnostic-tool-pairing-balance-is-the-only-structural-guard: - en: 3126c249c48e90b9 - zh: dc82bc9a37d90474 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/head-anchoring-one-auto-checkpoint-always-at-the-head: - en: 26f187b321217e4c - zh: b324dcdb6da4bf02 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/approximate-convergence-invariant: - en: 4870032f23833fc4 - zh: b30180c31ba84a5a -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/surface-replacement-compaction-events-are-log-only-one-user-message-carries-the-summary: - en: 6d50dfe66a35f6fb - zh: 6ff9e66553f10ac6 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/checkpoint-framing-incremental-merge-backend-private: - en: 1c71cf67450e78d5 - zh: fb44b3cf5cee070a -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/blocking-via-a-log-recorded-lock-plus-a-crash-recoverable-failure-taxonomy: - en: 5aed57a49e031b1e - zh: 1424f332197ef541 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/alternatives-considered: - en: 24a7bb1949445136 - zh: 7914ea99f498ed67 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/consequences: - en: 400bf1cadb974d04 - zh: 7612a4634ae98b32 -/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/testing: - en: 843476bd607fe832 - zh: 50be0b06553a54d0 diff --git a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml deleted file mode 100644 index 2362f9cac7..0000000000 --- a/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.i18n.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-21-subagent-capability-seam.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md -/agent-note-subagent-capability-seam: - en: 3c051f92759dd9c4 - zh: fceee1b8289f2779 -/agent-note-subagent-capability-seam/problem: - en: 5c81597e6dc16c39 - zh: c30c6b5dce12bfae -/agent-note-subagent-capability-seam/alternatives-considered: - en: 6119e2aff3b6be5a - zh: 76136ef5249c23eb -/agent-note-subagent-capability-seam/alternatives-considered/why-not-the-bash-seam-shape: - en: 3a01c98c45de8585 - zh: 7f87d8bd6d348dae -/agent-note-subagent-capability-seam/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-subagent-capability-seam/decision/the-three-package-boundary: - en: c470cd9518cebc8b - zh: 228b559f73c58f2b -/agent-note-subagent-capability-seam/decision/the-primitive-async-start-subagentrun: - en: 08250dc4d5faf918 - zh: 1b413cb438af6796 -/agent-note-subagent-capability-seam/decision/two-kinds-of-optional-capability-discovered-two-ways: - en: d2a27fcacc46ca76 - zh: 4e8cca931fcaec2c -/agent-note-subagent-capability-seam/decision/fork-vs-fresh-are-separate-backends-not-a-flag: - en: a8e071639738ea79 - zh: 3fd7cc7427843efe -/agent-note-subagent-capability-seam/decision/child-isolation-and-the-parent-log: - en: e970f97f27d11f4a - zh: 34ece0dfe5ec6185 -/agent-note-subagent-capability-seam/decision/synchronous-collect-first-cut: - en: a14200a7abfa532f - zh: ee4f8c08deb0ee99 -/agent-note-subagent-capability-seam/decision/transport-provider-selection-is-config-not-model-facing: - en: 3dab95ec93f918df - zh: 4c3c7d4e8357bf63 -/agent-note-subagent-capability-seam/testing: - en: 324951447c43d01a - zh: b04388c4665302d2 -/agent-note-subagent-capability-seam/consequences: - en: c60d3fb1012f52b4 - zh: f46ab4b214941622 diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.i18n.yaml b/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.i18n.yaml deleted file mode 100644 index b5de0ccbcd..0000000000 --- a/.agents/notes/implemented/feature/2026-06-30-interception-extension-points.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-30-interception-extension-points.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-interception-extension-points.md -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against: - en: 623ca9ecb3ed4c8e - zh: 5d5639dea9a6a541 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/problem: - en: 7ce5525628f7fc7d - zh: 53f27e5423da86f8 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/decision: - en: dc7daa9100978a8b - zh: c87aa08a4e085b7b -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/decision/the-tool-pipeline-gives-each-phase-one-kind-of-authority: - en: 65553dedec611132 - zh: 5b002349db90b83f -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/decision/three-load-bearing-loop-decisions: - en: d91bda956aa8bad9 - zh: 12bc8f819bf62ed2 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/decision/pre-tool-input-rewrite-is-a-separate-consistency-decision: - en: bea1cab63d0470f1 - zh: b3a1dba46e346109 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/decision/boundaries: - en: 30c23a1087faca81 - zh: 03d58723ee6b1a37 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/alternatives-considered: - en: d05468299caa114d - zh: d0b0eecf36456130 -/agent-note-interception-extension-points-the-typed-decision-surface-a-hook-programs-against/consequences: - en: 830c7bab979652be - zh: 7266e2d0780e9d67 diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml deleted file mode 100644 index b70097f9c9..0000000000 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-05-dynamic-workflows.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam: - en: 472dec484bf937c1 - zh: 1d6c38c699d43828 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/problem: - en: e890f8ac24054d94 - zh: 45ec14140f62e1c9 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision: - en: 16af14c0c9fc54ee - zh: 463dd56b53d05ea7 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision/the-script-contract-claude-code-compatible: - en: 889beb90c3650f25 - zh: b1ba5dac3c77f8d2 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision/the-seam-dsh-workflow: - en: d37f01afb4d9cd6a - zh: 5648b981b61e6624 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision/the-engine-dsh-workflow-ptc-shared-node-process-execution: - en: 7567d5fcf218e4b5 - zh: 2ba33b1237df9e4a -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision/the-consumer-dsh-tool-workflow: - en: 00b015ee9db948fb - zh: 6dac8d8aaf0b32d2 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/decision/the-foundation-structured-output-on-the-subagent-seam: - en: 40ad0d1f23ffa7db - zh: 23a81a277fb24cd9 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/testing: - en: a1607694b584dcbc - zh: 29c9c7560f0fcf56 -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/deferred-documented-non-goals: - en: 2bff807d808bfa13 - zh: add602509c0a826c -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/alternatives-considered: - en: 44a36cb0a91546cc - zh: f4ed8c49f3764d7a -/agent-note-dynamic-workflows-a-script-driven-multi-agent-orchestration-seam/consequences: - en: bfd2d827ffb20587 - zh: 310c4fe77f48aa4e diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml index 07e39db02d..c8979a44f7 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml @@ -6,8 +6,8 @@ en: 5a500ddf8910f3b2 zh: 0461f97e3c905d72 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/problem: - en: 0903931cf3fd2d9b - zh: ae39e774ffa66c36 + en: bbf79ea651c64540 + zh: 31c18cc9a96d4e86 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/decision: en: 058d4bd95d3c55d3 zh: a861c6a59340b121 @@ -24,8 +24,8 @@ en: 1d4478059f608fd2 zh: 4a1d12e4ccb766b5 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/decision/design-detail/the-per-session-policy-tier: - en: 4eaf3ae10290dc6a - zh: 0cb65c79a5e9effc + en: efb68e085f784862 + zh: ad73ca74d9174cd5 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/decision/design-detail/the-acp-answerer: en: bbfb8c3f1cb34b12 zh: 4f8f51a027a09a03 @@ -39,8 +39,8 @@ en: 01356985c175fcda zh: b1bb3b68f619e698 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/deferred: - en: 3a9a1ddf86364f14 - zh: ec8413e23f171bd2 + en: c6246315353b3e19 + zh: 3931b829e2288ac1 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/alternatives-considered: en: ae974b0f43a29b4e zh: bbc4662c0c63b6c1 @@ -48,8 +48,8 @@ en: 9d2097d7b796dd63 zh: 4f1a0493dc109b12 /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/faq: - en: 85f3a8270d96497d - zh: b97bedebc08f23f0 + en: 680b33c124d10384 + zh: 57ac659ffda68dad /agent-note-the-approval-seam-one-shot-permission-decisions-over-a-waterfall-of-answerers/prior-art: - en: 964a5c6afd4724d6 - zh: 1a386e1376860105 + en: 3672a3e2f52bf220 + zh: adc15c33f9d1884a diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md index bb44957b6d..dede6ae6b7 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md @@ -6,7 +6,7 @@ English | [中文](2026-07-06-approval-seam.zh.md) ## Problem -Two callers need one closed decision — "may this specific action proceed?": `tools/pre-execute`'s `ask` decision (including the Claude-Code hook bridge's `permissionDecision: ask`) and the [sandbox Agent Note](2026-07-06-sandbox.md)'s post-denial one-shot escalation retry. A shared seam keeps them from inventing separate outcome vocabularies, channel routing, cancellation, and audit trails, while guaranteeing that a deployment with no answerer can never grant an unanswerable request. The answerer may be an interactive host or an automated controller. +Two callers need one closed decision — "may this specific action proceed?": `tools/pre-execute`'s `ask` decision (including the Claude-Code hook bridge's `permissionDecision: ask`) and the [sandbox reference](../../../../packages/shell/tool-bash/README.md)'s post-denial one-shot escalation retry. A shared seam keeps them from inventing separate outcome vocabularies, channel routing, cancellation, and audit trails, while guaranteeing that a deployment with no answerer can never grant an unanswerable request. The answerer may be an interactive host or an automated controller. The routing problem is ownership: a permission request must reach the channel that owns the asking agent, fail closed for agents nobody owns, and stay out of deployments that compose no answerer. @@ -63,7 +63,7 @@ Answerers are `approval/request` waterfall listeners. Zero listeners fall throug #### The per-session policy tier -The seam also owns the session-scoped `'ask' | 'never'` policy described by [the sandbox Agent Note](2026-07-06-sandbox.md). Effective policy is folded from logged switches over the deployment default. `'never'` resolves to `rejected` inside `request()` before any answerer can run; `'ask'` dispatches and otherwise falls through to `unavailable`. Both current values join the atomic runtime-context snapshot before each model request, so a policy switch needs no separate narration; every approval request still records the audit pair. +The seam also owns the session-scoped `'ask' | 'never'` policy described by [the approval reference](../../../../packages/interaction/user-approval/README.md). Effective policy is folded from logged switches over the deployment default. `'never'` resolves to `rejected` inside `request()` before any answerer can run; `'ask'` dispatches and otherwise falls through to `unavailable`. Both current values join the atomic runtime-context snapshot before each model request, so a policy switch needs no separate narration; every approval request still records the audit pair. #### The ACP answerer @@ -87,7 +87,7 @@ Snapshots record allowed and rejected sandbox escalation through `session/reques ## Deferred -- **`allow_always` grant storage** — honoring a persistent grant means designing storage, scope identity (call? path? prefix? session? time window?), and revocation; until designed, only the one-shot options are advertised ([the sandbox Agent Note](2026-07-06-sandbox.md) § Escalation records the open scope question). +- **`allow_always` grant storage** — honoring a persistent grant means designing storage, scope identity (call? path? prefix? session? time window?), and revocation; until designed, only the one-shot options are advertised. - **A recorded hook-driven `ask` through a composed answerer** — the permission wire is recorded through the sandbox example's escalation branches. The hook matrix's `hook-cc-pretool-ask` pins the no-ApprovalService fallback denial, while the hook-producer-plus-answerer composition remains on the unit tier. - **Routing a child agent's approvals to the parent session** — `subagent-acp`'s child auto-answers its own permission requests; delegating them to the parent controller is its own design. @@ -123,7 +123,7 @@ Costs and accepted limits: - **Who decides whether a call asks in the first place?** Policy producers: a hook returning `permissionDecision: ask`, any `tools/pre-execute` listener, or the sandbox escalation gate. The seam and the bridge only route and answer; neither injects its own judgment about what deserves a prompt. - **What happens when the user dismisses the prompt, or the turn aborts mid-ask?** Dismissal maps to `cancelled` with its own deny text. An already-aborted signal settles `cancelled` without dispatching; an abort during the ask discards the late answer. When both audit appends commit, either path records one pair, never two. - **What if the client answers with an option the harness never offered?** Any selection other than the offered `allow_once` maps to `rejected` — an unknown optionId from a non-conforming client can never grant. -- **How do subagents' approvals route?** They do not: delegation pins every in-process child to `'never'` ([approvals-pinned decision](2026-08-10-subagent-approval-pinned-never.md)), so each child ask resolves `rejected` before any answerer and the child is told up front through its runtime context. `subagent-acp`'s child-side auto-answer is separate; routing a child's asks to the parent controller is deferred (§ Deferred). +- **How do subagents' approvals route?** They do not: delegation pins every in-process child to `'never'` ([approvals-pinned reference](../../../../packages/subagent/subagent/README.md)), so each child ask resolves `rejected` before any answerer and the child is told up front through its runtime context. `subagent-acp`'s child-side auto-answer is separate; routing a child's asks to the parent controller is deferred (§ Deferred). - **What does `policy: 'never'` actually change at runtime?** The service resolves every ask for that session to `rejected` before dispatching any answerer (in-service, so no registration order can bypass it); the next atomic runtime-context snapshot states the policy; each successful auto-rejection records the audit pair. - **What happens across a hot reload, or when an answerer unloads mid-session?** Answerers dispose with their owning fiber, so the next ask degrades to `unavailable` instead of hanging on a dead channel; remounting re-registers the answerer with no catch-up state. - **Where does a client get approval context?** The request carries the exact `callId` and the asker's human-readable `reason`; channel adapters may correlate richer tool-call state without duplicating arguments in the approval seam. @@ -134,6 +134,6 @@ In-repo precedents this design copies or contrasts with: - The `fs/write-intent` gate (`packages/fs/fs/`) — the documented single-occupancy decision-slot waterfall semantics (first answer wins, delegate via `next()`) the answerer contract reuses. - `hook/invoked`/`hook/result` — the log-only audit-pair precedent `approval/asked`/`approval/decided` follows; [the hook-bridges Agent Note](../../archived/feature/2026-06-30-hook-bridges.md) ships `permissionDecision: ask`, the first producer. -- [The interception extension-points Agent Note](2026-06-30-interception-extension-points.md) — the `tools/pre-execute` `allow`/`deny`/`ask` vocabulary whose `ask` this seam services. +- [The interception extension-points reference](../../../../docs/tool-execution-pipeline.md) — the `tools/pre-execute` `allow`/`deny`/`ask` vocabulary whose `ask` this seam services. - [The automation-only ACP Agent Note](../simplification/2026-07-23-acp-automation-only-protocol.md) — the exact-agent ownership check against the session map that the answerer routes through; [the multi-session Agent Note](../../archived/feature/2026-06-14-acp-multi-session.md) — the per-session permission-ownership blocker this implements. - The opportunistic `ctx.get()` consumption pattern (`tool-bash`'s owner-token lookup, the loop's persistence probe) — how `dsh-tools` consumes the seam without gating its fiber on it. diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md index b7e07a376c..61632594a1 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -两个调用方需要同一个封闭决策——「这个具体操作可以继续吗?」:`tools/pre-execute` 的 `ask` 决策(包括 Claude-Code 钩子桥的 `permissionDecision: ask`)以及[沙箱 Agent Note](2026-07-06-sandbox.zh.md) 中拒绝后的一次性升级重试。一个共享的 seam 使它们无需各自发明独立的结果词汇、通道路由、取消机制和审计轨迹,同时保证没有应答者的部署永远不会批准一个无法应答的请求。应答者可以是交互式宿主,也可以是自动化控制器。 +两个调用方需要同一个封闭决策——「这个具体操作可以继续吗?」:`tools/pre-execute` 的 `ask` 决策(包括 Claude-Code 钩子桥的 `permissionDecision: ask`)以及[沙箱 参考](../../../../packages/shell/tool-bash/README.zh.md) 中拒绝后的一次性升级重试。一个共享的 seam 使它们无需各自发明独立的结果词汇、通道路由、取消机制和审计轨迹,同时保证没有应答者的部署永远不会批准一个无法应答的请求。应答者可以是交互式宿主,也可以是自动化控制器。 路由问题的核心是归属:权限请求必须到达拥有发起请求的 agent(智能体)的通道,对无人拥有的 agent 失败关闭,并且不侵入没有组合应答者的部署。 @@ -63,7 +63,7 @@ tool/result "escalated" — this one call ran under the wider mode; the gra #### 每会话策略层 -seam 还拥有[沙箱 Agent Note](2026-07-06-sandbox.zh.md) 所描述的会话级 `'ask' | 'never'` 策略。生效策略由日志中记录的切换在部署默认值之上折叠而成。`'never'` 会在任何应答者运行之前,于 `request()` 内部解析为 `rejected`;`'ask'` 则派发请求,否则一路委派至 `unavailable`。两个当前值都会在每次模型请求前加入原子化的运行时上下文快照,因此策略切换无需单独叙述;每次审批请求仍会记录审计对。 +seam 还拥有[审批参考](../../../../packages/interaction/user-approval/README.zh.md)所描述的会话级 `'ask' | 'never'` 策略。生效策略由日志中记录的切换在部署默认值之上折叠而成。`'never'` 会在任何应答者运行之前,于 `request()` 内部解析为 `rejected`;`'ask'` 则派发请求,否则一路委派至 `unavailable`。两个当前值都会在每次模型请求前加入原子化的运行时上下文快照,因此策略切换无需单独叙述;每次审批请求仍会记录审计对。 #### ACP 应答者 @@ -87,7 +87,7 @@ ACP 桥只应答其会话映射所拥有的精确 agent 对象。它携带既有 ## 延后 -- **`allow_always` 授权存储**:兑现持久授权意味着设计存储、作用域标识(调用?路径?前缀?会话?时间窗口?)和撤销;在设计完成之前,只展示一次性选项([沙箱 Agent Note](2026-07-06-sandbox.zh.md) § Escalation 记录了开放的作用域问题)。 +- **`allow_always` 授权存储**:兑现持久授权意味着设计存储、作用域标识(调用?路径?前缀?会话?时间窗口?)和撤销;在设计完成之前,只展示一次性选项。 - **通过组合应答者录制由钩子驱动的 `ask`**:权限协议格式(wire format)已通过沙箱示例的升级分支录制。钩子矩阵中的 `hook-cc-pretool-ask` 固定无 ApprovalService 时的后备拒绝,而钩子生产者与应答者的组合仍留在单元测试层。 - **将子 agent 的审批路由到父会话**:`subagent-acp` 的子侧自动应答自己的权限请求;将其委派给父控制器是独立的设计。 @@ -123,7 +123,7 @@ ACP 桥只应答其会话映射所拥有的精确 agent 对象。它携带既有 - **谁决定一次调用是否需要 ask?** 策略生产者:返回 `permissionDecision: ask` 的钩子、任何 `tools/pre-execute` 监听器、或沙箱升级门禁。seam 和桥只负责路由和应答;二者都不注入自己对「什么值得弹出提示」的判断。 - **用户关闭提示或轮次在 ask 进行中中止时会发生什么?** 关闭映射为 `cancelled` 并携带自己的拒绝文本。已中止的 signal 直接结算为 `cancelled` 而不派发;ask 进行中的中止丢弃迟到的应答。当两个审计追加都提交时,任一路径都记录恰好一对事件,绝不会两对。 - **如果客户端以 harness 从未提供的选项应答呢?** 除已提供的 `allow_once` 之外的任何选项都映射为 `rejected`——来自不合规客户端的未知 optionId 永远不能授权。 -- **subagent 的审批如何路由?** 不路由:委派会把每个进程内子 agent 钉定为 `'never'`([审批钉定决策](2026-08-10-subagent-approval-pinned-never.zh.md)),因此子 agent 的每次 ask 都在任何应答者之前解析为 `rejected`,子 agent 则通过其运行时上下文一开始就会得知。`subagent-acp` 的子侧自动应答是独立的;将子 agent 的 ask 路由到父控制器已延后(§ 延后)。 +- **subagent 的审批如何路由?** 不路由:委派会把每个进程内子 agent 钉定为 `'never'`([审批钉定参考](../../../../packages/subagent/subagent/README.zh.md)),因此子 agent 的每次 ask 都在任何应答者之前解析为 `rejected`,子 agent 则通过其运行时上下文一开始就会得知。`subagent-acp` 的子侧自动应答是独立的;将子 agent 的 ask 路由到父控制器已延后(§ 延后)。 - **`policy: 'never'` 在运行时实际改变了什么?** 服务在派发任何应答者之前,将该会话的每次 ask 解析为 `rejected`(在服务内部,因此没有注册顺序能绕过它);下一份原子化的运行时上下文快照会声明该策略;每次成功的自动拒绝都会记录审计对。 - **热重载或应答者在会话中途卸载时会发生什么?** 应答者随其拥有的 fiber 一起 dispose,因此下一次 ask 降级为 `unavailable` 而非挂在死通道上;重新挂载会重新注册应答者,无需追赶状态。 - **客户端从哪里获得审批上下文?** 请求携带精确的 `callId` 和发起方的人类可读 `reason`;通道适配器可自行关联更丰富的工具调用状态,而无需在审批 seam 中重复携带参数。 @@ -134,6 +134,6 @@ ACP 桥只应答其会话映射所拥有的精确 agent 对象。它携带既有 - `fs/write-intent` 门禁(`packages/fs/fs/`)——文档化的单占用决策槽 waterfall 语义(先到先得,通过 `next()` 委派),应答者约定复用了它。 - `hook/invoked`/`hook/result`——仅日志审计对先例,`approval/asked`/`approval/decided` 沿用了它;[钩子桥 Agent Note](../../archived/feature/2026-06-30-hook-bridges.md) 交付了 `permissionDecision: ask`,即第一个生产者。 -- [拦截扩展点 Agent Note](2026-06-30-interception-extension-points.zh.md)——`tools/pre-execute` 的 `allow`/`deny`/`ask` 词汇,本 seam 服务其中的 `ask`。 +- [拦截扩展点 参考](../../../../docs/tool-execution-pipeline.zh.md)——`tools/pre-execute` 的 `allow`/`deny`/`ask` 词汇,本 seam 服务其中的 `ask`。 - [仅面向自动化的 ACP Agent Note](../simplification/2026-07-23-acp-automation-only-protocol.zh.md)——应答者路由时对会话映射执行的精确 agent 归属检查;[多会话 Agent Note](../../archived/feature/2026-06-14-acp-multi-session.md)——本设计实现的每会话权限归属阻塞项。 - 机会性 `ctx.get()` 消费模式(`tool-bash` 的 owner-token 查找、loop 的持久化探测)——`dsh-tools` 消费该 seam 而不阻塞其 fiber 的方式。 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml deleted file mode 100644 index e29631633c..0000000000 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-06-sandbox.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-06-sandbox.md -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes: - en: b36f59d0746e94f1 - zh: 0d0f8e19917a9eae -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/problem: - en: 045e61bb682755dc - zh: ac95eb64ce892f8d -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision: - en: f4748553d52935ff - zh: 1802a543c6d989ef -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/how-a-deployment-uses-it: - en: 956a2c069b1ac0b1 - zh: ebf91494b8107ced -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail: - en: a6d7884e4359af3a - zh: e72588705942000a -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/scope-grounding: - en: 0c09534b59e2063e - zh: 5225d6750d13b9d7 -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/the-seam-ctx-sandbox: - en: 2c98e8c0bd8a9ff1 - zh: 661b58f81f6e05c6 -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/local-backends-and-the-shipped-launcher: - en: 5c6bf38f958b07d0 - zh: 22bf9fac71eb643d -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/asynchronous-preparation: - en: 7bc0fab85c7c91d0 - zh: 3eafcde6b1e3249c -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/the-bash-consumer: - en: f7f359c89a0e8fd6 - zh: 07d0d551a35716e0 -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/escalation-one-approved-wider-retry-after-a-denial: - en: 035640e276225e97 - zh: 09fd95921529369b -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/per-session-modes-the-session-log-as-the-store: - en: 6805c807f584961c - zh: 687fd4ad687e4dee -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/design-detail/in-process-tools: - en: 73cd47df9f2acbd0 - zh: 877db86c2a36a9ab -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/decision/testing: - en: c0189d491354eb48 - zh: ac68b07188402f9c -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/deferred-phases: - en: f28b530000b8495e - zh: f256b8b32c4ce4ef -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/alternatives-considered: - en: eabe577b837f3de2 - zh: 4ef0167c895766a6 -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/consequences: - en: c0d8279a677a0ef1 - zh: 3518fc05f8700c0b -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/faq: - en: de45c2a398b8abe6 - zh: 93be112960b40736 -/agent-note-the-subprocess-sandbox-confinement-seam-native-runners-escalation-and-per-session-modes/prior-art: - en: a51744b1cafca339 - zh: 1d27f49f1aa550af diff --git a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml b/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml deleted file mode 100644 index 751b41f4c6..0000000000 --- a/.agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.i18n.yaml +++ /dev/null @@ -1,94 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-07-mcp-client-plugin.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools: - en: 7d41e632fd40efe0 - zh: 4c7f6410b74a5bd3 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/problem: - en: 7cef5ed5bc88fba5 - zh: 795917e3321148ab -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/package: - en: 90a6e5a22fa99a36 - zh: 4280a5feecd3e6c1 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/sdk: - en: f69bbd1cfca85fa0 - zh: 9b21abc7e3eeb2fd -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/scope: - en: 32cd57e8d3ea1ac1 - zh: 7c14401492f22c32 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/plugin-shape: - en: 3d6e4726e5c890a6 - zh: 888123ed412544a7 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/configuration: - en: a244e4d7a8294424 - zh: 4be5ed51345a660e -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/lifecycle: - en: b428a4e72dec4ba0 - zh: c070db6fdd2d3c7f -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/tool-discovery-and-registration: - en: 75da266ca8141a90 - zh: 078e3a969273ed9b -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/public-name-normalization: - en: eb0511bd6173c99c - zh: c5fc98231a733699 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/name-conflict-handling: - en: 88210585c857f934 - zh: fc0d90d572d6ba45 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/naming-invariants: - en: 1e2ce84605977339 - zh: 3b3c9d120c0262a7 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/tool-execution: - en: c82db842e7a6f3db - zh: ace9e266cd6c4203 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/subprocess-environment-stdio-transport: - en: d599a7db35e1f96d - zh: a2655e0410565c93 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/decision/disconnection-crash: - en: e8207a70c6511778 - zh: aa281be51d85be13 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered: - en: 6119e2aff3b6be5a - zh: 76136ef5249c23eb -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/mcp-server-side-expose-harness-tools-to-external-mcp-clients: - en: 831148b7e748eaa3 - zh: 845172ccc64d7a65 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/capability-seam-three-package-split-interface-impl-consumer: - en: caefc1146f9b2b16 - zh: 9c4a5d55019d8002 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/auto-reconnect-with-exponential-backoff: - en: e92d18530feb1ee3 - zh: 2fecc50b4b6a96d9 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/bridge-resources-and-prompts: - en: 606ee33502fa60cc - zh: 0c21afec01caf4c2 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/raw-model-facing-tool-names-with-an-optional-toolprefix: - en: ef886d275ac5734c - zh: 304c578e674f50a2 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/server-only-namespace-github-create-issue-no-mcp-marker: - en: d18be656bd334d18 - zh: b7cdc2d580e9f2ab -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/deriving-the-namespace-from-the-server-announced-serverinfo-name: - en: 17bc5d831e3d06a7 - zh: 6b9e2f5cd76d12f8 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/preserve-multiple-textblocks-in-tool-result: - en: 184f6d75be815400 - zh: a284afac1e2b80e2 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/replace-the-canonical-mcp-result-with-core-contentblock: - en: d5d253f8e466bab9 - zh: 7d93b7fa1c2219ed -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/add-a-generic-richcontent-service-or-perform-i-o-in-output-render: - en: 687c220df3767635 - zh: c87dd8700edc9291 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/alternatives-considered/let-each-image-returning-tool-special-case-ptc-mode-parents: - en: a5fc7b17970699ce - zh: 2d7bd23621f62597 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/testing: - en: 4bb5be21c1097a57 - zh: d12bf01bb70e1bc3 -/agent-note-mcp-client-plugin-connect-to-external-mcp-servers-and-bridge-their-tools/consequences: - en: ef35ec18568b38e5 - zh: ca85076585c2b787 diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml deleted file mode 100644 index 67063fa1b4..0000000000 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-08-self-referential-cordis-toolset.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md -/agent-note-cordis-runtime-inspection-and-runner-isolation: - en: d96c123d1989b22b - zh: 4a53cdd763955f19 -/agent-note-cordis-runtime-inspection-and-runner-isolation/problem: - en: 29d1465c348331e9 - zh: 25dbc885f5d7ee7d -/agent-note-cordis-runtime-inspection-and-runner-isolation/decision: - en: 8a4cbb660f2409d7 - zh: 2219e39468e6baee -/agent-note-cordis-runtime-inspection-and-runner-isolation/alternatives-considered: - en: 637137a3b035c211 - zh: 5a667bfe2446a5f8 -/agent-note-cordis-runtime-inspection-and-runner-isolation/consequences: - en: a21f854954e01db6 - zh: c16bbe7c3580b394 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml index 5867da4d43..639381d125 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml @@ -12,8 +12,8 @@ en: 532bb7268d9b93ce zh: ad47162233222ab0 /agent-note-parallel-tool-call-execution-by-per-call-safety/scheduling-and-ordering: - en: a485c530ea4366de - zh: 6f9915d726de9ac7 + en: 4e85280cc5eb8697 + zh: 8e6b09b1527784fa /agent-note-parallel-tool-call-execution-by-per-call-safety/safety-contract: en: 16c4ee08b65ead23 zh: df80d6e49982615b diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md index 004d725bfd..40adc9f9aa 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md @@ -46,9 +46,9 @@ Only dispatch and the tool body overlap. `tools/pre-execute` and `tools/post-exe Each started call appends `tool/call` immediately before its pre-execute gate. Completed dispatches occupy model-order slots, and a commit cursor appends `tool/result` and collects `additionalContexts` only when the next slot is ready. Live surfaces may show several pending calls, but results and post-tool context remain model-ordered. -An abort stops replenishment, waits for already-started calls, commits their results in order, drains accepted batch context after those results, and records synthetic aborted call/result pairs for undispatched calls. An unexpected scheduler failure stops new dispatches, waits for every already-started dispatch to settle, and rethrows the first failure. The driver records missing results before closing the failed step under the [failed-step recovery decision](../bug-fix/2026-09-19-failed-step-tool-results.md). +An abort stops replenishment, waits for already-started calls, commits their results in order, drains accepted batch context after those results, and records synthetic aborted call/result pairs for undispatched calls. An unexpected scheduler failure stops new dispatches, waits for every already-started dispatch to settle, and rethrows the first failure. The driver records missing results before closing the failed step under the [failed-step recovery reference](../../../../packages/core/agent-loop/README.md). -PTC mode remains outside this scheduler because the model emits one native `run_code` call. `run_code` and its internal dispatch queue remain serial; native sibling calls in `mode: 'both'` use the normal scheduler. +The loop schedules native sibling calls and each outer `run_code` call. A `run_code` program has its own bounded dispatch pool, which uses the same parallel/exclusive classification; the [PTC execution reference](../../../../packages/core/tools/README.md#ptc-mode) owns that nested scheduling. ## Safety contract diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md index e78f56e07c..536541370b 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md @@ -46,9 +46,9 @@ Status: implemented 每个已启动的调用都会在进入 pre-execute 门禁之前立即追加 `tool/call`。已完成的派发占据模型顺序的槽位;提交游标只有在下一个槽位就绪时,才会追加 `tool/result` 并收集 `additionalContexts`。实时界面可以显示多个待处理调用,但结果和工具执行后的上下文仍按模型顺序排列。 -中止会停止补充池,等待已启动的调用,按顺序提交其结果,在这些结果之后排空已接受的批次上下文,并为未派发的调用记录合成的中止调用/结果对。调度器发生意外故障时,会停止新的派发,等待每项已启动的派发结算,并重新抛出第一个故障。驱动器按照[失败步骤恢复决策](../bug-fix/2026-09-19-failed-step-tool-results.zh.md),在关闭失败步骤之前记录缺失结果。 +中止会停止补充池,等待已启动的调用,按顺序提交其结果,在这些结果之后排空已接受的批次上下文,并为未派发的调用记录合成的中止调用/结果对。调度器发生意外故障时,会停止新的派发,等待每项已启动的派发结算,并重新抛出第一个故障。驱动器按照[失败步骤恢复参考](../../../../packages/core/agent-loop/README.zh.md),在关闭失败步骤之前记录缺失结果。 -PTC mode 仍不使用此调度器,因为模型只会发出一个原生 `run_code` 调用。`run_code` 及其内部派发队列仍按串行方式执行;`mode: 'both'` 中的原生并列调用使用常规调度器。 +循环调度原生并列调用和每个外层 `run_code` 调用。`run_code` 程序拥有自己的有界派发池,并使用相同的并行/独占分类;[PTC 执行参考](../../../../packages/core/tools/README.zh.md#ptc-mode)负责说明该嵌套调度。 ## 安全约定 diff --git a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml deleted file mode 100644 index 938869414c..0000000000 --- a/.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-14-cross-family-fs-sandbox.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity: - en: d971839c319030fa - zh: 29d8cc0ec85337af -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/problem: - en: 2ce70b520caa28b5 - zh: 6f023332dd48bf83 -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision: - en: dd825d09d6d850ed - zh: 0c355b56ff17d099 -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision/ctx-sandboxpolicy-one-home-for-mode-and-workspace-root: - en: e8ef62a480614c1a - zh: 90c29f6845e2f437 -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision/dsh-fs-sandbox-enforcement-inside-the-provider: - en: 077a767b86dd7860 - zh: e8797afed2f65fa6 -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision/tool-parity-one-denial-marker-one-escalation-flow: - en: f1fed3aaf3a1482c - zh: d4bc7989477b3a0b -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision/the-enforcement-point-provider-not-intent-gate: - en: 98549276110ac457 - zh: d680c26086c68a8c -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/decision/out-of-scope: - en: 9157891a477f17bd - zh: 58d926d0b86bc4fd -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/alternatives-considered: - en: 669431ca494a7783 - zh: e875a63c553135e1 -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/consequences: - en: 3848066c5a8c56ef - zh: 048cdf6cb8dbde4d -/agent-note-cross-family-file-sandbox-one-policy-home-a-sandboxed-fs-provider-and-fs-escalation-parity/testing: - en: dc226f5a587ff7d4 - zh: 55f5d937d6bc86e9 diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml deleted file mode 100644 index 624d9c4025..0000000000 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-16-harness-level-loop.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md -/agent-note-harness-level-goal-based-execution: - en: 20d103f4160b124d - zh: 36e888ebeec1d366 -/agent-note-harness-level-goal-based-execution/problem: - en: b0294f40b8915f39 - zh: 786bcecd8e0df343 -/agent-note-harness-level-goal-based-execution/decision: - en: f9d2e5774bca2d5b - zh: 375683705a21f16c -/agent-note-harness-level-goal-based-execution/decision/vocabulary-and-policy-boundary: - en: 213a55ec1ef4dd58 - zh: 56076b8524188153 -/agent-note-harness-level-goal-based-execution/decision/package-topology-and-owning-verbs: - en: fb8e9df7705a2293 - zh: ffe7040bdad4fa64 -/agent-note-harness-level-goal-based-execution/decision/durable-goal-state-and-live-authority: - en: ef386ad58ff3bcaf - zh: 9517886000faf515 -/agent-note-harness-level-goal-based-execution/decision/same-session-continuation: - en: 669447319e89b06a - zh: 2a08e6a8622aabd1 -/agent-note-harness-level-goal-based-execution/decision/human-and-model-interactions: - en: 8923f16b40a2f331 - zh: 697417b21f475c1e -/agent-note-harness-level-goal-based-execution/decision/fresh-agent-ralph-execution: - en: 3cc9d8fee8dfbd41 - zh: 2c4bf8aab739b0e8 -/agent-note-harness-level-goal-based-execution/decision/external-design-lineage: - en: 4a7b290a2649720c - zh: 1a2f32667061e7b4 -/agent-note-harness-level-goal-based-execution/decision/verification: - en: cb538a23eb170008 - zh: fdd00c5ca70918a1 -/agent-note-harness-level-goal-based-execution/alternatives-considered: - en: 5f4c2ba58601587c - zh: a7e2028dd8bc23c1 -/agent-note-harness-level-goal-based-execution/consequences: - en: 31d109628ee650f4 - zh: 2278dd884e6a088a -/agent-note-harness-level-goal-based-execution/known-limitations-and-deferred-work: - en: 2bc0e7709c516ed0 - zh: 614b4ddf8ca6ebc6 diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml index 6f55ce4f48..e56fd4028e 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.i18n.yaml @@ -18,8 +18,8 @@ en: 9bf79081f102eb51 zh: 34855888f0614b47 /agent-note-persistent-pty-sessions/decision/security-and-process-boundary: - en: 10d84442483e1d40 - zh: c970ae058e2e0f6d + en: 882bbfd3f0dc6e09 + zh: a547d6ff5e6ef7a1 /agent-note-persistent-pty-sessions/decision/six-model-facing-tools: en: b92715f36550c728 zh: e18d7e0e2363347a diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md index b309e1b1f9..43a707e661 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md @@ -45,7 +45,7 @@ A registered `shell` backend constrains how a terminal starts; it does not const Sandboxing confines local process effects but does not make arbitrary shell input safe: network calls and other external side effects remain governed by deployment policy. Tool descriptions state that PTY sessions are less auditable than one-shot tools and should be used only when persistence or interactive stdin is necessary. -The local subprocess terminal primitive uses only public `node-pty` capabilities: child PID, `data` and `exit` notifications, `write`, and `kill`. It does not assume access to the native master fd or call `waitpid` from TypeScript. On supported Linux hosts the [native-containment owner](../architecture/2026-08-28-subprocess-native-containment.md) starts that same PTY command inside a user-systemd scope without changing its PID, session, controlling terminal, foreground-group, or readiness semantics. Platform process inspectors below the primitive still derive foreground process groups and fallback parent/child identity from `/proc` on Linux and `ps` on macOS. The [portable execution-world decision](../architecture/2026-07-28-portable-execution-world-consumers.md) owns this process/consumer split. +The local subprocess terminal primitive uses only public `node-pty` capabilities: child PID, `data` and `exit` notifications, `write`, and `kill`. It does not assume access to the native master fd or call `waitpid` from TypeScript. On supported Linux hosts the [native-containment owner](../../../../packages/subprocess/subprocess-local/README.md) starts that same PTY command inside a user-systemd scope without changing its PID, session, controlling terminal, foreground-group, or readiness semantics. Platform process inspectors below the primitive still derive foreground process groups and fallback parent/child identity from `/proc` on Linux and `ps` on macOS. The [portable execution-world decision](../architecture/2026-07-28-portable-execution-world-consumers.md) owns this process/consumer split. ### Six model-facing tools diff --git a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md index 46257a6835..04891dc106 100644 --- a/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.zh.md @@ -45,7 +45,7 @@ agent scope dispose(资源释放)时先撤销注册,再等待全部所属 沙箱限制本地进程副作用,但不会让任意 shell 输入自动安全:网络调用和其他外部副作用仍由部署策略治理。工具描述会说明 PTY 会话比一次性工具更难审计,只应在确实需要持久状态或交互式 stdin 时使用。 -本地子进程终端原语只使用 `node-pty` 的公开能力:子进程 PID、`data` 与 `exit` 通知、`write` 和 `kill`。它不假设能访问原生 master fd,也不从 TypeScript 调用 `waitpid`。在受支持的 Linux 宿主上,[原生收容 owner](../architecture/2026-08-28-subprocess-native-containment.zh.md)会在 user-systemd scope 内启动同一条 PTY 命令,同时保持 PID、session、控制终端、前台进程组与就绪语义。该原语下的平台进程检查器仍在 Linux 上通过 `/proc`、在 macOS 上通过 `ps` 推导前台进程组和 fallback 父子进程身份。[可移植执行环境决策](../architecture/2026-07-28-portable-execution-world-consumers.zh.md)负责定义这种进程/消费方拆分。 +本地子进程终端原语只使用 `node-pty` 的公开能力:子进程 PID、`data` 与 `exit` 通知、`write` 和 `kill`。它不假设能访问原生 master fd,也不从 TypeScript 调用 `waitpid`。在受支持的 Linux 宿主上,[原生收容 owner](../../../../packages/subprocess/subprocess-local/README.zh.md)会在 user-systemd scope 内启动同一条 PTY 命令,同时保持 PID、session、控制终端、前台进程组与就绪语义。该原语下的平台进程检查器仍在 Linux 上通过 `/proc`、在 macOS 上通过 `ps` 推导前台进程组和 fallback 父子进程身份。[可移植执行环境决策](../architecture/2026-07-28-portable-execution-world-consumers.zh.md)负责定义这种进程/消费方拆分。 ### 6 个面向模型的工具 diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml index c67f4a0d57..179198d487 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml @@ -30,5 +30,5 @@ en: e254571a176b37e5 zh: 01f76aff35e3618d /agent-note-model-facing-same-session-goal-tools/known-limitations-and-deferred-work: - en: 5b043a930afd4a13 - zh: d91e8a61de2590a4 + en: 61e119f3d8dc130d + zh: 1d543c4143d4cc21 diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md index 2c05c8aa0e..d3dd654e02 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.md @@ -62,7 +62,7 @@ Unit coverage pins registration and disposal, exclusive scheduling, generated pr ## Known limitations and deferred work - Semantic classification of a substantial goal, a request to continue, objective completion, and the same blocking condition remains model judgment. An independent evaluator or completion certificate is deferred. -- The model cannot resume a durable paused goal; that user-owned path is enforced by the separate [user-owned goal pause decision](../bug-fix/2026-09-03-user-owned-goal-pause-activation.md). +- The model cannot resume a durable paused goal; that user-owned path is enforced by the separate [user-owned goal pause reference](../../../../packages/goal/tool-goal/README.md). - These tools mutate goal state but do not schedule goal rounds, classify abnormal driver stops, or cancel an active turn; the same-session driver owns those behaviors. - Goal-round authority is dormant unless a separately mounted continuation driver admits goal-sourced user turns; this tool package never manufactures that authority itself. - Human slash-command discovery and rendering are owned by the separate [`dsh-command-goal`](../../../../packages/goal/command-goal/README.md) plugin. diff --git a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md index f16055e52e..8c5c502f37 100644 --- a/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.zh.md @@ -62,7 +62,7 @@ Status: implemented ## 已知限制与暂缓事项 - 是否属于重大目标、是否要求继续、目标是否完成以及阻塞条件是否相同,仍由模型进行语义分类。独立评估器或完成证书予以延期。 -- 模型不能恢复持久 paused goal;该用户独占路径由独立的[用户独占 goal 暂停决策](../bug-fix/2026-09-03-user-owned-goal-pause-activation.zh.md)强制执行。 +- 模型不能恢复持久 paused goal;该用户独占路径由独立的[用户独占 goal 暂停参考](../../../../packages/goal/tool-goal/README.zh.md)强制执行。 - 这些工具会改变目标状态,但不调度 Goal Round、不分类异常驱动停止,也不取消活跃轮次;这些行为由同会话驱动器负责。 - 除非另行挂载的继续执行驱动器接纳了目标来源的用户轮次,否则 Goal Round 权限路径处于休眠状态;本工具包本身不会制造这种权限。 - 面向人类的斜杠命令发现与渲染由独立的 [`dsh-command-goal`](../../../../packages/goal/command-goal/README.zh.md) 插件负责。 diff --git a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml deleted file mode 100644 index 722d00ad1a..0000000000 --- a/.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-persisted-same-session-goal-domain.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md -/agent-note-persisted-same-session-goal-domain: - en: 05f6641270399579 - zh: 962259d5bd3b3e16 -/agent-note-persisted-same-session-goal-domain/problem: - en: 45e351264fabe761 - zh: 6513999a7d83a528 -/agent-note-persisted-same-session-goal-domain/decision: - en: 7efc52c6215aa755 - zh: e570a4f82e69300b -/agent-note-persisted-same-session-goal-domain/decision/durable-record-and-replay: - en: d4520ec456f4023a - zh: 2ffcb155955539e5 -/agent-note-persisted-same-session-goal-domain/decision/lifecycle-and-live-activation: - en: d68c058ff7a49fee - zh: cbcdbce1aade7758 -/agent-note-persisted-same-session-goal-domain/decision/service-boundary: - en: 0bff035a2664e7c0 - zh: ba303bd3ec64151b -/agent-note-persisted-same-session-goal-domain/testing: - en: 9fb97674d1dd45ac - zh: c56df41a9aa6767a -/agent-note-persisted-same-session-goal-domain/alternatives-considered: - en: b8bae793bb1c922d - zh: 4abb28a01b1fc7f3 -/agent-note-persisted-same-session-goal-domain/consequences: - en: fa46d172b9d01b52 - zh: 54c40631d1719624 -/agent-note-persisted-same-session-goal-domain/known-limitations-and-deferred-work: - en: 21f5c50b4eafa028 - zh: 4cad975fb10501bd diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml deleted file mode 100644 index a8a8205207..0000000000 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-plugin-command-registration.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md -/agent-note-plugin-owned-human-command-registration: - en: f1509f79bd9e2445 - zh: 30f62b9b4f611f9d -/agent-note-plugin-owned-human-command-registration/problem: - en: 628dcc0bda181677 - zh: 71b110588f89c2b4 -/agent-note-plugin-owned-human-command-registration/decision: - en: acf2742722ab7c85 - zh: e0bdbd6fd0bf51b1 -/agent-note-plugin-owned-human-command-registration/decision/registry-contract: - en: 1b3daffaa9a8b56e - zh: fa99bb6bc553cafb -/agent-note-plugin-owned-human-command-registration/decision/scope-and-lifecycle: - en: 6d55a0d4b1b9606a - zh: 34bf959ef332630f -/agent-note-plugin-owned-human-command-registration/decision/direct-dispatch-and-cancellation: - en: 074ee61a689493cb - zh: 580035e2a4c82d4f -/agent-note-plugin-owned-human-command-registration/decision/tui-mapping: - en: 00f0f8b697d9e1ff - zh: 9c4ceb0024e50835 -/agent-note-plugin-owned-human-command-registration/testing: - en: 0679c206796636a6 - zh: ca2cc5df6aa3aa2e -/agent-note-plugin-owned-human-command-registration/alternatives-considered: - en: ffb2f060314a17b2 - zh: e7707cf5faf80227 -/agent-note-plugin-owned-human-command-registration/consequences: - en: 6b8525a4ca3213a8 - zh: 286a336a68669f72 -/agent-note-plugin-owned-human-command-registration/known-limitations-and-deferred-work: - en: 5e2417d5b1237526 - zh: dbfeea7c7ac38cf0 diff --git a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml index 1d59d0072b..5f8169b4c4 100644 --- a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.i18n.yaml @@ -6,11 +6,11 @@ en: 4b7c45b382db3a40 zh: 96e9210446ec35ed /agent-note-typed-tool-returns-in-ptc-mode/problem: - en: cf7610bb16ff04af - zh: 73b00b539dbaecd9 + en: e2403bc358c4e14b + zh: dd37923097a3b24c /agent-note-typed-tool-returns-in-ptc-mode/decision: - en: 55bb889404c89f4a - zh: f9a9091569fbf7b4 + en: 2218fac13ef57c77 + zh: d4a18daa9debc77c /agent-note-typed-tool-returns-in-ptc-mode/decision/generated-sdk: en: 30583e9ac6715952 zh: 3a85c9482abf574c @@ -21,11 +21,11 @@ en: d907d0a8205157e1 zh: 1ed99168266a7ca4 /agent-note-typed-tool-returns-in-ptc-mode/decision/typed-handles-and-lifetime: - en: c5d9997ae7c7362d - zh: 1da8883d17d2748e + en: b2b9318c2812e3a4 + zh: 73cf34bfd487e2f1 /agent-note-typed-tool-returns-in-ptc-mode/decision/persistence-metadata-and-spill: - en: 4d1e5f83f15f99cc - zh: b7f41a084cb23745 + en: c388619f862a92ba + zh: d557eae40c7b6b7a /agent-note-typed-tool-returns-in-ptc-mode/testing: en: 728513c779f031f8 zh: f6d869f18add7242 @@ -33,8 +33,8 @@ en: dedd87b9b35d8fab zh: 78db4c7e8c4e5f61 /agent-note-typed-tool-returns-in-ptc-mode/consequences: - en: 7c2ee65532ab76f8 - zh: 3fb074c69909d390 + en: f5726b5e7a2fc73f + zh: 0b9aa884db5d920f /agent-note-typed-tool-returns-in-ptc-mode/known-limitations-and-deferred-work: en: a42af629ad6872cc zh: 41e5c0bb3de0ae4d diff --git a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md index 08e760b95a..032322407d 100644 --- a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.md @@ -10,13 +10,13 @@ PTC mode originally projected each nested tool result back from `ContentBlock[]` The runtime also treated binding values and the final program value as presentation data. Separate log and completion caps could replace an oversized or non-cloneable completion with inspected text even though intermediate values do not enter model context. That made programmatic composition lossy and confused the memory boundary with the prompt boundary. -The [canonical tool-output contract](../architecture/2026-07-20-canonical-tool-output-contract.md) establishes one validated execution-time value and a separate Native renderer. PTC mode should consume that value directly, preserve it across the worker boundary, and bound only the final output the program deliberately returns to the model. +The [canonical tool-output contract](../../../../packages/core/tools/README.md) establishes one validated execution-time value and a separate Native renderer. PTC mode should consume that value directly, preserve it across the worker boundary, and bound only the final output the program deliberately returns to the model. ## Decision PTC mode is a typed projection of the visible tool registry. Each successful binding resolves to the final canonical `JsonValue` after post-execute policy, while a failed binding rejects with a real `ToolCallError`. Intermediate values remain inside the run and cross the worker boundary whole. The outer `run_code` logs, completion value, or failure diagnostic enter the configurable output ledger and model-facing spill pipeline; a successfully settled sub-call whose final Native content contains an image additionally defers that complete ordered content through the parent result as logged, source-attributed context. -This note owns the return and failure contract layered on the original [PTC mode foundation](2026-06-15-ptc.md). The unified schema vocabulary is owned by the [JSON-value schema DSL note](../architecture/2026-07-20-unified-json-value-schema-dsl.md), and Native rendering and policy projection remain owned by the canonical-output note. +This note owns the return and failure contract layered on the original [PTC mode foundation](2026-06-15-ptc.md). The unified schema vocabulary is owned by the [JSON-value schema DSL reference](../../../../packages/core/tools/README.md), and Native rendering and policy projection remain owned by the canonical-output note. ### Generated SDK @@ -67,7 +67,7 @@ Compute time, wall time, worker heap, cancellation, and fresh-worker isolation r ### Typed handles and lifetime -Background producers return a typed canonical handle such as `{ kind: 'background', jobId }` while retaining their established Native sentence. A pre-aborted background call remains a failure because successful output promises an id and no task was created. After `ctx.jobs.start()` publishes the id, task-owned cancellation governs the work: settlement or later cancellation of the enclosing `run_code` call does not kill it. A later program can pass the returned id to `job_output`, and `job_kill`, owner disposal, or service teardown owns cancellation. Foreground execution remains coupled to the call signal. The task lifetime contract is owned by the [background job runtime note](../architecture/2026-06-20-generic-long-running-tool-runtime.md). +Background producers return a typed canonical handle such as `{ kind: 'background', jobId }` while retaining their established Native sentence. A pre-aborted background call remains a failure because successful output promises an id and no task was created. After `ctx.jobs.start()` publishes the id, task-owned cancellation governs the work: settlement or later cancellation of the enclosing `run_code` call does not kill it. A later program can pass the returned id to `job_output`, and `job_kill`, owner disposal, or service teardown owns cancellation. Foreground execution remains coupled to the call signal. The task lifetime contract is owned by the [background job runtime reference](../../../../packages/jobs/jobs/README.md). Temporary Cordis Plugins follow the same rule: `cordis_mount` returns `{ id, pluginName, state, provides, waitingFor }`, so a program can read `mounted.id`, inspect active or pending state, and pass that id to `cordis_unmount` without parsing the stable Native sentence. @@ -75,7 +75,7 @@ Temporary Cordis Plugins follow the same rule: `cordis_mount` returns `{ id, plu Nested dispatch logs the sub-call's full rendered `content`/`isError` on `tool/ptc-dispatch` but does not persist canonical values. `tool/result` continues to persist only rendered content, error, and optional metadata. A successful final content sequence containing an image is also wrapped in a source-attributed user message and deferred through the outer result; the normal session event makes that model-visible input reconstructable. The [PTC mode note](2026-06-15-ptc.md) owns durable event names and historical identity preservation; replay cannot recreate intermediate canonical program values. -The opaque `exec.parent` token marks nested calls. Presentation metadata and generic or tool-owned spill projections skip those calls; their canonical values never enter context. The Client can derive [nested terminal cards](../bug-fix/2026-09-05-nested-terminal-cards.md) from raw dispatch events without metadata. The outer `run_code` call produces the model-facing result and may spill its final post-policy presentation; `run_code` intentionally declares neither a result presenter nor presentation metadata, so UI adapters complete the card through their generic raw-content fallback using durable `tool/result.content`. +The opaque `exec.parent` token marks nested calls. Presentation metadata and generic or tool-owned spill projections skip those calls; their canonical values never enter context. The Client can derive [nested terminal cards](../../../../packages/client/ui-tool/README.md) from raw dispatch events without metadata. The outer `run_code` call produces the model-facing result and may spill its final post-policy presentation; `run_code` intentionally declares neither a result presenter nor presentation metadata, so UI adapters complete the card through their generic raw-content fallback using durable `tool/result.content`. ## Testing @@ -99,7 +99,7 @@ Keyless real-worker integration tests pin the two handle workflows that prose re ## Consequences -Code programs can compose tools through stable values instead of reverse-engineering Native prose. Native and Both Mode retain their existing text and UI presentation, while PTC mode receives output-schema types and exact runtime JSON. Tool authors must treat the canonical value as their programmatic API and put display-only formatting in the renderer. +Code programs can compose tools through stable values instead of reverse-engineering Native prose. Native mode retains its existing text and UI presentation, while PTC mode receives output-schema types and exact runtime JSON. Tool authors must treat the canonical value as their programmatic API and put display-only formatting in the renderer. The worker performs bounded-depth flat-wire transport and lossless validation but does not make intermediate values cheap or durable. Outer overflow is an explicit failed run, and error handling remains intentionally human-guided rather than a versioned code union. diff --git a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md index c2883740e1..6d8bf2d771 100644 --- a/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md @@ -10,13 +10,13 @@ PTC mode 过去会把每个嵌套工具的结果从 `ContentBlock[]` 重新投 运行时还把绑定值和程序最终返回值当作展示数据。日志和完成值分别设置上限,导致过大或无法克隆的完成值可能被替换为检查后生成的文本,而中间值本来就不会进入模型上下文。这种设计使程序化组合产生信息损失,也混淆了内存边界与提示词边界。 -[规范工具输出约定](../architecture/2026-07-20-canonical-tool-output-contract.zh.md)确立了单一、经过校验的执行期值,并将 Native 渲染器与之分离。PTC mode 应直接消费该值,在跨越 worker 边界时完整保留它,并且只限制程序有意返回给模型的最终输出。 +[规范工具输出约定](../../../../packages/core/tools/README.zh.md)确立了单一、经过校验的执行期值,并将 Native 渲染器与之分离。PTC mode 应直接消费该值,在跨越 worker 边界时完整保留它,并且只限制程序有意返回给模型的最终输出。 ## 决策 PTC mode 是可见工具注册表的类型化投影。每个成功的绑定调用都会解析为 post-execute 策略处理后的最终规范 `JsonValue`,失败的绑定调用则会以真正的 `ToolCallError` 拒绝 Promise。中间值只存在于本次运行中,并完整跨越 worker 边界。外层 `run_code` 的日志、完成值或失败诊断会进入可配置的输出账本以及面向模型的输出落盘流水线;如果成功结算的子调用最终 Native 内容包含图片,其完整有序内容还会经父结果延后为写入日志且带来源归属的上下文。 -本文档定义叠加在原始 [PTC mode 基础](2026-06-15-ptc.zh.md)之上的返回值与失败约定。统一 schema 词汇由 [JSON 值 schema DSL Agent Note](../architecture/2026-07-20-unified-json-value-schema-dsl.zh.md)负责定义;Native 渲染与策略投影仍由规范输出 Agent Note 负责定义。 +本文档定义叠加在原始 [PTC mode 基础](2026-06-15-ptc.zh.md)之上的返回值与失败约定。统一 schema 词汇由 [JSON 值 schema DSL reference](../../../../packages/core/tools/README.zh.md)负责定义;Native 渲染与策略投影仍由规范输出 Agent Note 负责定义。 ### 生成的 SDK @@ -67,7 +67,7 @@ PTC mode 通过运行时请求中的 `{ name: "ToolCallError", memberNamePropert ### 类型化句柄与生命周期 -后台生产方返回类型化的规范句柄,例如 `{ kind: 'background', jobId }`,同时保留既有的 Native 语句。已预先中止的后台调用仍是失败,因为成功输出承诺返回 id,而此时并未创建任务。`ctx.jobs.start()` 发布 id 后,工作由任务自有的取消机制控制:外围 `run_code` 调用完成,或随后被取消,都不会终止该任务。后续程序可以把返回的 id 传给 `job_output`;任务取消则由 `job_kill`、所有者的 dispose(资源释放)或服务 teardown 流程负责。前台执行仍与本次调用的信号耦合。任务生命周期约定由[后台任务运行时 Agent Note](../architecture/2026-06-20-generic-long-running-tool-runtime.zh.md)定义。 +后台生产方返回类型化的规范句柄,例如 `{ kind: 'background', jobId }`,同时保留既有的 Native 语句。已预先中止的后台调用仍是失败,因为成功输出承诺返回 id,而此时并未创建任务。`ctx.jobs.start()` 发布 id 后,工作由任务自有的取消机制控制:外围 `run_code` 调用完成,或随后被取消,都不会终止该任务。后续程序可以把返回的 id 传给 `job_output`;任务取消则由 `job_kill`、所有者的 dispose(资源释放)或服务 teardown 流程负责。前台执行仍与本次调用的信号耦合。任务生命周期约定由[后台任务运行时 reference](../../../../packages/jobs/jobs/README.zh.md)定义。 临时 Cordis 插件遵循同一规则:`cordis_mount` 返回 `{ id, pluginName, state, provides, waitingFor }`,因此程序可以直接读取 `mounted.id`,检查 active 或 pending 状态,并把该 id 传给 `cordis_unmount`,无需解析稳定的 Native 语句。 @@ -75,7 +75,7 @@ PTC mode 通过运行时请求中的 `{ name: "ToolCallError", memberNamePropert 嵌套分发在 `tool/ptc-dispatch` 上记录子调用完整渲染后的 `content`/`isError`,但不会持久化规范值。`tool/result` 继续只持久化渲染后的内容、错误和可选元数据。包含图片的成功最终内容序列还会包装成带来源归属的用户消息,并经外层结果延后;普通会话事件使该模型可见输入可以重建。[PTC mode note](2026-06-15-ptc.zh.md) 负责持久事件名称与历史标识保留规则;回放无法重建程序的规范中间值。 -不透明的 `exec.parent` token 用于标识嵌套调用。展示元数据以及通用或工具自有的 spill 投影都会跳过这些调用;它们的规范值永远不会进入上下文。Client 可以从原始分发事件派生[嵌套 terminal 卡片](../bug-fix/2026-09-05-nested-terminal-cards.zh.md),无需元数据。外层 `run_code` 调用产生面向模型的结果,并且可能对 post-policy 处理后的最终展示执行 spill;`run_code` 有意既不声明结果展示器,也不声明展示元数据,因此 UI 适配器会通过通用的原始内容回退机制,使用持久化的 `tool/result.content` 补全该卡片。 +不透明的 `exec.parent` token 用于标识嵌套调用。展示元数据以及通用或工具自有的 spill 投影都会跳过这些调用;它们的规范值永远不会进入上下文。Client 可以从原始分发事件派生[嵌套 terminal 卡片](../../../../packages/client/ui-tool/README.zh.md),无需元数据。外层 `run_code` 调用产生面向模型的结果,并且可能对 post-policy 处理后的最终展示执行 spill;`run_code` 有意既不声明结果展示器,也不声明展示元数据,因此 UI 适配器会通过通用的原始内容回退机制,使用持久化的 `tool/result.content` 补全该卡片。 ## 测试 @@ -99,7 +99,7 @@ PTC mode 通过运行时请求中的 `{ name: "ToolCallError", memberNamePropert ## 后果 -PTC mode 程序可以通过稳定值组合工具,无需逆向解析 Native 自然语言。Native 和 Both Mode 保留现有文本与 UI 展示,PTC mode 则获得输出 schema 类型和精确的运行时 JSON。工具作者必须把规范值视为程序化 API,并将仅用于展示的格式化放入渲染器。 +PTC mode 程序可以通过稳定值组合工具,无需逆向解析 Native 自然语言。Native mode 保留现有文本与 UI 展示,PTC mode 则获得输出 schema 类型和精确的运行时 JSON。工具作者必须把规范值视为程序化 API,并将仅用于展示的格式化放入渲染器。 worker 会以嵌套深度有界的扁平协议格式传输数据并执行无损校验,但不会降低中间值的开销,也不会使其具备持久性。外层输出溢出会显式导致运行失败,错误处理则有意由人类引导,而不是依赖带版本的错误代码联合。 diff --git a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.i18n.yaml deleted file mode 100644 index b85229745f..0000000000 --- a/.agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-21-follow-instruction-symlinks.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-21-follow-instruction-symlinks.md -/agent-note-follow-symlinked-instruction-files: - en: aa37067267aa70ff - zh: 75aff9256b49199f -/agent-note-follow-symlinked-instruction-files/problem: - en: 81e1947150ceb054 - zh: 97575f09a9e57db4 -/agent-note-follow-symlinked-instruction-files/decision: - en: 911b5bb48a95afd0 - zh: 1a38e50602b86bc4 -/agent-note-follow-symlinked-instruction-files/decision/trust-boundary-and-residual-risk: - en: 31ea638766861600 - zh: a270db3c868585b0 -/agent-note-follow-symlinked-instruction-files/alternatives-considered: - en: cf5e62b7cae4f0dc - zh: ff7d5821958efeac -/agent-note-follow-symlinked-instruction-files/consequences: - en: b0ee45a9e4d95dd5 - zh: 338d3758f400a651 diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml deleted file mode 100644 index a06d830891..0000000000 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-23-session-telemetry-otel-revival.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md -/agent-note-session-telemetry-seam-with-mandatory-redaction-and-the-otel-backend: - en: 36222884462b86d2 - zh: 2f834557e3fa3baa -/agent-note-session-telemetry-seam-with-mandatory-redaction-and-the-otel-backend/problem: - en: d3383b495424d118 - zh: 4e6ffc8ebfa7f741 -/agent-note-session-telemetry-seam-with-mandatory-redaction-and-the-otel-backend/decision: - en: 739b871ae583dcbf - zh: d17053e3b0a35942 -/agent-note-session-telemetry-seam-with-mandatory-redaction-and-the-otel-backend/alternatives-considered: - en: 01838eefca2bc117 - zh: fc71f5449d095b27 -/agent-note-session-telemetry-seam-with-mandatory-redaction-and-the-otel-backend/consequences: - en: 487d440abfc7e3a9 - zh: a31d6abc8a0176c6 diff --git a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.i18n.yaml b/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.i18n.yaml deleted file mode 100644 index f52f1c14fc..0000000000 --- a/.agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-25-subagent-policy-inheritance.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-25-subagent-policy-inheritance.md -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override: - en: 02b7ba888360c75c - zh: 42a6d20927622c9c -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override/problem: - en: 476b4a1b9ada2d8a - zh: 15b8051d22bb1dd0 -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override/decision: - en: 1f99951bc562cce4 - zh: d68ee315b7aea792 -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override/decision/what-a-blocked-child-experiences: - en: 3b6ff196a06c2420 - zh: c04cb8bd81eb1c4b -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override/alternatives-considered: - en: 2f659b5b3b6faa21 - zh: c3712d0b6f8903c9 -/agent-note-in-process-subagent-policy-inheritance-the-child-starts-under-the-parent-s-sandbox-override/consequences: - en: 1b0a242aa33a8005 - zh: 426f83e118771b8e diff --git a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml deleted file mode 100644 index 07e8978506..0000000000 --- a/.agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-26-ptc-live-parallel-dispatch.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-26-ptc-live-parallel-dispatch.md -/agent-note-ptc-mode-live-dispatch-lifecycle-and-native-contract-parallelism: - en: f546683c54ae7d6b - zh: 1c0760c5f49f0188 -/agent-note-ptc-mode-live-dispatch-lifecycle-and-native-contract-parallelism/problem: - en: ce16b44b1db07ec0 - zh: 30681dab84948cd8 -/agent-note-ptc-mode-live-dispatch-lifecycle-and-native-contract-parallelism/decision: - en: 7ac907f773ff9d50 - zh: 86982428baf83cde -/agent-note-ptc-mode-live-dispatch-lifecycle-and-native-contract-parallelism/alternatives-considered: - en: b015fa95b46e5fd5 - zh: b4fe283a1ab598c1 -/agent-note-ptc-mode-live-dispatch-lifecycle-and-native-contract-parallelism/consequences: - en: d16ba3a88fa7d528 - zh: 8c374a14a4f34cfe diff --git a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml deleted file mode 100644 index e811b85577..0000000000 --- a/.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-27-web-subagent-conversations.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md -/agent-note-web-subagent-catalog-and-human-continuation: - en: c6373db2300cb803 - zh: 4d0589f21ec284ff -/agent-note-web-subagent-catalog-and-human-continuation/problem: - en: dfdbe64d34f9e05b - zh: 4cb7a92a6d3dc3e3 -/agent-note-web-subagent-catalog-and-human-continuation/decision: - en: 8892eea7f62d45ed - zh: 18f64966ff53e38b -/agent-note-web-subagent-catalog-and-human-continuation/design-context: - en: da3858c86cf685ff - zh: b13ee4f45e3dff7d -/agent-note-web-subagent-catalog-and-human-continuation/product-contract: - en: 2060d607d1cd1ef3 - zh: e7100887358625c4 -/agent-note-web-subagent-catalog-and-human-continuation/host-adapter-and-wire-contract: - en: e0e82599b0a8d3bf - zh: e671e1f0f98795b4 -/agent-note-web-subagent-catalog-and-human-continuation/client-object-layer-and-presentation: - en: c0c891d86b1f7e1e - zh: c7b8da2bee81755d -/agent-note-web-subagent-catalog-and-human-continuation/default-web-assembly: - en: 6d4d8db99535aa18 - zh: 803554b55a67acb1 -/agent-note-web-subagent-catalog-and-human-continuation/alternatives-considered: - en: 427f0a8ace8ad692 - zh: 876771621858f77d -/agent-note-web-subagent-catalog-and-human-continuation/testing: - en: 0c2f7f5cc473ecff - zh: 00b2c22e109b3429 -/agent-note-web-subagent-catalog-and-human-continuation/consequences: - en: 143fc76c6d09e8fb - zh: 01a042fb32b0d6e0 diff --git a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml deleted file mode 100644 index ff719e7b97..0000000000 --- a/.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-27-workspace-registration-deletion.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md -/agent-note-workspace-registration-deletion: - en: 68262d0cd61db2ae - zh: bb2f78757972aae6 -/agent-note-workspace-registration-deletion/problem: - en: a51dbe0190d4dff0 - zh: 25b6c3083ee60627 -/agent-note-workspace-registration-deletion/decision: - en: 70182cd2979bdfd6 - zh: a6580a23cfbe6c07 -/agent-note-workspace-registration-deletion/durable-commit-and-publication: - en: f97d526274c3608b - zh: b0ae161f4e9febb8 -/agent-note-workspace-registration-deletion/client-convergence: - en: 84e56775e22ecf23 - zh: ab146e3179868efb -/agent-note-workspace-registration-deletion/confirmation-interaction: - en: 95178832030a9697 - zh: 3a2bd50680ed0c97 -/agent-note-workspace-registration-deletion/alternatives-considered: - en: d7b866f763db5364 - zh: 5e0865d42522d189 -/agent-note-workspace-registration-deletion/verification: - en: a7e98d39e3ffdba7 - zh: 9c4f8168e3fff83e -/agent-note-workspace-registration-deletion/consequences: - en: 3a50bfb5d93f6ef7 - zh: 80b8dd19b2d50b16 diff --git a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml index e3f1e8bb08..6566e1a57e 100644 --- a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.i18n.yaml @@ -3,29 +3,29 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md /agent-note-continuable-subagents: - en: 14c7d31032237174 - zh: ac2666670a0ca5ab + en: 110c0660aacfa3e4 + zh: cf405fad1c5834f7 /agent-note-continuable-subagents/problem: en: a72071eeedb77402 zh: 818d16ce429fd610 /agent-note-continuable-subagents/decision: - en: 445a8c9783aad214 - zh: 9b5c576bb76100a4 + en: 3420195435e02a8f + zh: 6156eedb3c23d360 /agent-note-continuable-subagents/decision/materialization-and-public-operations: - en: c7c55e5738a7c0d8 - zh: 54849d8938f7c77f + en: c3a83ff748a02143 + zh: 468bf62c8ddabf70 /agent-note-continuable-subagents/decision/durable-session-and-live-activation: - en: 92fd7302d4c8f2e5 - zh: e821764c07070ebc + en: 5c96c0343564c64a + zh: 85686e022d870cc7 /agent-note-continuable-subagents/decision/activation-lifecycle: - en: 68215c0ae5628608 - zh: fdf038decac8f07a + en: 5b84e6825247850c + zh: 18d43381aa3e8b05 /agent-note-continuable-subagents/decision/one-inbox-and-follow-up-delivery: - en: 065399ce5b1de59e - zh: b60af3c37851323e + en: 388934f4c4e50f65 + zh: 545a66157a4c46c4 /agent-note-continuable-subagents/decision/child-ownership: - en: 503fb4311cd94674 - zh: 376fa98ec4d22b20 + en: b6b62a4c083562b6 + zh: 86d02714e997942d /agent-note-continuable-subagents/decision/adjacent-agent-messaging: en: a6899f5a82d5dceb zh: 0671b89b2dca9c4e @@ -36,17 +36,17 @@ en: f5ba7e9c5339ceef zh: 31ae1b2f57e44463 /agent-note-continuable-subagents/decision/durability-disposal-and-recovery: - en: 276626f3b6d4a080 - zh: 806f9c4786bc4004 + en: 0bbc18843aa04ad3 + zh: d56fab1d12a28a96 /agent-note-continuable-subagents/decision/scope: - en: a47eed90de23351c - zh: 2351e4acbfe4c8e1 + en: bb3e8b2f8054e075 + zh: 15c126d51031fcb3 /agent-note-continuable-subagents/alternatives-considered: - en: 485aef32231dac62 - zh: 5e2c103afb3e2d6f + en: d786fd413fabe0f5 + zh: c5d66f5b880d9758 /agent-note-continuable-subagents/consequences: - en: fa5a6b4bf262b8dd - zh: 4bffd7b8cc2efae5 + en: d7ba8e86c3f14eaa + zh: b31ba49dee6aa85d /agent-note-continuable-subagents/consequences/accepted-costs: - en: 4d4330ee4d7c3f31 - zh: 8fd11ba0625fb76d + en: 95199dfb2adab107 + zh: e7644bcb70169368 diff --git a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md index a30966c4d6..e76aae22fe 100644 --- a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md +++ b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-28-continuable-subagent-conversations.zh.md) -This record replaces the Task-backed continuation manager from [Continuable background subagents](../../archived/feature/2026-07-21-continuable-background-subagents.md). It retains the single `ctx.subagents` service from [Merge subagent control into the subagent service](../../archived/simplification/2026-07-26-merge-subagent-control-service.md) and the intent-named `followup` operation from [Intent-named subagent continuation operations](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md). +This record replaces the Task-backed continuation manager from [Continuable background subagents](../../archived/feature/2026-07-21-continuable-background-subagents.md). It retains the single `ctx.subagents` service from [Merge subagent control into the subagent service](../../archived/simplification/2026-07-26-merge-subagent-control-service.md). ## Problem @@ -18,6 +18,8 @@ Parent Agents need to send later work to the same live child without changing it ## Decision +The shared activation entry point, external execution ownership, and caller-versus-parent result delivery are governed by [Unified subagent activations](../simplification/2026-09-17-unified-subagent-activations.md). This record retains the independent rationale described below. + A continuable subagent has one durable Session and at most one process-local Activation: ```text @@ -28,25 +30,25 @@ persisted Session -> zero or more owned child Activations ``` -An Activation is one residency epoch for a reconstructed child Agent. It may execute multiple FIFO turns and remain resident while waiting for descendants. It is not a request, result, cancellation, or Task boundary. +An Activation is one residency epoch for a reconstructed child Agent. It may execute multiple FIFO turns and remain resident while waiting for descendants. Its receipt owns the result and cancellation of that exact residency; it does not assign a separate result or Task to each accepted message. The continuation manager owns activation admission, authority checks, the live ownership graph, cold resume, and child-first disposal. The Agent loop owns all turn ordering and execution. No continuable subagent has a Task, an Activation FIFO, or queued Activation state. ### Materialization and public operations -The named subagent provider participates only in preparing the initial creation spec, where `spawn` and `fork` differ. Its optional `prepareContinuable(request): Promise` method is the continuable-creation capability. The returned spec contains only detached provider-specific creation inputs such as the optional parent-history seed; it contains no Agent, `AgentHandle`, prompt delivery, result, disposal, or resume operation. The manager reserves the child identity, resolves the durable descriptor and common Agent setup, calls `ctx.agents.create()` through a private activation-owner scope, installs the returned `AgentHandle` into the Activation, establishes any continuable-parent ownership, and then calls `Agent.followup(initialPrompt)`. Inbox acceptance yields a `MessageId`; at that boundary `ctx.subagents.startContinuable()` returns `{ childId, messageId }` without waiting for the turn to start or for the message to enter the Session log. +The named subagent provider participates only in preparing the initial creation spec, where `spawn` and `fork` differ. Its optional `prepareContinuable(request): Promise` method is the continuable-creation capability. The returned spec contains only detached provider-specific creation inputs such as the optional parent-history seed; it contains no Agent, `AgentHandle`, prompt delivery, result, disposal, or resume operation. The manager reserves the child identity, resolves the durable descriptor and common Agent setup, calls `ctx.agents.create()` through a private activation-owner scope, installs the returned `AgentHandle` into the Activation, establishes any continuable-parent ownership, and then calls `Agent.followup(initialPrompt)`. Inbox acceptance yields a `MessageId`; at that boundary `ctx.subagents.startActivation()` returns a receipt containing `childId`, `messageId`, `result`, and `dispose()` without waiting for the turn to start or for the message to enter the Session log. Any failure before inbox acceptance rejects without returning either id. Agent creation provides rollback before handle transfer; after transfer, the manager keeps one closing transaction visible to concurrent delivery and drain, disposes the created handle, removes the Activation, and rolls back any parent `ownedChildren` membership before rejecting. Failure before the residency start edge publishes no terminal edge, while failure after a published start closes the lifecycle pair through normal disposal. -`backgroundMode: 'one-shot' | 'continuable'` remains deployment policy. Configured continuable mode requires `prepareContinuable`; method presence replaces `SubagentProvider.resume?()` as the capability check, while a capable provider may still run one-shot work. +Provider method presence selects execution: `prepareContinuable()` creates a local activation; otherwise `start()` starts external execution. `delivery` chooses the result recipient without changing the local lifecycle. -Cold resume does not dispatch through a subagent provider. The continuation manager folds the generic in-process descriptor, calls `ctx.agents.resume()` through the same activation-owner scope, installs the returned `AgentHandle`, and submits the waiting `next-turn`. `SubagentProvider.resume?()` and `SubagentProviderResumeRequest` are absent. The descriptor retains the initial provider name after that provider unregisters; the name does not grant a recovery capability or require the provider for later residency. Remote providers require a separate design. +Cold resume does not dispatch through a subagent provider. The continuation manager folds the generic in-process descriptor, calls `ctx.agents.resume()` through the same activation-owner scope, installs the returned `AgentHandle`, and submits the waiting `next-turn`. `SubagentProvider.resume?()` and `SubagentProviderResumeRequest` are absent. The descriptor retains the initial provider name after that provider unregisters; the name does not grant a recovery capability or require the provider for later residency. External executions do not support cold resume. -`SubagentProvider.start()` and `SubagentRun` remain exclusively on the unchanged one-shot path. A continuable Activation directly owns its `AgentHandle` and never creates, wraps, or retains a `SubagentRun`; `SubagentRun.steer?()` is therefore absent. +`SubagentProvider.start()` and `SubagentRun` belong to external execution. A local Activation directly owns its `AgentHandle`; both kinds share manager-owned capacity, settlement, and disposal. -`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives a durable `agent-message` source from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Both start and send return the accepted `MessageId`, and neither reports how the manager materialized the Activation. +`ctx.subagents.sendMessage(sender, targetId, content, { signal })` is the sole model-authored continuation-message operation. The exact live sender authorizes delivery to its direct parent or direct continuable child; cold resume checks direct-child authority before reconstruction and every path checks again in the final no-await inbox-admission span, so an Agent unregistered or replaced during materialization cannot authorize delivery. The service derives a durable `agent-message` source from that sender. The model-facing `send_message` tool keeps only `agent_id` and `message` and uses fixed Steer scheduling. Local start returns an activation receipt containing the accepted `MessageId`; send returns the `MessageId` directly. Neither exposes whether materialization created or resumed the Agent. -For start and follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance. After the operation returns its `MessageId`, the manager owns the Activation independently; later caller cancellation does not cancel the accepted turn or dispose the child. +For start and follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance. After inbox acceptance, the manager owns the Activation independently; later caller cancellation does not cancel the accepted turn or dispose the child. ### Durable Session and live Activation @@ -54,7 +56,7 @@ The Session owns the stable child identity, transcript, direct-parent lineage, d An idle historical Session has no `AgentHandle`. The first authorized `next-turn` delivery resumes an Activation from the persisted Session and submits the message to its inbox. Cold resume uses the exact live parent Agent for authorization and, when that parent has an Activation, ownership; it never uses the parent for reconstruction. -The Activation directly owns the published `AgentHandle` until it settles, while the manager's private activation-owner scope is its structural Cordis owner. The continuable path creates no intermediate result-bearing execution wrapper, including `SubagentRun`; one-shot delegation remains unchanged and outside this lifecycle. Remote providers are out of scope here and require a separate Activation ownership contract when introduced. Historical Sessions consume no runtime memory after their Activation is disposed. +A local Activation directly owns the published `AgentHandle` until settlement; the private activation-owner scope is its structural Cordis owner. It creates no intermediate `SubagentRun`. The unified manager also owns external executions through their provider handles. Historical Sessions retain no Agent resources after disposal. ### Activation lifecycle @@ -81,21 +83,21 @@ no Activation The manager derives these states from Agent quiescence, the Inbox's pending state, and the owned-child set rather than maintaining a second execution state machine. A `next-turn` delivered while `running` joins the Agent inbox. A waking delivery while `waiting` wakes the same Agent and returns the Activation to `running`. Delivery after disposal cold-resumes a new Activation. -The manager linearizes manager-owned delivery, child release, and disposal for each durable child. A private `SubagentInbox` delegates Queue and Steer to the Agent inbox and owns the Activation's existing close transaction. If manager delivery races with final disposal, exactly one side wins this admission cutoff: delivery either enters the still-live Agent inbox, or observes closing and follows its operation-specific rejection or cold-resume path. Direct Agent work does not use this wrapper, so natural settlement uses short maintenance claims to validate the idle phase before the final flush and final disposal decision, then revalidates the Session sequence, Inbox pending state, wake generation, and owned-child set under the child lock. Accepted work that remains active or changes Session, Inbox, or ownership state invalidates that settlement attempt instead of being cancelled by it; maintenance that starts and finishes entirely during the flush has completed before the cutoff. +The manager linearizes manager-owned delivery, child release, and disposal for each durable child. Private manager delivery delegates Queue and Steer to the Agent inbox and checks the Activation closing promise. If manager delivery races with final disposal, exactly one side wins this admission cutoff: delivery either enters the still-live Agent inbox, or observes closing and follows its operation-specific rejection or cold-resume path. Direct Agent work does not use this wrapper, so natural settlement uses short maintenance claims to validate the idle phase before the final flush and final disposal decision, then revalidates the Session sequence, Inbox pending state, wake generation, and owned-child set under the child lock. Accepted work that remains active or changes Session, Inbox, or ownership state invalidates that settlement attempt instead of being cancelled by it; maintenance that starts and finishes entirely during the flush has completed before the cutoff. ### One inbox and follow-up delivery -The Agent inbox is the only queue. Every continuation message uses `Agent.followup()` and becomes one FIFO turn; neither the continuation manager nor the host maintains another message queue. Every pending Inbox occurrence keeps the current Activation live until it is claimed or discarded. This conservative rule also retains injected context: a quiet injection that remains after quiescence can keep the Activation and its live ancestors resident until a waking delivery claims it, a queue mutation removes it, or manager teardown disposes the tree. +The Agent inbox is the only queue. Agent messages use Steer; human prompts choose Queue or Steer. Every pending Inbox occurrence keeps the current Activation live until claimed or discarded, including quiet injected context. Neither the manager nor the host maintains a second message queue. Routing depends only on Activation residency: -| Activation state | `followup` | +| Activation state | Waking message delivery | |---|---| | `running` | enqueue in the same Activation | | `waiting` | wake the same Activation | | no Activation | cold-resume a new Activation | -The continuation layer defines no separate delivery-route result. Successful `ctx.subagents.followup()` and `send_message` delivery returns the accepted `MessageId`, while delivery failure throws. Existing `agent/inbox/enqueue`, `agent/inbox/dequeue`, and `agent/inbox/discard` events remain the message-lifecycle observations; adapters may render a generic acceptance but do not expose `started`, `queued`, `resumed`, or another subagent-specific route vocabulary. +Successful `sendMessage()` returns the accepted `MessageId`; failed delivery throws. Inbox events remain the message-lifecycle observations. The service exposes no separate started/queued/resumed routing result. ### Child ownership @@ -107,9 +109,9 @@ Child release occurs only after the child Agent is quiescent, its Inbox is empty Ownership is retained until the child Activation is disposed. A later refinement may release a request-scoped lease earlier, but it would require an exact turn-completion correlation that this Task-free design deliberately does not add. -Top-level teardown is host-owned rather than represented as another Activation. Manager unload invokes its internal manager-wide drain to close admission synchronously, await every admitted materialization through publication or rollback, stop the stable live forest, and release it child-first. A host that owns selected top-level Agents uses `drainContinuableDescendants(parents)`: exact Agent identities close admission only below those roots until each leaves the registry, while unrelated forests and manager-wide admission remain live; the manager stops their visible descendants before its first await, waits only materializations admitted below those roots, and releases only the selected branches. Every materialized start and live delivery rechecks caller cancellation, the applicable draining scope, Activation disposal, and exact parent authority in the same synchronous span as inbox submission, so teardown or parent replacement that wins before acceptance prevents delivery to the closing handle. Only after the applicable drain settles may the host dispose its top-level Agents; only manager-wide drain precedes manager-scope disposal. +Top-level teardown is host-owned rather than represented as another Activation. Manager unload invokes its internal manager-wide drain to close admission synchronously, await every admitted materialization through publication or rollback, stop the stable live forest, and release it child-first. A host that owns selected top-level Agents uses `drainDescendants(parents)`: exact Agent identities close admission only below those roots until each leaves the registry, while unrelated forests and manager-wide admission remain live; the manager stops their visible descendants before its first await, waits only materializations admitted below those roots, and releases only the selected branches. Every materialized start and live delivery rechecks caller cancellation, the applicable draining scope, Activation disposal, and exact parent authority in the same synchronous span as inbox submission, so teardown or parent replacement that wins before acceptance prevents delivery to the closing handle. Only after the applicable drain settles may the host dispose its top-level Agents; only manager-wide drain precedes manager-scope disposal. -The activation-owner scope exists because ordinary Cordis owner effects unwind in reverse registration order, which cannot express the dynamic child graph. Manager initialization registers the private scope's structural disposer first and its drain disposer afterward, so reverse unwind invokes the drain before releasing that scope; merely registering a cleanup effect on the same scope as later Agent handles would allow structural handle disposal to bypass child-first ordering. Each materialization registers its barrier participant and snapshots its exact live ancestry before starting the inner transaction, then remains tracked until it installs an Activation or fully rolls back. The Activation retains weak membership of that ancestry, so an intermediate Agent may leave the registry without hiding a still-live descendant from its host root. Its private `SubagentInbox` installs one memoized closing promise before cancellation or recursive callbacks, allowing scoped host shutdown, global manager unload, child release, and normal settlement to converge without double release. Cancellation propagates top-down before slow descendant cleanup; handle release remains child-first. Sibling branches drain independently; one disposal failure is recorded but does not prevent the manager from attempting the remaining selected handles, and the aggregate drain reports failure after all selected branches settle. Durable child Sessions survive this process-local teardown. +The activation-owner scope exists because ordinary Cordis owner effects unwind in reverse registration order, which cannot express the dynamic child graph. Manager initialization registers the private scope's structural disposer first and its drain disposer afterward, so reverse unwind invokes the drain before releasing that scope; merely registering a cleanup effect on the same scope as later Agent handles would allow structural handle disposal to bypass child-first ordering. Each materialization registers its barrier participant and snapshots its exact live ancestry before starting the inner transaction, then remains tracked until it installs an Activation or fully rolls back. The Activation retains weak membership of that ancestry, so an intermediate Agent may leave the registry without hiding a still-live descendant from its host root. The manager installs one memoized closing promise before cancellation or recursive callbacks, allowing scoped host shutdown, global manager unload, child release, and normal settlement to converge without double release. Cancellation propagates top-down before slow descendant cleanup; handle release remains child-first. Sibling branches drain independently; one disposal failure is recorded but does not prevent the manager from attempting the remaining selected handles, and the aggregate drain reports failure after all selected branches settle. Durable child Sessions survive this process-local teardown. ### Adjacent-Agent messaging @@ -129,7 +131,7 @@ Parent-originated delivery requires the parent to be live when admitted and keep ### Durability, disposal, and recovery -Without Jobs there is no `job_output`, `job_kill`, Task status, or per-message result promise. The caller signal can abort start or follow-up only before inbox acceptance. After acceptance, the parent cannot cancel the accepted message or dispose the Activation through `ctx.subagents`; the only public stop is the later [current-turn interrupt](2026-08-06-continuable-subagent-interrupt.md), which cancels the live target's current turn with `keepInbox` and leaves residency, pending work, and descendants intact. +An activation receipt exposes `result` and `dispose()` without Jobs. The caller signal owns unpublished work; receipt disposal cancels the exact activation and awaits descendant startup rollback and child-first release. The [current-turn interrupt](../../../../packages/subagent/subagent/README.md) cancels only the live target turn with `keepInbox`, preserving residency, pending work, and descendants. Host and manager teardown remains the lifecycle stop path. Manager unload applies it globally; a host applies it only below the exact top-level Agents it owns. Each form closes the applicable admission scope, stops the selected visible Activations, awaits admitted materializations in that scope, releases child-first, and preserves the durable Sessions. @@ -141,9 +143,9 @@ Session and descriptor persistence survive restart. Activation state, Agent inbo ### Scope -This version covers continuable in-process children and leaves one-shot delegation unchanged. Remote providers require a separate Activation handle with equivalent authenticated control and child-first quiescence contracts before they can support the same behavior. +Local continuable control and external execution share activation ownership. Only local children support later messages and cold resume. -It adds no host-user continuation, subagent steering operation, durable mailbox, cross-process lease, automatic replay of interrupted inbox work, team authority, workflow authority, public residency query, or runtime cache; the later [current-turn interrupt](2026-08-06-continuable-subagent-interrupt.md) added the one public stop operation on top of this lifecycle. Existing delegation-depth policy remains unchanged. Optional child-to-parent reporting is a later consumer of this lifecycle rather than part of the base continuable capability. +Human Queue/Steer delivery and [current-turn interruption](../../../../packages/subagent/subagent/README.md) use this lifecycle; receipt disposal stops its exact activation and owned descendants. Durable mailboxes, cross-process leases, automatic replay of interrupted inbox work, team authority, workflow authority, public residency queries, and runtime caches remain outside this capability. Delegation-depth policy and adjacent-Agent message authorization retain their own owners. ## Alternatives considered @@ -155,7 +157,7 @@ It adds no host-user continuation, subagent steering operation, durable mailbox, **Let the provider create, resume, or deliver through an Agent handle.** Initial providers own only `prepareContinuable()` and its detached creation-spec distinction: whether a child begins fresh or with a parent prefix. The manager must call `ctx.agents.create()` through its private activation-owner scope so that scope is a structural owner of every handle. A persisted in-process Session already contains the initial prefix and generic reconstruction descriptor, while delivery belongs to the Agent inbox. Giving providers any later handle, `SubagentRun`, or message ownership would retain provider ownership with no shipped behavior to justify it. -**Make report delivery part of the base lifecycle.** Repeatable child-to-parent reporting is compatible with this lifecycle, but quiet versus next-step delivery, acknowledgement, durability, and retry behavior are independent product choices. The later report package remains optional and consumes an explicit child-setup hook, so continuable residency does not silently grant a return channel. +**Make child-authored reporting part of residency.** Adjacent-Agent messaging is separately composed through `send_message`; its authority and scheduling are independent of residency. Parent-result delivery belongs to the manager and includes a runtime settlement notice even when the child sends no message. **Treat `SessionHeader.parentSession` as live ownership.** Durable lineage does not prove that the recorded parent currently owns the child. Membership in the live parent's `ownedChildren` records the process-local relationship without changing the durable parent id. @@ -176,39 +178,39 @@ It adds no host-user continuation, subagent steering operation, durable mailbox, The implementation pins these behaviors: - A continuable child has at most one live Activation and one Agent inbox; the continuation manager has no Activation FIFO or queued Activation state. -- `SubagentProvider.prepareContinuable?()` returns only a detached `ContinuableCreateSpec`; configured continuable mode requires that capability, while `backgroundMode` remains an independent policy choice. -- The manager calls `ctx.agents.create()` through its private activation-owner scope, installs the returned `AgentHandle` and parent ownership, calls `Agent.followup(initialPrompt)`, and returns `{ childId, messageId }` when inbox acceptance yields the `MessageId`, without waiting for turn start or a Session-log write. +- `SubagentProvider.prepareContinuable?()` returns detached local creation inputs; `start()` establishes external execution. +- The manager calls `ctx.agents.create()` through its private activation-owner scope, installs the returned `AgentHandle` and parent ownership, calls `Agent.followup(initialPrompt)`, and returns a receipt containing `childId`, `messageId`, `result`, and `dispose()` when inbox acceptance yields the `MessageId`, without waiting for turn start or a Session-log write. - Every failure before initial-prompt inbox acceptance rejects without ids and rolls back any created handle, Activation, and parent `ownedChildren` membership through a closing transaction visible to concurrent delivery and drain; lifecycle publication failure emits no unmatched terminal edge. - Cold resume calls `ctx.agents.resume()` from the continuation manager and never dispatches through or requires the initial subagent provider; the descriptor retains the initial provider name after provider removal, while `SubagentProvider.resume?()` and `SubagentProviderResumeRequest` are absent. -- A continuable Activation directly owns `AgentHandle` and never creates, wraps, or retains `SubagentRun`; `SubagentProvider.start()` and `SubagentRun` remain one-shot-only, without `SubagentRun.steer?()`. -- `followup()` accepts only the exact live direct parent and rechecks that identity at the final no-await inbox-admission boundary after any materialization; durable message source fields cannot authorize delivery. -- Continuation messages always use `Agent.followup()` and share its inbox FIFO, including when the child already has an open turn. -- `ctx.subagents.followup()` and its `send_message` adapter return only the accepted `MessageId`; the continuation layer accepts no delivery target and defines no subagent-specific route result. -- Caller signals stop start and follow-up only before inbox acceptance, while host-scoped and manager-global teardown retain child-first cleanup; the [current-turn interrupt](2026-08-06-continuable-subagent-interrupt.md) is the one public stop and does not enter teardown. -- This version exposes no subagent steering operation or current-turn controller state. +- A local Activation directly owns `AgentHandle`; an external Activation owns `SubagentRun`. +- `sendMessage()` accepts only an exact live adjacent Agent and rechecks authority at final inbox admission after materialization; recorded source fields cannot authorize delivery. +- Agent messages use Steer; human Queue prompts retain FIFO turn ordering. +- `ctx.subagents.sendMessage()` returns the accepted `MessageId` without exposing materialization routing. +- Caller signals stop unpublished work; activation receipts and host drains own child-first teardown. The [current-turn interrupt](../../../../packages/subagent/subagent/README.md) preserves pending work and residency. +- Human prompt delivery and current-turn interruption retain their separate authorization checks. - An idle Agent with live owned children yields a `waiting` Activation whose `AgentHandle` remains retained. - A `next-turn` delivered to `waiting` wakes the same Activation; delivery after completed disposal cold-resumes a new Activation. - Every continuation-managed parent Activation disposes only after all directly owned child Activations complete `AgentHandle` disposal; top-level Agents do not join the waiting graph. - Final Activation settlement awaits `ctx.sessions.flush(child.session)` with admission open, logs rejection without interpreting listener participation as durability proof, revalidates the final state under the child lock, then closes admission, disposes the child handle, and releases parent ownership so a flush failure cannot leak a `waiting` Activation. - Manager teardown closes admission globally; a host owning selected top-level Agents instead closes admission only below their exact identities until those roots leave the registry. Both track admitted materializations by exact ancestry, install one memoized disposal cutoff per selected visible Activation, propagate cancellation top-down, release handles child-first, await every selected branch despite individual failures, and only then dispose the corresponding top-level Agents or manager scope. -- The base lifecycle has no implicit report behavior; the optional report package contributes an explicit child-scoped tool through the setup hook. +- Parent delivery emits runtime settlement notices independently of child-authored messages. - Session logs reconstruct only messages that were actually written, with the source that supplied each message; inbox-accepted but unlogged messages have no restart guarantee. - No continuable-subagent path creates or depends on a Task, `JobId`, Task completion notice, Task cancellation, or intermediate result-bearing execution wrapper. -- Unit coverage pins the `startContinuable()` inbox-acceptance return boundary, complete rollback for each pre-acceptance and lifecycle-publication failure, global and parent-scoped drain quiescence for materialization caught between Agent publication and Activation registration, sibling-forest isolation, exact ancestry after an intermediate Agent leaves the registry, provider-independent cold resume, final exact-parent reauthorization after cold-resume materialization, caller-signal and teardown ownership on both sides of acceptance, and the absence of automatic replay for accepted-but-unlogged messages. -- Unit coverage pins the residency-only routing table, single-inbox ordering, `MessageId` correlation through inbox events, follow-up during an open turn, waiting wakeup, cold resume, ownership registration and release, child-first disposal, send-versus-dispose races, direct Agent turns, Session-only work, and maintenance accepted during the final-flush await, best-effort final flush with absent and failing listeners, and the absence of public subagent cancellation and steering. -- Report-package unit coverage separately pins child-only visibility, setup revocation, authority, delivery modes, stable message identity, and lifecycle races. -- A keyless assembled-app snapshot covers parent delegation and follow-up queueing, the absence of subagent steering and implicit report delivery, retained waiting `AgentHandle`, and child-first disposal. A separate report snapshot covers the optional explicit return channel. +- Unit coverage pins the `startActivation()` inbox-acceptance return boundary, complete rollback for each pre-acceptance and lifecycle-publication failure, global and parent-scoped drain quiescence for materialization caught between Agent publication and Activation registration, sibling-forest isolation, exact ancestry after an intermediate Agent leaves the registry, provider-independent cold resume, final exact-parent reauthorization after cold-resume materialization, caller-signal and teardown ownership on both sides of acceptance, and the absence of automatic replay for accepted-but-unlogged messages. +- Unit coverage pins the residency-only routing table, single-inbox ordering, `MessageId` correlation through inbox events, follow-up during an open turn, waiting wakeup, cold resume, ownership registration and release, child-first disposal, send-versus-dispose races, direct Agent turns, Session-only work, and maintenance accepted during the final-flush await, best-effort final flush with absent and failing listeners, and separately authorized human prompt delivery and interruption. +- Adjacent-Agent tests cover direct-parent authority, tool visibility, cold resume, and delivery during settlement. +- Keyless assembled-app snapshots cover delegation, adjacent-Agent messaging, runtime settlement notices, and child-first disposal. ### Accepted costs Removing Jobs gives up generic background-work inspection, result collection, and exact Task cancellation. If those product features become requirements, they need a request ticket or inbox capability that does not reintroduce a second execution queue. -Retaining an Activation while descendants run consumes Agent resources proportional to the unfinished ownership graph. The existing delegation-depth policy still bounds nesting, and [shared Activation capacity](2026-09-15-continuable-activation-capacity.md) bounds live continuable descendants; settled historical Sessions retain no `AgentHandle`. +Retaining an Activation while descendants run consumes Agent resources proportional to the unfinished ownership graph. The existing delegation-depth policy still bounds nesting, and [shared Activation capacity](../../../../packages/subagent/subagent/README.md) bounds live continuable descendants; settled historical Sessions retain no `AgentHandle`. The process-local inbox and ownership graph do not coordinate two harness processes. Deployments allowing concurrent access to one persistence store still require a durable lease and mailbox protocol. -Without the optional report package, completing a child turn neither sends its content to nor wakes the historical parent. With the package, only an explicit `report` call sends selected content; quiet delivery does not wake the parent, while next-step delivery wakes it and joins its nearest step boundary. In every case the detailed child output remains in its durable Session. +Child-authored `send_message` calls and manager-authored settlement notices remain distinct in the parent log. The detailed local child transcript remains in its own durable Session. -Queueing every continuation message means a parent cannot correct an in-progress child turn immediately; the correction runs as the next turn. A later UI steering action may reduce that latency without changing follow-up ordering. +Steer delivery reaches the next available step boundary; it does not preempt an executing tool. Queue delivery retains later-turn ordering. A failed best-effort final flush is logged while the runtime ownership graph continues draining; the persisted child state may be missing or stale. Retry and repair require a separate recovery design. diff --git a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md index 59a02a211e..ce543b4efa 100644 --- a/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md +++ b/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-28-continuable-subagent-conversations.md) | 中文 -本记录取代[可继续的后台 subagent](../../archived/feature/2026-07-21-continuable-background-subagents.md)中由 Task 支撑的继续执行管理器。它保留[将 subagent 控制合并到 subagent 服务](../../archived/simplification/2026-07-26-merge-subagent-control-service.md)确立的单一 `ctx.subagents` 服务,以及[以意图命名的 subagent 继续执行操作](../../archived/simplification/2026-07-27-intent-named-subagent-continuation-operations.md)确立的 `followup` 操作。 +本记录取代[可继续的后台 subagent](../../archived/feature/2026-07-21-continuable-background-subagents.md)中由 Task 支撑的继续执行管理器。它保留[将 subagent 控制合并到 subagent 服务](../../archived/simplification/2026-07-26-merge-subagent-control-service.md)确立的单一 `ctx.subagents` 服务。 ## 问题 @@ -18,6 +18,8 @@ parent Agent 还需要在不改变当前轮次的前提下,向同一个在线 ## 决策 +共享 activation 入口、外部执行所有权,以及调用方与父级之间的结果投递选择由[统一 subagent activation](../simplification/2026-09-17-unified-subagent-activations.zh.md) 决策拥有。本记录保留下述独立理由。 + 一个可继续 subagent 拥有一个持久化会话,并且至多拥有一个进程内激活: ```text @@ -28,25 +30,25 @@ persisted Session -> zero or more owned child Activations ``` -激活是重建 child Agent 的一次驻留周期。它可以执行多个 FIFO 轮次,并在等待后代时保持驻留。它不是请求、结果、取消或 Task 边界。 +激活是重建 child Agent 的一次驻留周期。它可以执行多个 FIFO 轮次,并在等待后代时保持驻留。其回执持有这次确切驻留的结果与取消操作;它不会为每条已接受的消息分配独立结果或 Task。 继续执行管理器负责激活准入、权限检查、在线所有权图、冷恢复和 child-first dispose。Agent loop 负责全部轮次排序与执行。没有任何可继续 subagent 拥有 Task、激活 FIFO 或 queued 激活状态。 ### 物化与公开操作 -具名 subagent 提供方只参与准备初始创建规格,此时 `spawn` 与 `fork` 有所区别。其可选的 `prepareContinuable(request): Promise` 方法就是可继续创建能力。返回的规格只包含与 Agent 实例分离且由提供方决定的创建输入,例如可选的 parent 历史种子;它不包含 Agent、`AgentHandle`、提示词投递、结果、dispose 或恢复操作。管理器会预留 child 身份,解析持久化描述符和通用 Agent 配置,通过私有 activation-owner 作用域调用 `ctx.agents.create()`,将返回的 `AgentHandle` 安装到激活中,建立适用的可继续 parent 所有权,然后调用 `Agent.followup(initialPrompt)`。inbox 接受消息后会产生一个 `MessageId`;`ctx.subagents.startContinuable()` 在此边界返回 `{ childId, messageId }`,不等待轮次开始,也不等待消息写入会话日志。 +具名 subagent 提供方只参与准备初始创建规格,此时 `spawn` 与 `fork` 有所区别。其可选的 `prepareContinuable(request): Promise` 方法就是可继续创建能力。返回的规格只包含与 Agent 实例分离且由提供方决定的创建输入,例如可选的 parent 历史种子;它不包含 Agent、`AgentHandle`、提示词投递、结果、dispose 或恢复操作。管理器会预留 child 身份,解析持久化描述符和通用 Agent 配置,通过私有 activation-owner 作用域调用 `ctx.agents.create()`,将返回的 `AgentHandle` 安装到激活中,建立适用的可继续 parent 所有权,然后调用 `Agent.followup(initialPrompt)`。inbox 接受消息后会产生一个 `MessageId`;`ctx.subagents.startActivation()` 在此边界返回包含 `childId`、`messageId`、`result` 与 `dispose()` 的回执,不等待轮次开始,也不等待消息写入会话日志。 inbox 接受消息前发生任何失败,操作都会在不返回任何 id 的情况下被拒绝。Agent 创建流程负责 handle 移交前的回滚;移交后,管理器会保留一个对并发投递和 drain 可见的关闭事务,dispose 已创建的 handle、移除激活并回滚 parent `ownedChildren` 中的任何成员关系,再拒绝操作。在驻留 start 事件发布前失败不会发布终止事件,start 发布后失败则通过正常 dispose 闭合生命周期配对。 -`backgroundMode: 'one-shot' | 'continuable'` 仍是部署策略。配置为 continuable 时要求存在 `prepareContinuable`;该方法是否存在会取代 `SubagentProvider.resume?()` 成为能力检查,而具备该能力的提供方仍可运行 one-shot 工作。 +提供方方法是否存在决定执行方式:`prepareContinuable()` 创建本地 activation,否则由 `start()` 启动外部执行。`delivery` 选择结果接收方,不改变本地生命周期。 -冷恢复不会通过 subagent 提供方分发。继续执行管理器会归并通用的进程内描述符,通过同一个 activation-owner 作用域调用 `ctx.agents.resume()`,安装返回的 `AgentHandle`,并提交等待中的 `next-turn`。`SubagentProvider.resume?()` 和 `SubagentProviderResumeRequest` 均不存在。初始提供方注销后,描述符仍保留其名称;该名称不赋予恢复能力,也不要求后续驻留时该提供方存在。远程提供方需要单独设计。 +冷恢复不会通过 subagent 提供方分发。继续执行管理器会归并通用的进程内描述符,通过同一个 activation-owner 作用域调用 `ctx.agents.resume()`,安装返回的 `AgentHandle`,并提交等待中的 `next-turn`。`SubagentProvider.resume?()` 和 `SubagentProviderResumeRequest` 均不存在。初始提供方注销后,描述符仍保留其名称;该名称不赋予恢复能力,也不要求后续驻留时该提供方存在。外部执行不支持冷恢复。 -`SubagentProvider.start()` 和 `SubagentRun` 只保留在不变的 one-shot 路径上。可继续激活直接持有自身的 `AgentHandle`,绝不创建、包装或保留 `SubagentRun`;因此,`SubagentRun.steer?()` 不存在。 +`SubagentProvider.start()` 与 `SubagentRun` 属于外部执行。本地 Activation 直接持有其 `AgentHandle`;两类执行共享管理器持有的容量、结算与 dispose。 -`ctx.subagents.sendMessage(sender, targetId, content, { signal })` 是唯一由模型编写的继续执行消息操作。确切在线 sender 授权向其直接 parent 或直接可继续 child 投递;冷恢复会在重建前检查直接 child 权限,每条路径还会在最终无 await 的 inbox 准入区间再次检查,因此在物化期间被注销或替换的 Agent 无法授权投递。服务从该 sender 推导持久化 `agent-message` 来源信息。面向模型的 `send_message` 工具只保留 `agent_id` 和 `message`,并使用固定 Steer 调度。start 与 send 都返回已接受的 `MessageId`,两者都不报告管理器如何物化 Activation。 +`ctx.subagents.sendMessage(sender, targetId, content, { signal })` 是唯一由模型编写的继续执行消息操作。确切在线 sender 授权向其直接 parent 或直接可继续 child 投递;冷恢复会在重建前检查直接 child 权限,每条路径还会在最终无 await 的 inbox 准入区间再次检查,因此在物化期间被注销或替换的 Agent 无法授权投递。服务从该 sender 推导持久化 `agent-message` 来源信息。面向模型的 `send_message` 工具只保留 `agent_id` 和 `message`,并使用固定 Steer 调度。本地 start 返回包含已接受 `MessageId` 的 activation 回执;send 直接返回 `MessageId`。两者都不暴露物化过程是创建还是恢复 Agent。 -对于 start 和 follow-up,调用方 signal 只在 inbox 接受消息前持有查找、物化和准入。操作返回 `MessageId` 后,管理器会独立持有该激活;调用方之后的取消不会取消已接受的轮次,也不会 dispose child。 +对于 start 和 follow-up,调用方 signal 只在 inbox 接受消息前持有查找、物化和准入。inbox 接受消息后,管理器会独立持有该激活;调用方之后的取消不会取消已接受的轮次,也不会 dispose child。 ### 持久化会话与在线激活 @@ -54,7 +56,7 @@ inbox 接受消息前发生任何失败,操作都会在不返回任何 id 的 空闲的历史会话没有 `AgentHandle`。第一条通过鉴权的 `next-turn` 投递会根据持久化会话恢复激活,并将消息提交到其 inbox。冷恢复使用经过身份认证的确切在线 parent Agent 执行鉴权;当该 parent 有激活时,还使用它建立所有权,但绝不使用 parent 执行重建。 -激活会直接持有已发布的 `AgentHandle` 直至结算,而管理器的私有 activation-owner 作用域则是其 Cordis 结构化所有者。可继续 subagent 路径不创建任何中间的带结果执行包装层,包括 `SubagentRun`;一次性委派保持不变,且不属于该生命周期。远程提供方不在此处的范围内,引入时需要单独的激活所有权约定。激活 dispose 后,历史会话不消耗运行时内存。 +本地 Activation 直接持有已发布的 `AgentHandle` 直至结算;私有 activation-owner 作用域是其 Cordis 结构化所有者。它不创建中间 `SubagentRun`。统一管理器也通过提供方 handle 持有外部执行。dispose 后历史 Session 不保留 Agent 资源。 ### 激活生命周期 @@ -81,21 +83,21 @@ no Activation 管理器根据 Agent 是否完全停稳、Inbox 的待处理状态以及所持 child 集合派生这些状态,而不是维护第二套执行状态机。在 `running` 时投递的 `next-turn` 会进入 Agent inbox。在 `waiting` 时到达的唤醒投递会唤醒同一个 Agent,并使激活回到 `running`。在 dispose 完成后投递消息则会冷恢复新激活。 -管理器会针对每个持久化 child,将 manager 所有的投递、child 释放和 dispose 线性化。私有 `SubagentInbox` 会把 Queue 与 Steer 委托给 Agent inbox,并持有 Activation 既有的关闭事务。如果 manager 投递与最终 dispose 发生竞争,只有一方能越过这条准入截止点:投递要么进入仍在线的 Agent inbox,要么观察到正在关闭,并遵循该操作特有的拒绝或冷恢复路径。直接操作 Agent 的工作不经过这层包装,因此自然结算会通过短暂的 maintenance 占用,在最终 flush 与最终 dispose 决策之前验证 idle 阶段,并在 child lock 内重新验证 Session 序号、Inbox 待处理状态、wake generation 与 owned-child set。仍然活跃或改变 Session、Inbox 或所有权状态的已接受工作会让本次结算尝试失效,而不会被它取消;完全在 flush 期间开始并结束的 maintenance 已在截止点前完成。 +管理器会针对每个持久化 child,将 manager 所有的投递、child 释放和 dispose 线性化。管理器的私有投递将 Queue 与 Steer 委托给 Agent inbox,并检查 Activation 的 closing promise。如果 manager 投递与最终 dispose 发生竞争,只有一方能越过这条准入截止点:投递要么进入仍在线的 Agent inbox,要么观察到正在关闭,并遵循该操作特有的拒绝或冷恢复路径。直接操作 Agent 的工作不经过这层包装,因此自然结算会通过短暂的 maintenance 占用,在最终 flush 与最终 dispose 决策之前验证 idle 阶段,并在 child lock 内重新验证 Session 序号、Inbox 待处理状态、wake generation 与 owned-child set。仍然活跃或改变 Session、Inbox 或所有权状态的已接受工作会让本次结算尝试失效,而不会被它取消;完全在 flush 期间开始并结束的 maintenance 已在截止点前完成。 ### 一个 inbox 与 follow-up 投递 -Agent inbox 是唯一队列。每条继续执行消息都使用 `Agent.followup()`,并成为一个 FIFO 轮次;继续执行管理器和宿主都不维护另一条消息队列。每个待处理 Inbox occurrence 都会让当前激活保持在线,直到它被 claim 或 discard。这条保守规则也会保留注入 context:完全停稳后仍存在的静默注入可以让 Activation 及其在线祖先继续驻留,直到唤醒投递将其 claim、queue 变更将其移除,或 manager teardown dispose 整棵树。 +Agent inbox 是唯一队列。Agent 消息使用 Steer;人类提示词选择 Queue 或 Steer。每个待处理 Inbox 项(包括静默注入的上下文)都会让当前 Activation 保持在线,直到被认领或丢弃。管理器与宿主都不维护第二条消息队列。 路由只取决于激活的驻留状态: -| 激活状态 | `followup` | +| 激活状态 | 唤醒消息投递 | |---|---| | `running` | 在同一激活中排队 | | `waiting` | 唤醒同一激活 | | 无激活 | 冷恢复新激活 | -继续执行层不定义单独的投递路由结果。成功投递 `ctx.subagents.followup()` 或 `send_message` 时会返回已接受的 `MessageId`,投递失败则会抛出异常。现有的 `agent/inbox/enqueue`、`agent/inbox/dequeue` 和 `agent/inbox/discard` 事件仍用于观测消息生命周期;适配器可以呈现通用的接受确认,但不暴露 `started`、`queued`、`resumed` 或其他 subagent 专属路由词汇。 +成功的 `sendMessage()` 返回已接受的 `MessageId`;投递失败则抛错。Inbox 事件仍是消息生命周期的观察来源。服务不暴露独立的 started/queued/resumed 路由结果。 ### child 所有权 @@ -107,9 +109,9 @@ Agent inbox 是唯一队列。每条继续执行消息都使用 `Agent.followup( 系统会一直保留所有权,直至 child 激活完成 dispose。后续改进可以更早释放限定到请求的 lease,但这需要精确关联轮次完成,而本 Task-free 设计特意不增加该机制。 -顶层拆卸由宿主负责,而不表示为另一次激活。管理器卸载会调用其内部的管理器全局 drain,同步关闭准入,等待每个已获准的物化过程完成发布或回滚,停止稳定的在线森林,并按 child-first 顺序释放。拥有选定顶层 Agent 的宿主使用 `drainContinuableDescendants(parents)`:确切的 Agent 身份只关闭这些根之下的准入,直到每个身份离开注册表,而无关森林和管理器全局准入保持在线;管理器会在第一次 await 之前停止其可见后代,只等待这些根之下已获准的物化过程,并且只释放选定分支。每个已物化的 start 和在线投递都会在与 inbox 提交相同的同步区间内重新检查调用方取消、适用的 draining 作用域、Activation dispose 和确切的 parent 权限,因此只要拆卸或 parent 替换先于接受发生,就会阻止向正在关闭的 handle 投递。只有适用的 drain 结算后,宿主才能 dispose 自己的顶层 Agent;只有管理器全局 drain 会先于管理器作用域 dispose。 +顶层拆卸由宿主负责,而不表示为另一次激活。管理器卸载会调用其内部的管理器全局 drain,同步关闭准入,等待每个已获准的物化过程完成发布或回滚,停止稳定的在线森林,并按 child-first 顺序释放。拥有选定顶层 Agent 的宿主使用 `drainDescendants(parents)`:确切的 Agent 身份只关闭这些根之下的准入,直到每个身份离开注册表,而无关森林和管理器全局准入保持在线;管理器会在第一次 await 之前停止其可见后代,只等待这些根之下已获准的物化过程,并且只释放选定分支。每个已物化的 start 和在线投递都会在与 inbox 提交相同的同步区间内重新检查调用方取消、适用的 draining 作用域、Activation dispose 和确切的 parent 权限,因此只要拆卸或 parent 替换先于接受发生,就会阻止向正在关闭的 handle 投递。只有适用的 drain 结算后,宿主才能 dispose 自己的顶层 Agent;只有管理器全局 drain 会先于管理器作用域 dispose。 -activation-owner 作用域之所以存在,是因为普通 Cordis owner effect 按注册逆序撤销,无法表达动态 child 图。管理器初始化时先注册私有作用域的结构化 disposer,再注册自身的 drain disposer,使逆序撤销先执行 drain、再释放该作用域;如果只在与后续 Agent handle 相同的作用域上注册 cleanup effect,结构化 handle dispose 就可能绕过 child-first 顺序。每个物化过程都会在启动内部事务前注册其屏障参与项,并对其确切的在线祖先建立快照,然后保持跟踪,直到安装 Activation 或完全回滚。Activation 会保留其在这组祖先中的弱成员关系,因此中间 Agent 即使离开注册表,也不会让仍在线的后代脱离宿主根节点的可见范围。其私有 `SubagentInbox` 会在取消或递归回调前安装一个记忆化的 closing promise,使限定作用域的宿主关闭、全局管理器卸载、child 释放和正常结算能够汇合,而不会重复释放。取消会在等待缓慢的后代清理之前自顶向下传播;handle 释放仍是 child-first。同级分支独立 drain;系统会记录单次 dispose 失败,但仍会尝试其余选中 handle,聚合 drain 则在所有选中分支结算后报告失败。这次进程内拆卸不会销毁持久化 child 会话。 +activation-owner 作用域之所以存在,是因为普通 Cordis owner effect 按注册逆序撤销,无法表达动态 child 图。管理器初始化时先注册私有作用域的结构化 disposer,再注册自身的 drain disposer,使逆序撤销先执行 drain、再释放该作用域;如果只在与后续 Agent handle 相同的作用域上注册 cleanup effect,结构化 handle dispose 就可能绕过 child-first 顺序。每个物化过程都会在启动内部事务前注册其屏障参与项,并对其确切的在线祖先建立快照,然后保持跟踪,直到安装 Activation 或完全回滚。Activation 会保留其在这组祖先中的弱成员关系,因此中间 Agent 即使离开注册表,也不会让仍在线的后代脱离宿主根节点的可见范围。管理器会在取消或递归回调前安装一个记忆化的 closing promise,使限定作用域的宿主关闭、全局管理器卸载、child 释放和正常结算能够汇合,而不会重复释放。取消会在等待缓慢的后代清理之前自顶向下传播;handle 释放仍是 child-first。同级分支独立 drain;系统会记录单次 dispose 失败,但仍会尝试其余选中 handle,聚合 drain 则在所有选中分支结算后报告失败。这次进程内拆卸不会销毁持久化 child 会话。 ### 相邻 Agent 消息 @@ -129,7 +131,7 @@ activation-owner 作用域之所以存在,是因为普通 Cordis owner effect ### 持久性、dispose 与恢复 -没有 Task 后,系统不再提供 `job_output`、`job_kill`、Task 状态或逐消息结果 promise。调用方 signal 只能在 inbox 接受消息前中止 start 或 follow-up。消息被接受后,parent 不能通过 `ctx.subagents` 取消已接受的消息或 dispose 激活;唯一的公开停止操作是后来的[当前轮次中断](2026-08-06-continuable-subagent-interrupt.zh.md),它以 `keepInbox` 取消在线目标的当前轮次,驻留、待处理工作与后代均保持不变。 +activation 回执无需 Jobs 即可暴露 `result` 与 `dispose()`。调用方 signal 持有未发布工作;回执 dispose 会取消确切的 activation,并等待后代启动回滚与 child-first 释放。[当前轮次中断](../../../../packages/subagent/subagent/README.zh.md)仅以 `keepInbox` 取消在线目标轮次,保留驻留、待处理工作与后代。 宿主和管理器拆卸仍是生命周期停止路径。管理器卸载会全局应用它;宿主只会在自己确切拥有的顶层 Agent 之下应用它。两种形式都会关闭适用的准入作用域,停止选中的可见 Activation,等待该作用域中已获准的物化过程,按 child-first 顺序释放,并保留持久化 Session。 @@ -141,9 +143,9 @@ activation-owner 作用域之所以存在,是因为普通 Cordis owner effect ### 范围 -本版本覆盖可继续的进程内 child,一次性委派保持不变。远程提供方必须具备单独的激活 handle,以及等价的认证控制与 child-first 完全停稳约定,才能支持同样的行为。 +本地可继续控制与外部执行共享 activation 所有权。只有本地 child 支持后续消息与冷恢复。 -它不新增 host-user 继续执行、subagent steering 操作、持久化邮箱、跨进程 lease、中断 inbox 工作的自动回放、团队权限、工作流权限、公开驻留查询、以及运行时缓存;后来的[当前轮次中断](2026-08-06-continuable-subagent-interrupt.zh.md)在此生命周期之上补充了唯一的公开停止操作。现有委派深度策略保持不变。可选的 child 到 parent 报告是后续消费该生命周期的功能,不属于基础可继续能力。 +人工 Queue/Steer 投递和[当前轮次中断](../../../../packages/subagent/subagent/README.zh.md)使用此生命周期;回执释放会停止这次确切 activation 及其所属后代。持久化邮箱、跨进程 lease、中断 inbox 工作的自动回放、团队权限、工作流权限、公开驻留查询和运行时缓存仍不属于此能力。委派深度策略和相邻 Agent 消息授权各自由其原有组件管理。 ## 曾考虑的替代方案 @@ -155,7 +157,7 @@ activation-owner 作用域之所以存在,是因为普通 Cordis owner effect **让提供方通过 Agent handle 创建、恢复 child 或投递消息。** 初始提供方只持有 `prepareContinuable()` 及其分离式创建规格这一项差异:child 是全新启动,还是带有 parent 前缀。管理器必须通过私有 activation-owner 作用域自行调用 `ctx.agents.create()`,使该作用域成为每个 handle 的结构化所有者。持久化的进程内会话已经包含初始前缀及通用重建描述符,消息投递则属于 Agent inbox。让提供方持有任何后续 handle、`SubagentRun` 或消息所有权,会让提供方保留所有权,却没有已发布行为需要它。 -**将报告投递纳入基础生命周期。** 可重复的 child 到 parent 报告与该生命周期兼容,但静默投递还是 next-step 投递、确认、持久性和重试行为都是独立的产品决策。后续的 report 包保持可选,并消费一个显式的 child 设置钩子,因此可继续驻留不会默认授予返回通道。 +**把 child 编写的报告纳入驻留。** 相邻 Agent 消息通过 `send_message` 独立组合;其权限与调度独立于驻留。父级结果投递由管理器持有,即使 child 没有发消息,也会包含运行时结算通知。 **将 `SessionHeader.parentSession` 视为在线所有权。** 持久化谱系不能证明已记录的 parent 当前持有 child。在线 parent 的 `ownedChildren` 成员关系会记录进程内关系,而不改变持久化 parent id。 @@ -176,39 +178,39 @@ activation-owner 作用域之所以存在,是因为普通 Cordis owner effect 本实现固定了以下行为: - 可继续 child 至多拥有一个在线激活和一个 Agent inbox;继续执行管理器没有激活 FIFO 或 queued 激活状态。 -- `SubagentProvider.prepareContinuable?()` 只返回分离式 `ContinuableCreateSpec`;配置为 continuable 时要求具备该能力,而 `backgroundMode` 仍是独立的策略选择。 -- 管理器通过私有 activation-owner 作用域调用 `ctx.agents.create()`,安装返回的 `AgentHandle` 并建立 parent 所有权,调用 `Agent.followup(initialPrompt)`,然后在 inbox 接受消息并产生 `MessageId` 时返回 `{ childId, messageId }`,而不等待轮次开始或消息写入会话日志。 +- `SubagentProvider.prepareContinuable?()` 返回独立的本地创建输入;`start()` 建立外部执行。 +- 管理器通过私有 activation-owner 作用域调用 `ctx.agents.create()`,安装返回的 `AgentHandle` 并建立 parent 所有权,调用 `Agent.followup(initialPrompt)`,然后在 inbox 接受消息并产生 `MessageId` 时返回包含 `childId`、`messageId`、`result` 与 `dispose()` 的回执,而不等待轮次开始或消息写入会话日志。 - 初始提示词被 inbox 接受前的每条失败路径都会导致操作被拒绝且不返回 id,并通过一个对并发投递和 drain 可见的关闭事务回滚已创建的任何 handle、激活和 parent `ownedChildren` 成员关系;生命周期发布失败不会产生无配对的终止事件。 - 冷恢复由继续执行管理器调用 `ctx.agents.resume()`,绝不通过或依赖初始 subagent 提供方;提供方移除后,描述符仍保留初始提供方名称,且 `SubagentProvider.resume?()` 和 `SubagentProviderResumeRequest` 均不存在。 -- 可继续激活直接持有 `AgentHandle`,绝不创建、包装或保留 `SubagentRun`;`SubagentProvider.start()` 和 `SubagentRun` 只用于 one-shot,且没有 `SubagentRun.steer?()`。 -- `followup()` 只接受确切的在线直接 parent,并在任何物化之后的最终无 await 的 inbox 准入边界再次检查该身份;持久化消息来源信息不能授权投递。 -- 继续执行消息始终使用 `Agent.followup()` 并共享其 inbox FIFO,包括 child 已有开放轮次的情况。 -- `ctx.subagents.followup()` 及其 `send_message` 适配器只返回已接受的 `MessageId`;继续执行层不接受投递 target,也不定义 subagent 专属路由结果。 -- 调用方 signal 只能在 inbox 接受消息前停止 start 和 follow-up,限定到宿主的拆卸与管理器全局拆卸则保留 child-first 清理;[当前轮次中断](2026-08-06-continuable-subagent-interrupt.zh.md)是唯一的公开停止操作,且不进入拆卸流程。 -- 本版本不暴露 subagent steering 操作或当前轮次控制方状态。 +- 本地 Activation 直接持有 `AgentHandle`;外部 Activation 持有 `SubagentRun`。 +- `sendMessage()` 只接受确切在线的相邻 Agent,并在物化后的最终 inbox 准入处重新检查权限;记录的来源字段不能授权投递。 +- Agent 消息使用 Steer;人类 Queue 提示词保留 FIFO 轮次顺序。 +- `ctx.subagents.sendMessage()` 返回已接受的 `MessageId`,不暴露物化路由。 +- 调用方 signal 停止未发布工作;activation 回执与宿主 drain 持有 child-first 拆卸。[当前轮次中断](../../../../packages/subagent/subagent/README.zh.md)保留待处理工作与驻留。 +- 人类提示词投递与当前轮次中断保留各自的权限检查。 - 带有在线所持 child 的空闲 Agent 会产生 `waiting` 激活,其 `AgentHandle` 继续保留。 - 向 `waiting` 投递 `next-turn` 会唤醒同一个激活;完成 dispose 后投递消息会冷恢复新激活。 - 每个由继续执行管理器管理的 parent 激活只会在直接持有的所有 child 激活完成 `AgentHandle` dispose 后进行 dispose;顶层 Agent 不加入等待图。 - Activation 最终结算会在准入开放时等待 `ctx.sessions.flush(child.session)`,将其作为 best-effort 屏障;它会记录 rejection,但不会把 listener 参与解释为持久性证明,随后在 child lock 内重新验证最终状态,再关闭准入、dispose child handle 并释放 parent 所有权,使 flush 失败不会泄漏 `waiting` Activation。 - 管理器拆卸会全局关闭准入;拥有选定顶层 Agent 的宿主则只关闭这些确切身份之下的准入,直到这些根离开注册表。两者都会按确切祖先关系跟踪已获准的物化过程,为每个选中的可见 Activation 安装一个记忆化 dispose 截止点,自顶向下传播取消,按 child-first 顺序释放 handle,即使个别分支失败也会等待所有选中分支,之后才 dispose 对应的顶层 Agent 或管理器作用域。 -- 基础生命周期不暴露隐式报告行为;可选的 report 包通过 setup 钩子贡献一个显式的 child 作用域工具。 +- parent 投递独立于 child 编写的消息发出运行时结算通知。 - 会话日志只会重建实际写入的消息,并保留每条消息的提供来源;已被 inbox 接受但未写入日志的消息没有重启保证。 - 可继续 subagent 路径不创建或依赖 Task、`JobId`、Task 完成通知、Task 取消或中间的带结果执行包装层。 -- 单元覆盖固定 `startContinuable()` 在 inbox 接受消息时的返回边界、每条接受前和生命周期发布失败路径的完整回滚、全局和限定到 parent 作用域的 drain 都会等待夹在 Agent 发布与 Activation 注册之间的物化过程完全停稳、同级森林隔离、中间 Agent 离开注册表后的确切祖先关系、不依赖提供方的冷恢复、冷恢复物化后的最终确切 parent 再授权、接受前后两个阶段的调用方 signal 与拆卸所有权,以及已接受但未写入日志的消息不会自动回放。 -- 单元覆盖固定仅由驻留状态决定的路由表、单 inbox 顺序、通过 inbox 事件关联 `MessageId`、在开放轮次期间 follow-up、等待唤醒、冷恢复、所有权注册与释放、child-first dispose、发送与 dispose 的竞争、在最终 flush await 期间接受的直接 Agent 轮次、仅修改 Session 的工作与 maintenance、没有 listener 和 listener 失败时的 best-effort 最终 flush,以及不存在公开 subagent 取消和 steering。 -- report 包的单元覆盖会分别固定仅 child 可见性、setup 撤销、权限、投递模式、稳定消息身份和生命周期竞争。 -- 一项无密钥整套应用快照覆盖 parent 委派和 follow-up 排队、不存在 subagent steering 和隐式 report 投递、保留 waiting 中的 `AgentHandle` 以及 child-first dispose。另一项 report 快照覆盖可选的显式返回通道。 +- 单元覆盖固定 `startActivation()` 在 inbox 接受消息时的返回边界、每条接受前和生命周期发布失败路径的完整回滚、全局和限定到 parent 作用域的 drain 都会等待夹在 Agent 发布与 Activation 注册之间的物化过程完全停稳、同级森林隔离、中间 Agent 离开注册表后的确切祖先关系、不依赖提供方的冷恢复、冷恢复物化后的最终确切 parent 再授权、接受前后两个阶段的调用方 signal 与拆卸所有权,以及已接受但未写入日志的消息不会自动回放。 +- 单元覆盖固定仅由驻留状态决定的路由表、单 inbox 顺序、通过 inbox 事件关联 `MessageId`、在开放轮次期间 follow-up、等待唤醒、冷恢复、所有权注册与释放、child-first dispose、发送与 dispose 的竞争、在最终 flush await 期间接受的直接 Agent 轮次、仅修改 Session 的工作与 maintenance、没有 listener 和 listener 失败时的 best-effort 最终 flush,以及单独鉴权的人类提示词投递与中断。 +- 相邻 Agent 测试覆盖直接 parent 权限、工具可见性、冷恢复与结算期间投递。 +- keyless 组装应用快照覆盖委派、相邻 Agent 消息、运行时结算通知与 child-first dispose。 ### 已接受的代价 移除 Task 会放弃通用后台工作检查、结果收集和精确 Task 取消。如果这些产品功能成为需求,就需要不会重新引入第二条执行队列的请求 ticket 或 inbox 能力。 -在后代运行期间保留激活,会按尚未完成所有权图的规模消耗 Agent 资源。现有委派深度策略仍会限制嵌套层级,而[共享 Activation 容量](2026-09-15-continuable-activation-capacity.zh.md)限制存活的可续接后代数;已结算的历史会话不保留 `AgentHandle`。 +在后代运行期间保留激活,会按尚未完成所有权图的规模消耗 Agent 资源。现有委派深度策略仍会限制嵌套层级,而[共享 Activation 容量](../../../../packages/subagent/subagent/README.zh.md)限制存活的可续接后代数;已结算的历史会话不保留 `AgentHandle`。 进程内 inbox 和所有权图无法协调两个 harness 进程。允许多个进程并发访问同一持久化存储的部署,仍需要持久化 lease 和邮箱协议。 -未安装可选 report 包时,完成 child 轮次既不会把内容发送给历史 parent,也不会唤醒它。安装后,只有显式调用 `report` 才会发送选中内容;静默投递不唤醒 parent,next-step 投递则会唤醒它并加入最近的 step 边界。无论如何,child 的详细输出都会保留在其持久化会话中。 +child 编写的 `send_message` 调用与管理器编写的结算通知在父级日志中保持区分。本地 child 的完整文本记录保留在其自身的持久化 Session 中。 -将每条继续执行消息排队,意味着 parent 无法立即纠正正在进行的 child 轮次;纠正操作会在下一个轮次执行。后续 UI steering 操作可以缩短该延迟,而不改变 follow-up 排序。 +Steer 投递抵达下一个可用 step 边界,不会抢占正在执行的工具。Queue 投递保留后续轮次顺序。 best-effort 最终 flush 失败时会记录日志,同时运行时所有权图继续 drain;持久化 child 状态可能缺失或陈旧。重试与修复需要单独的恢复设计。 diff --git a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml deleted file mode 100644 index 55f7304827..0000000000 --- a/.agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-28-skill-invocation-policy.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-skill-invocation-policy.md -/agent-note-independent-model-and-user-skill-invocation-policy: - en: d5f87e846ef041b1 - zh: 273e87043d19a709 -/agent-note-independent-model-and-user-skill-invocation-policy/problem: - en: 9fc564115fca4b22 - zh: 3a414ee5c1f7f842 -/agent-note-independent-model-and-user-skill-invocation-policy/decision: - en: dbd7aabf9fdd7c37 - zh: d71a2fd355e30fd1 -/agent-note-independent-model-and-user-skill-invocation-policy/alternatives-considered: - en: fdffcd101a494c4a - zh: db802bf9bc04ae74 -/agent-note-independent-model-and-user-skill-invocation-policy/consequences: - en: 94071073367396eb - zh: 5afca22d8140b5c0 diff --git a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml deleted file mode 100644 index 9da56d634b..0000000000 --- a/.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.i18n.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-30-queued-manual-compaction.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md -/agent-note-queued-manual-compaction-with-one-durable-lock: - en: 45504a64618db51a - zh: f8dfebaf42ee4ddf -/agent-note-queued-manual-compaction-with-one-durable-lock/problem: - en: ffd033e0cea4b612 - zh: 63fa18871f038eb7 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/compact-is-a-command-over-a-backend-independent-seam: - en: a8631732e038e08d - zh: 68cb705f38ddfa57 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/idle-maintenance-is-synchronously-claimed: - en: a11c700f32838561 - zh: c20636c5f7efabaf -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/one-parameterized-transaction-owns-every-bracket: - en: 7b75672fafc370c9 - zh: 4fb473856c9c7265 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/bracket-first-deliberately-differs-from-the-surveyed-implementations: - en: 309671a79b437033 - zh: 86e0f366aed73c35 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/markers-are-time-points-not-an-event-container: - en: e9d8043178dc946f - zh: 56808150ba2ab275 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/end-seed-distinguishes-live-and-stale-orphans: - en: cc767e216225e117 - zh: 4ec7dbc179f96c37 -/agent-note-queued-manual-compaction-with-one-durable-lock/decision/reference-implementation-boundaries: - en: 97b121c53b8f3b68 - zh: 13d86db92c30bef1 -/agent-note-queued-manual-compaction-with-one-durable-lock/alternatives-considered: - en: f1c5bae090425598 - zh: 60a30f851d85dfb0 -/agent-note-queued-manual-compaction-with-one-durable-lock/verification: - en: 37587419460fcf36 - zh: e45c752f93d5a87e -/agent-note-queued-manual-compaction-with-one-durable-lock/consequences: - en: b8c71ea1d1db7b8e - zh: 0d276bb8317ada91 diff --git a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.i18n.yaml b/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.i18n.yaml deleted file mode 100644 index aea31db0d2..0000000000 --- a/.agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-30-web-remote-markdown-images.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-remote-markdown-images.md -/agent-note-remote-web-markdown-images: - en: a11eadff0703761d - zh: 59841eaf2993b7c4 -/agent-note-remote-web-markdown-images/problem: - en: 97447c93b654eda7 - zh: 456a3ceed4967d0a -/agent-note-remote-web-markdown-images/decision: - en: 8dfbdb81dff9cae3 - zh: 217042998b65c158 -/agent-note-remote-web-markdown-images/alternatives-considered: - en: 32a4116e8ffc4b16 - zh: 59dd991fede9ac71 -/agent-note-remote-web-markdown-images/consequences: - en: 12b97c75c5d19308 - zh: ee8102b7d01e579b diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml deleted file mode 100644 index 2a3d4bae8d..0000000000 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-31-even-out-shipped-tool-rosters.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md -/agent-note-even-out-the-shipped-tool-rosters: - en: aa8ec8438dc609aa - zh: 28172207b09ea3ad -/agent-note-even-out-the-shipped-tool-rosters/problem: - en: 69e127b53366cf66 - zh: 63f783ac453d4cbe -/agent-note-even-out-the-shipped-tool-rosters/decision: - en: 45662b711bd9a201 - zh: 845cd2a9193cbe0f -/agent-note-even-out-the-shipped-tool-rosters/decision/what-stays-unmounted-and-why: - en: 7a74b42b428b9cf9 - zh: 4f534ccd26b9ecc4 -/agent-note-even-out-the-shipped-tool-rosters/decision/mcp-is-a-dependency-not-a-row: - en: 45358314e6adae26 - zh: 85ca6a6ce7bbf947 -/agent-note-even-out-the-shipped-tool-rosters/testing: - en: 07e46125d49a2068 - zh: 83a1b2737768651b -/agent-note-even-out-the-shipped-tool-rosters/alternatives-considered: - en: cf41f0bf700af59c - zh: 1dcaafe6bb944d92 -/agent-note-even-out-the-shipped-tool-rosters/consequences: - en: cf1821c985507b13 - zh: 0f7396a06d1b19fb diff --git a/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml deleted file mode 100644 index f398e72a4e..0000000000 --- a/.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-31-web-default-search.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-web-default-search.md -/agent-note-default-web-search-in-shipped-compositions: - en: 030dc2ef246ba33d - zh: 94cc720adfd3d4ef -/agent-note-default-web-search-in-shipped-compositions/problem: - en: cd5af0c73d578874 - zh: 29e14c7ff1c77391 -/agent-note-default-web-search-in-shipped-compositions/decision: - en: 7062647c8e227743 - zh: 26bdd8dc4b0cd7ad -/agent-note-default-web-search-in-shipped-compositions/alternatives-considered: - en: f28e0f1183e755ce - zh: 2cbf256a33d99686 -/agent-note-default-web-search-in-shipped-compositions/consequences: - en: d69965f3be327186 - zh: 4e2b10bb0a227f0a diff --git a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml deleted file mode 100644 index 25355eb4d5..0000000000 --- a/.agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-31-web-workspace-file-links.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-web-workspace-file-links.md -/agent-note-opening-a-produced-file-from-the-web-ui: - en: bdfcd62110aaefa2 - zh: 92981f1fe8ca52c9 -/agent-note-opening-a-produced-file-from-the-web-ui/problem: - en: 06ba78ace240645c - zh: 07a46477d41ddcee -/agent-note-opening-a-produced-file-from-the-web-ui/decision: - en: a3d6e9aa14dc6fb8 - zh: d9696d46eb4a348d -/agent-note-opening-a-produced-file-from-the-web-ui/alternatives-considered: - en: ba9603055aea84aa - zh: 3ab217f1b1ccb3b6 -/agent-note-opening-a-produced-file-from-the-web-ui/consequences: - en: aa460548654a89a5 - zh: ebb8e51f5cd18f0b diff --git a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml b/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml deleted file mode 100644 index 4ac0e96629..0000000000 --- a/.agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-02-pwsh-tool-bash-parity.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-02-pwsh-tool-bash-parity.md -/agent-note-pwsh-tool-bash-parity: - en: 76e33af7ff962cd2 - zh: 392f3d5f7100c564 -/agent-note-pwsh-tool-bash-parity/problem: - en: de5bfad14ef58243 - zh: fe8168a215719127 -/agent-note-pwsh-tool-bash-parity/decision: - en: 169a42b6c2dc965d - zh: 4172091b0299c748 -/agent-note-pwsh-tool-bash-parity/alternatives-considered: - en: 7dc381c52b191bc9 - zh: 6c07393519d3bb21 -/agent-note-pwsh-tool-bash-parity/consequences: - en: ea302108933e8a69 - zh: 563db786f22d07fa diff --git a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml b/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml deleted file mode 100644 index dc33cc6355..0000000000 --- a/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-04-claude-code-and-codex-subagent-backends.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md -/agent-note-claude-code-and-codex-subagent-backends: - en: 0f3599cefa53ad5b - zh: 988c28cdd9ee4e61 -/agent-note-claude-code-and-codex-subagent-backends/problem: - en: dbd5758c1c46b303 - zh: ca5b8479f4358c9b -/agent-note-claude-code-and-codex-subagent-backends/decision: - en: caf8c74c3466b27a - zh: 5b1daf0a738221cd -/agent-note-claude-code-and-codex-subagent-backends/decision/ownership-and-lifecycle: - en: 1c106b8b0063fee2 - zh: 0a38a1ffbe93086d -/agent-note-claude-code-and-codex-subagent-backends/codex-provider: - en: 5835894b4ac2e182 - zh: c1fcbd2b2b7e0ea0 -/agent-note-claude-code-and-codex-subagent-backends/claude-code-provider: - en: 81a92541d399b155 - zh: d73a1f20c34982bb -/agent-note-claude-code-and-codex-subagent-backends/distribution-and-evidence: - en: ff6d6216e5ce11dd - zh: 6198c006ba8c61e7 -/agent-note-claude-code-and-codex-subagent-backends/alternatives-considered: - en: 10b5b282fac03988 - zh: bf9fb4899f2c8e16 -/agent-note-claude-code-and-codex-subagent-backends/consequences: - en: 4154bcdc7ad9ff7c - zh: e2bc1f2010bb48a9 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml index 1f9b21f587..a38c0852ba 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml @@ -9,8 +9,8 @@ en: bab62798c2dc07b4 zh: fe42c5abf9127d37 /agent-note-durable-agent-teams-over-continuable-children/decision: - en: 58568b3a9b5f60d5 - zh: 02d67b6cb0736c0a + en: 95e7d723d6b00930 + zh: d18fdf9b2bf69bdb /agent-note-durable-agent-teams-over-continuable-children/profile-delegation: en: 3a66308576f74247 zh: 3453f0bcce88033d @@ -24,8 +24,8 @@ en: ab2ad55021eb34e0 zh: 2d1996df7a69201d /agent-note-durable-agent-teams-over-continuable-children/shared-checkout-boundary: - en: 3969de3b44e412c8 - zh: 12be5854816cb808 + en: 34920da68b5763e0 + zh: 7604392594e4b8a8 /agent-note-durable-agent-teams-over-continuable-children/web-projection: en: e6348054f024725b zh: 8740636e99940308 @@ -33,8 +33,8 @@ en: bb266fe7a97f0828 zh: 7c86c2d6d11ba02a /agent-note-durable-agent-teams-over-continuable-children/testing: - en: 151b462649212acf - zh: 983c4bee7b71c1e3 + en: 35ce199351d8b63d + zh: 332f0520e27e3309 /agent-note-durable-agent-teams-over-continuable-children/consequences: en: c3740ae56809e537 zh: 76a1e77d14cb6f26 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md index 6333e511ba..74556d041f 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md @@ -16,7 +16,7 @@ Agent Teams needs an explicit opt-in composition while its public contracts rema Every ordinary runtime root is the implicit Lead of a Team identified by that root's `SessionId`. The Team has no creation event: its Lead pseudo-row exists by identity, while durable state begins with the first member, message, or task event. A roster is flat and contains at most the configured number of immutable lowercase-kebab-case names. Each teammate is a continuable direct child with a reserved Session id; only the Lead creates or interrupts teammates. Ordinary provider-owned subagents outside the roster are not Team members, and an ordinary fork is a new root whose inherited Team records are excluded by their ancestor `TeamId`. -The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, which owns `ctx.agentTeams` and durable semantics, and `@deepseek-ai/dsh-experimental-tool-agent-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly and may disable legacy continuable controls with the same model-visible names. The explicit delegation policy permits Team creation only when the user asks for Agent Teams or teammates. Both packages are public members of `packages/experimental/`; the [experimental package decision](../architecture/2026-08-18-experimental-agent-teams-packages.md) owns publication, dependency isolation, and promotion. +The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, which owns `ctx.agentTeams` and durable semantics, and `@deepseek-ai/dsh-experimental-tool-agent-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly and may disable legacy continuable controls with the same model-visible names. The explicit delegation policy permits Team creation only when the user asks for Agent Teams or teammates. Both packages are public members of `packages/experimental/`; the [experimental package reference](../../../../packages/experimental/AGENTS.md) owns publication, dependency isolation, and promotion. The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit. @@ -50,9 +50,9 @@ Shared tasks are complete snapshots with Team-local ids and monotonic revisions. ## Shared checkout boundary -All members use the same cwd and observe writes immediately. The policy tells members to partition tasks, record advisory write scopes, order dependent work, and let the Lead inspect the final diff and run tests. A filesystem stale-version rejection requires rereading and rebasing the intended change. No equivalent guarantee is claimed for Bash, formatters, code generation, or direct external writes. +Members start in the Lead’s current directory and observe shared filesystem writes immediately. Later directory changes stay local to each Session. The policy tells members to partition tasks, record advisory write scopes, order dependent work, and let the Lead inspect the final diff and run tests. A filesystem stale-version rejection requires rereading and rebasing the intended change. No equivalent guarantee is claimed for Bash, formatters, code generation, or direct external writes. -Worktree isolation is not a harness runtime behavior. A deployment or prompt may arrange separate worktrees, but the Team domain does not infer branches, merge changes, or silently change cwd. This preserves the existing same-world subagent and sandbox contracts. +The Team domain does not infer branches, create worktrees, merge changes, or silently change cwd. A deployment or caller can use [explicit worktree creation](2026-09-13-explicit-worktree-creation.md) separately. This preserves the existing same-world subagent and sandbox contracts. ## Web projection @@ -82,7 +82,7 @@ Publication requires `apply` to return a new state object per applied Team event Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless product snapshot loads the Agent Teams profile bundle through `dsh --profile headless` and pins its complete model-visible tool list, Team policy, and durable workflow projection for two teammates, dependent tasks, peer delivery, waiting, completion, and aggregation. A CLI e2e reuses the same deterministic adapter and verifies normal process exit with persisted Team and child logs. -Model-facing member identities and availability follow the [tool projection decision](../simplification/2026-09-15-model-agent-availability-and-team-targets.md); service residency and durable identity remain distinct. +Model-facing member identities and availability follow the [tool projection reference](../../../../packages/experimental/tool-agent-team/README.md); service residency and durable identity remain distinct. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md index 3fed130214..d7c2bd7d7e 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md @@ -16,7 +16,7 @@ Agent Teams 的公开约定仍处于实验阶段,因此需要显式启用的 每个普通运行时 Root 都是一个隐式 Team 的 Lead,Team id 等于该 Root 的 `SessionId`。Team 没有 creation event:Lead pseudo-row 由身份直接存在,持久状态从第一条 member、message 或 task event 开始。roster 是扁平结构,最多包含配置数量、不可变且采用小写 kebab-case 的名字。每个 teammate 都是使用预留 Session id 的 continuable 直接 child;只有 Lead 可以创建或 interrupt teammate。roster 外由 provider 管理的普通 subagent 不是 Team member;普通 fork 是新的 Root,继承的 Team 记录会因 ancestor `TeamId` 被排除。 -实现拆分为 `@deepseek-ai/dsh-experimental-agent-team` 与 `@deepseek-ai/dsh-experimental-tool-agent-team`:前者负责 `ctx.agentTeams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件,并可禁用具有相同模型可见名称的旧 continuable control。显式 delegation 策略只允许在用户要求 Agent Teams 或 teammate 时创建 Team。两个包都是 `packages/experimental/` 的公开成员;[实验性包决策](../architecture/2026-08-18-experimental-agent-teams-packages.zh.md)负责发布、依赖隔离与 promotion。 +实现拆分为 `@deepseek-ai/dsh-experimental-agent-team` 与 `@deepseek-ai/dsh-experimental-tool-agent-team`:前者负责 `ctx.agentTeams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件,并可禁用具有相同模型可见名称的旧 continuable control。显式 delegation 策略只允许在用户要求 Agent Teams 或 teammate 时创建 Team。两个包都是 `packages/experimental/` 的公开成员;[实验性包参考](../../../../packages/experimental/AGENTS.md)负责发布、依赖隔离与 promotion。 Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。 @@ -50,9 +50,9 @@ Peer 通讯使用 Lead 日志 mailbox。投递前先追加并 flush `team/messag ## Shared checkout boundary -所有 member 使用相同 cwd,并立即观察写入。策略要求 member 切分任务、记录提示性 write scope、为有序工作添加依赖,并由 Lead 检查最终 diff 和运行测试。文件系统 stale-version 拒绝后必须重新读取并 rebase 修改意图。Bash、formatter、codegen 与直接外部写入不具备等价保证。 +所有 member 创建时使用 Lead 的当前目录,并立即观察共享文件系统写入。后续目录变更仅影响各自 Session。策略要求 member 切分任务、记录提示性 write scope、为有序工作添加依赖,并由 Lead 检查最终 diff 和运行测试。文件系统 stale-version 拒绝后必须重新读取并 rebase 修改意图。Bash、formatter、codegen 与直接外部写入不具备等价保证。 -Worktree isolation 不是 harness runtime 行为。deployment 或 prompt 可以安排独立 worktree,但 Team 领域不会推断 branch、merge 变更或静默改变 cwd。这样保留既有 same-world subagent 与 sandbox 契约。 +Team 领域不会推断 branch、创建 worktree、merge 变更或静默改变 cwd。deployment 或调用方可以另外使用[显式工作树创建](2026-09-13-explicit-worktree-creation.zh.md)。这样保留既有 same-world subagent 与 sandbox 契约。 ## Web projection @@ -82,7 +82,7 @@ Web panel 读取 Lead Session 的 `agentTeam` 传输投影,因为现有投影 Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONL 对账、target-local 并发顺序、pending/history 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait cancel/timeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless 产品快照会通过 `dsh --profile headless` 加载 Agent Teams profile bundle,并为两个 teammate、依赖任务、peer 投递、等待、完成和汇总固定完整的面向模型工具列表、Team policy 与持久 workflow 投影。CLI e2e 会复用同一个确定性 adapter,并验证带持久 Team 与 child 日志的正常退出。 -模型可见的成员身份与可用状态遵循[工具投影决策](../simplification/2026-09-15-model-agent-availability-and-team-targets.zh.md);服务驻留状态和持久身份仍保持区分。 +模型可见的成员身份与可用状态遵循[工具投影说明](../../../../packages/experimental/tool-agent-team/README.zh.md);服务驻留状态和持久身份仍保持区分。 ## Consequences diff --git a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.i18n.yaml deleted file mode 100644 index 74f90930fc..0000000000 --- a/.agents/notes/implemented/feature/2026-08-05-durable-web-schedule.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-05-durable-web-schedule.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# 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-durable-web-schedule.md -/agent-note-durable-scheduled-reminders: - en: 7d312bc635f68bef - zh: f675a214bfe26dac -/agent-note-durable-scheduled-reminders/problem: - en: 85e09d88c1383aeb - zh: 2d90fcbf2fe5ad3c -/agent-note-durable-scheduled-reminders/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-durable-scheduled-reminders/decision/explicit-absolute-time-boundary: - en: 16cc539a319f99b4 - zh: ae06872529f2227d -/agent-note-durable-scheduled-reminders/decision/bounded-fixed-rate-semantics: - en: 4a12a91087629614 - zh: 4f23eee242eeadd3 -/agent-note-durable-scheduled-reminders/decision/queue-admission-is-not-completion: - en: 1d88821105254303 - zh: 08982a1534d8fd18 -/agent-note-durable-scheduled-reminders/alternatives-considered: - en: f6d44a87797410ca - zh: 383ac9e712492c93 -/agent-note-durable-scheduled-reminders/verification: - en: 113a6d381ff1b145 - zh: 59f9693741adbbab -/agent-note-durable-scheduled-reminders/consequences: - en: 2121b1640094e601 - zh: 9420468b80ff1155 diff --git a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml deleted file mode 100644 index 8862a5bc60..0000000000 --- a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-06-continuable-subagent-interrupt.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md -/agent-note-continuable-subagent-current-turn-interrupt: - en: 0940bfeaf12caf8c - zh: e6a1da625950371d -/agent-note-continuable-subagent-current-turn-interrupt/problem: - en: be2b1e9ef5bb2511 - zh: 36c7ee54c37c0700 -/agent-note-continuable-subagent-current-turn-interrupt/decision: - en: db543685b2fd2f88 - zh: fcc848c04036253c -/agent-note-continuable-subagent-current-turn-interrupt/alternatives-considered: - en: e7e953783cc4a332 - zh: be5d47a503b47170 -/agent-note-continuable-subagent-current-turn-interrupt/consequences: - en: 8e75516b07cdc842 - zh: 10e31033e14241e7 -/agent-note-continuable-subagent-current-turn-interrupt/testing: - en: e46413bc89f9238e - zh: 4fb1639ae696e9a4 diff --git a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml deleted file mode 100644 index c405aca74b..0000000000 --- a/.agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.i18n.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-06-manager-owned-subagent-settlement-delivery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md -/agent-note-settlement-delivery-belongs-to-the-continuation-manager: - en: e8e4fec15acfe5d4 - zh: 6dac0279cf244edc -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/problem: - en: 325f283354af59f6 - zh: 059179e883da78c2 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision: - en: 472a34f28ed5200a - zh: 70dcbbd2e26dc32f -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/closing-text: - en: 229151705efda8a9 - zh: b4e94b52280a2076 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/runtime-source: - en: b2e4931dfb6c21b1 - zh: 9bf619cd5b697e68 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/two-ordering-rules-and-why-the-manager-owns-them: - en: 34fad866d5c6377b - zh: e865e2cb70c3e00a -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/establishment-holds-ownership-open: - en: bcaf3f22ba44fa01 - zh: d7b0968990f41a66 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/scheduling: - en: 3d1862329727a379 - zh: 92fcf4ffd366ea30 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/the-epoch-s-own-log-is-the-whole-account: - en: f444a596a75f0180 - zh: ec9c0b61a38a3458 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/decision/snapshot-coverage: - en: 9e50e4014df1d5b0 - zh: 1a111b53a149a21b -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/alternatives-considered: - en: dd3073a05a8e13bd - zh: e58debc18a591077 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/consequences: - en: 10ebfbc70ff94e20 - zh: c32a335007de89d1 -/agent-note-settlement-delivery-belongs-to-the-continuation-manager/consequences/accepted-risks: - en: 615cd09443411b4d - zh: f74b5dcab43975d3 diff --git a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.i18n.yaml b/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.i18n.yaml deleted file mode 100644 index adc20bec51..0000000000 --- a/.agents/notes/implemented/feature/2026-08-08-web-background-job-display.i18n.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-08-web-background-job-display.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-08-web-background-job-display.md -/agent-note-web-background-job-display: - en: 78eff738bc5ed554 - zh: c8a8a83853fa806b -/agent-note-web-background-job-display/problem: - en: 41ddca92193cbbf1 - zh: ec8f39ad20044d1c -/agent-note-web-background-job-display/decision: - en: 50058c6557e05f15 - zh: bbf35bb475474cd8 -/agent-note-web-background-job-display/decision/wire-shape: - en: 4d6b92cbfde80add - zh: d32e0421100eeb71 -/agent-note-web-background-job-display/decision/the-task-registry-change-feed: - en: a7a9416ab3b325dc - zh: 448820bad0224785 -/agent-note-web-background-job-display/decision/the-session-controller-carrier: - en: e9ef574e65e8fb7d - zh: d1a6e446c48a0921 -/agent-note-web-background-job-display/decision/the-client-mirror: - en: 6185994020e639ee - zh: df9b464f95c0f3e2 -/agent-note-web-background-job-display/decision/the-header-action: - en: 8985aa8f6ade32c3 - zh: 94ea0844ed8649b5 -/agent-note-web-background-job-display/decision/what-this-deliberately-does-not-do: - en: c9ff2d1fec1459d9 - zh: b28a573ca9fbb1c2 -/agent-note-web-background-job-display/alternatives-considered: - en: a648d1748674d866 - zh: 0f68b82476121c38 -/agent-note-web-background-job-display/testing: - en: f41d5017ea4beabb - zh: cb216dd1d802ebe2 -/agent-note-web-background-job-display/consequences: - en: 5204e04c3ed02d53 - zh: 9eaa9b52b69be7b2 diff --git a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.i18n.yaml index 89e9e156f9..98b9eecb42 100644 --- a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.i18n.yaml @@ -6,8 +6,8 @@ en: cfc871213b6d8360 zh: 18ff96c2272fee09 /agent-note-windows-sandbox-rung-raw-acl-restricted-tokens-over-mxc-and-appcontainer/problem: - en: 86e01a1c009ac595 - zh: 91844972d41dd6f9 + en: f62d87b7623a9904 + zh: 48bbb7c35298f96c /agent-note-windows-sandbox-rung-raw-acl-restricted-tokens-over-mxc-and-appcontainer/decision: en: 125dea7427047966 zh: 1e459b1106597695 @@ -30,8 +30,8 @@ en: e2c573d90c4ddeb9 zh: f0259213e1321722 /agent-note-windows-sandbox-rung-raw-acl-restricted-tokens-over-mxc-and-appcontainer/consequences: - en: b9f3a7c6890707f1 - zh: c9a344b50732c296 + en: dd43744417f207a5 + zh: d7b4fee1e2d87b60 /agent-note-windows-sandbox-rung-raw-acl-restricted-tokens-over-mxc-and-appcontainer/testing: en: b223cb2cf13193c9 zh: 61ddd0a0b99f8f56 diff --git a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md index c1cf8c7faf..a108e42626 100644 --- a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md +++ b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.md @@ -6,7 +6,7 @@ English | [中文](2026-08-08-windows-acl-restricted-token-sandbox.zh.md) ## Problem -The original [sandbox decision](2026-07-06-sandbox.md) left `PLATFORM_CHAINS.win32` empty, so shipped Windows profiles degraded to danger-full-access because no confining executor existed. The win32 rung must govern the two file-effect modes in the sandbox vocabulary — `read-only` (no explicit writable root) and `workspace-write` (writes under the workspace root plus a backend-defined temp area) — while reporting any effects its mechanism cannot govern; reads, network, and process visibility remain outside this vocabulary. +The original [historical sandbox decision](../../archived/feature/2026-07-06-sandbox.md) left `PLATFORM_CHAINS.win32` empty, so shipped Windows profiles degraded to danger-full-access because no confining executor existed. The win32 rung must govern the two file-effect modes in the sandbox vocabulary — `read-only` (no explicit writable root) and `workspace-write` (writes under the workspace root plus a backend-defined temp area) — while reporting any effects its mechanism cannot govern; reads, network, and process visibility remain outside this vocabulary. ## Decision @@ -40,7 +40,7 @@ The landstrip evaluation was rejected before implementation (not battle-tested; ## Consequences -Bought: write-only confinement with no new OS floor (`CreateRestrictedToken` predates the mxc releases by two decades; mandatory integrity control predates it as well), reads/network/process visibility untouched exactly as the mode vocabulary requires, deletes confined through every authority Windows accepts and confined to the granted root that owns them, and fail-closed errors carrying the API name and exact Win32 code. Sessions share the intentionally standing workspace capability but not their revocable temp capabilities; restart residue cannot block or authorize a resumed session. Cost: enforcement is structurally partial because NTFS hard-link aliases cannot be path-confined by this token shape and because a Low-integrity token cannot open a file whose DACL carries another sandbox's AppContainer package SID, which makes a tree stamped by such a tool unreadable to the confined child; no read-side or network isolation; console isolation unavailable (children created with `CREATE_NO_WINDOW` or `CREATE_NEW_CONSOLE` die with `STATUS_DLL_INIT_FAILED`; [startup visibility](../bug-fix/2026-09-16-windows-subprocess-console-visibility.md) preserves console inheritance); standing workspace security-descriptor mutations (the reuse cache, plus inert residue when a workspace is renamed) and random temp litter after an unclean shutdown until OS hygiene reclaims it; EAGER full-tree workspace propagation (`SetNamedSecurityInfoW` walks every descendant immediately — tens of seconds on large workspaces), paid once per workspace per machine; CIM unavailable in both confined modes (Authenticated Users is absent, closing the C:\-root tree-creation escape); FAT-class non-ACL targets unverified rather than writable; NULL-DACL directories not identity-preserving under a grant/revoke round trip; read-only pwsh entering ConstrainedLanguage while workspace-write remains FullLanguage absent host policy; and named-pipe opens remaining denied, so libuv piped-stdio grandchildren fail with EPERM while inherited/ignored stdio and anonymous pipes work. The package README owns these operational limits. +Bought: write-only confinement with no new OS floor (`CreateRestrictedToken` predates the mxc releases by two decades; mandatory integrity control predates it as well), reads/network/process visibility untouched exactly as the mode vocabulary requires, deletes confined through every authority Windows accepts and confined to the granted root that owns them, and fail-closed errors carrying the API name and exact Win32 code. Sessions share the intentionally standing workspace capability but not their revocable temp capabilities; restart residue cannot block or authorize a resumed session. Cost: enforcement is structurally partial because NTFS hard-link aliases cannot be path-confined by this token shape and because a Low-integrity token cannot open a file whose DACL carries another sandbox's AppContainer package SID, which makes a tree stamped by such a tool unreadable to the confined child; no read-side or network isolation; console isolation unavailable (children created with `CREATE_NO_WINDOW` or `CREATE_NEW_CONSOLE` die with `STATUS_DLL_INIT_FAILED`; [startup visibility](../../../../packages/subprocess/subprocess-local/README.md) preserves console inheritance); standing workspace security-descriptor mutations (the reuse cache, plus inert residue when a workspace is renamed) and random temp litter after an unclean shutdown until OS hygiene reclaims it; EAGER full-tree workspace propagation (`SetNamedSecurityInfoW` walks every descendant immediately — tens of seconds on large workspaces), paid once per workspace per machine; CIM unavailable in both confined modes (Authenticated Users is absent, closing the C:\-root tree-creation escape); FAT-class non-ACL targets unverified rather than writable; NULL-DACL directories not identity-preserving under a grant/revoke round trip; read-only pwsh entering ConstrainedLanguage while workspace-write remains FullLanguage absent host policy; and named-pipe opens remaining denied, so libuv piped-stdio grandchildren fail with EPERM while inherited/ignored stdio and anonymous pipes work. The package README owns these operational limits. ## Testing diff --git a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.zh.md b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.zh.md index c7ca1c3632..63d97f85a8 100644 --- a/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-08-08-windows-acl-restricted-token-sandbox.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -最初的[沙箱决策](2026-07-06-sandbox.zh.md)将 `PLATFORM_CHAINS.win32` 留空,因此交付的 Windows profile 因不存在隔离执行器而退化为 danger-full-access。win32 档必须约束沙箱词汇表中的两种文件效果模式——`read-only`(不显式授予任何可写根目录)与 `workspace-write`(允许写入工作区根目录及后端定义的临时区域)——并报告其机制无法约束的任何效果;读取、网络与进程可见性仍在这套词汇之外。 +最初的[历史沙箱决策](../../archived/feature/2026-07-06-sandbox.md)将 `PLATFORM_CHAINS.win32` 留空,因此交付的 Windows profile 因不存在隔离执行器而退化为 danger-full-access。win32 档必须约束沙箱词汇表中的两种文件效果模式——`read-only`(不显式授予任何可写根目录)与 `workspace-write`(允许写入工作区根目录及后端定义的临时区域)——并报告其机制无法约束的任何效果;读取、网络与进程可见性仍在这套词汇之外。 ## 决策 @@ -41,7 +41,7 @@ landstrip 评估在实现前已被否决(未经实战检验;自建 launcher ## 后果 -所得:仅写隔离、不引入新的 OS 版本下限(`CreateRestrictedToken` 比 mxc 的版本早二十年;强制完整性控制出现得更早)、读/网络/进程可见性完全不受影响(与模式词汇表一致)、删除在 Windows 接受的每条授权路径上都被约束且被约束在各自的授权根目录内,且 fail-closed 错误携带 API 名与精确 Win32 错误码。会话共享有意常驻的工作区能力,但不共享各自可回收的临时能力;重启残留既不能阻塞恢复的会话,也不能向其授权。所失:强制执行在结构上只能是部分的,因为此令牌形态无法把 NTFS 硬链接别名限制在路径边界内,而 Low 完整性的令牌也无法打开 DACL 中携带其他沙箱 AppContainer 包 SID 的文件——被这类工具标记过的目录树对受限子进程将不可读;无读侧或网络隔离;控制台隔离不可用(通过 `CREATE_NO_WINDOW` 或 `CREATE_NEW_CONSOLE` 创建的子进程以 `STATUS_DLL_INIT_FAILED` 死亡;[启动可见性](../bug-fix/2026-09-16-windows-subprocess-console-visibility.zh.md)保留控制台继承);工作区常驻安全描述符改动(复用缓存,以及工作区改名后的失效残留)与异常关闭后遗留的随机临时目录垃圾,直到 OS 卫生机制将其回收;工作区授权采用急切的全树传播(`SetNamedSecurityInfoW` 立即遍历每个后代——大型工作区上耗时数十秒),每台机器每个工作区只付一次;CIM 在两种受限模式下均不可用(Authenticated Users 不存在,从而关闭 C:\-root 建树逃逸);FAT 类无 ACL 目标的行为未经验证而非「仍可写」;NULL-DACL 目录在 grant/revoke 往返下不保持身份;read-only pwsh 会进入 ConstrainedLanguage,而在没有主机策略时 workspace-write 保持 FullLanguage;named pipe 打开仍被拒绝,因此 libuv 管道 stdio 的孙进程以 EPERM 失败,而继承/忽略的 stdio 与匿名管道可用。包 README 负责记录这些运行限制。 +所得:仅写隔离、不引入新的 OS 版本下限(`CreateRestrictedToken` 比 mxc 的版本早二十年;强制完整性控制出现得更早)、读/网络/进程可见性完全不受影响(与模式词汇表一致)、删除在 Windows 接受的每条授权路径上都被约束且被约束在各自的授权根目录内,且 fail-closed 错误携带 API 名与精确 Win32 错误码。会话共享有意常驻的工作区能力,但不共享各自可回收的临时能力;重启残留既不能阻塞恢复的会话,也不能向其授权。所失:强制执行在结构上只能是部分的,因为此令牌形态无法把 NTFS 硬链接别名限制在路径边界内,而 Low 完整性的令牌也无法打开 DACL 中携带其他沙箱 AppContainer 包 SID 的文件——被这类工具标记过的目录树对受限子进程将不可读;无读侧或网络隔离;控制台隔离不可用(通过 `CREATE_NO_WINDOW` 或 `CREATE_NEW_CONSOLE` 创建的子进程以 `STATUS_DLL_INIT_FAILED` 死亡;[启动可见性](../../../../packages/subprocess/subprocess-local/README.zh.md)保留控制台继承);工作区常驻安全描述符改动(复用缓存,以及工作区改名后的失效残留)与异常关闭后遗留的随机临时目录垃圾,直到 OS 卫生机制将其回收;工作区授权采用急切的全树传播(`SetNamedSecurityInfoW` 立即遍历每个后代——大型工作区上耗时数十秒),每台机器每个工作区只付一次;CIM 在两种受限模式下均不可用(Authenticated Users 不存在,从而关闭 C:\-root 建树逃逸);FAT 类无 ACL 目标的行为未经验证而非「仍可写」;NULL-DACL 目录在 grant/revoke 往返下不保持身份;read-only pwsh 会进入 ConstrainedLanguage,而在没有主机策略时 workspace-write 保持 FullLanguage;named pipe 打开仍被拒绝,因此 libuv 管道 stdio 的孙进程以 EPERM 失败,而继承/忽略的 stdio 与匿名管道可用。包 README 负责记录这些运行限制。 ## 测试 diff --git a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml b/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml deleted file mode 100644 index 0841a07056..0000000000 --- a/.agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-10-subagent-approval-pinned-never.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-10-subagent-approval-pinned-never.md -/agent-note-delegated-subagents-run-with-approvals-pinned-to-never: - en: f38cf8f2c67fc976 - zh: a4b357a3452383c3 -/agent-note-delegated-subagents-run-with-approvals-pinned-to-never/problem: - en: fa1add8bd8bb3733 - zh: 35ac0951e676acaf -/agent-note-delegated-subagents-run-with-approvals-pinned-to-never/decision: - en: 2d2e42923663abc7 - zh: 5728346e7c5ea291 -/agent-note-delegated-subagents-run-with-approvals-pinned-to-never/alternatives-considered: - en: 32fe1eb5bd7cd5ab - zh: ea437edee4fbc1c6 -/agent-note-delegated-subagents-run-with-approvals-pinned-to-never/consequences: - en: 4503eecfc7f17fed - zh: 1f201ba769ec0354 diff --git a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml deleted file mode 100644 index 562d31b711..0000000000 --- a/.agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.i18n.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-11-background-job-completion-wakes-an-idle-owner.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-11-background-job-completion-wakes-an-idle-owner.md -/agent-note-background-job-completion-wakes-an-idle-owner: - en: b761777a02303d0e - zh: a65eb415e75bcac5 -/agent-note-background-job-completion-wakes-an-idle-owner/problem: - en: da112fe20077acdf - zh: b0e3e099c3aacde5 -/agent-note-background-job-completion-wakes-an-idle-owner/decision: - en: c5d7901d87803d0c - zh: 8a1e473e88f3e400 -/agent-note-background-job-completion-wakes-an-idle-owner/decision/the-busy-owner-keeps-injection: - en: 841588bc73fc4613 - zh: 7fb4095bd55ee241 -/agent-note-background-job-completion-wakes-an-idle-owner/decision/the-cap-on-consecutive-wakes-is-opt-in-and-it-is-not-time: - en: fa9f572f763bc6b0 - zh: f3924fa49d2be22d -/agent-note-background-job-completion-wakes-an-idle-owner/decision/teardown-claims-the-report: - en: f79257596c755a32 - zh: 856bf42492425f9c -/agent-note-background-job-completion-wakes-an-idle-owner/decision/completion-is-announced-last: - en: 9cab013b3513f1e0 - zh: 78ea1b05df94d8a8 -/agent-note-background-job-completion-wakes-an-idle-owner/alternatives-considered: - en: debb2c9b924474c3 - zh: 90e2cefc230e3ec8 -/agent-note-background-job-completion-wakes-an-idle-owner/consequences: - en: a2a6f76c8cc92d57 - zh: 6eed3e6a5c75dfb9 -/agent-note-background-job-completion-wakes-an-idle-owner/consequences/accepted-risks: - en: 2141374aa4f79a2b - zh: acff1b68abf2db7e diff --git a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml deleted file mode 100644 index 8f60e2293e..0000000000 --- a/.agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-11-deepseek-request-user-id-header.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-11-deepseek-request-user-id-header.md -/agent-note-deepseek-request-user-and-session-identity-headers: - en: 6888d1c6a57a6839 - zh: e3817ccbcd782a44 -/agent-note-deepseek-request-user-and-session-identity-headers/problem: - en: fdeb3bc22bab88c0 - zh: 089ea4713776fb7a -/agent-note-deepseek-request-user-and-session-identity-headers/decision: - en: b168d3c975d191aa - zh: fdd92a1a92f87537 -/agent-note-deepseek-request-user-and-session-identity-headers/verification: - en: 3984cb1cb953605e - zh: 59e06d6eac8070d7 -/agent-note-deepseek-request-user-and-session-identity-headers/alternatives-considered: - en: 2233f9f6a934a48f - zh: c73d1db971239742 -/agent-note-deepseek-request-user-and-session-identity-headers/consequences: - en: 1621a8eab7787622 - zh: 636cf922cf76d2bc diff --git a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml b/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml deleted file mode 100644 index 13d32374f6..0000000000 --- a/.agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-11-minimal-profiles-bare-two-tool-runtime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-11-minimal-profiles-bare-two-tool-runtime.md -/agent-note-minimal-profiles-use-a-bare-runtime: - en: b086c2a8d0fca111 - zh: 3c81b90a2818e7ff -/agent-note-minimal-profiles-use-a-bare-runtime/problem: - en: 725fe45436a65f26 - zh: 97f96497173bec4f -/agent-note-minimal-profiles-use-a-bare-runtime/decision: - en: ef9aa81743b6bc46 - zh: 86bcf7e3a7e34f4b -/agent-note-minimal-profiles-use-a-bare-runtime/verification: - en: c9d0b2145e4c1271 - zh: 186919163b20379c -/agent-note-minimal-profiles-use-a-bare-runtime/alternatives-considered: - en: 35b3346928cb6c3f - zh: 6e60d1073b5a95e3 -/agent-note-minimal-profiles-use-a-bare-runtime/consequences: - en: 462abbbe168de14c - zh: 13f9e257a5f18984 diff --git a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml index c0058f198d..1f5c66710f 100644 --- a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.i18n.yaml @@ -6,8 +6,8 @@ en: feb20636c72ade23 zh: fd2e9f192a3fa527 /agent-note-product-subagents-use-profile-selected-non-interactive-permissions/problem: - en: 905b54b06f35015a - zh: b0b3a79e9f92b33d + en: 04481e9087fdfca0 + zh: 33071e25054460c5 /agent-note-product-subagents-use-profile-selected-non-interactive-permissions/decision: en: a4635e16af21d478 zh: 6cd59ad7e2bb9885 diff --git a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.md b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.md index 1ec45f9ada..af302dacc9 100644 --- a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.md +++ b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.md @@ -6,9 +6,9 @@ English | [中文](2026-08-15-product-subagent-noninteractive-permissions.zh.md) ## Problem -The [Claude Code and Codex product providers](2026-08-04-claude-code-and-codex-subagent-backends.md) run without a human interface. Native permission prompts, user dialogs, or MCP elicitation therefore cannot wait for a person, but relying on either product's ambient default can still select an interactive mode. A deployment also needs to choose broader native modes without giving the parent model or one tool call a way to raise its own authority. +The [Claude Code and Codex product providers](../../../../packages/subagent/subagent-codex/README.md) run without a human interface. Native permission prompts, user dialogs, or MCP elicitation therefore cannot wait for a person, but relying on either product's ambient default can still select an interactive mode. A deployment also needs to choose broader native modes without giving the parent model or one tool call a way to raise its own authority. -A failed product run previously reached the [subagent seam](2026-06-21-subagent-capability-seam.md) only as a stop reason. Logs could retain the product error, but the foreground parent and a [one-shot background Job](../../archived/feature/2026-08-12-product-subagent-one-shot-background-tasks.md) could not distinguish a permission refusal from another failure. Reusing assistant output for that fact would misattribute infrastructure detail to the child model. +A failed product run previously reached the [subagent seam](../../../../packages/subagent/subagent/README.md) only as a stop reason. Logs could retain the product error, but the foreground parent and a [one-shot background Job](../../archived/feature/2026-08-12-product-subagent-one-shot-background-tasks.md) could not distinguish a permission refusal from another failure. Reusing assistant output for that fact would misattribute infrastructure detail to the child model. ## Decision diff --git a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md index 80e99d9ee4..8e5065e640 100644 --- a/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md +++ b/.agents/notes/implemented/feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md @@ -6,9 +6,9 @@ Status: implemented ## Problem -[Claude Code 与 Codex 产品提供方](2026-08-04-claude-code-and-codex-subagent-backends.zh.md)都在没有人工界面的情况下运行。因此,原生权限提示、用户对话或 MCP elicitation 不能等待人员响应,但依赖任一产品环境中的默认值仍可能选择交互模式。部署也需要选择更宽松的原生模式,同时不能让父模型或单次工具调用提升自身权限。 +[Claude Code 与 Codex 产品提供方](../../../../packages/subagent/subagent-codex/README.zh.md)都在没有人工界面的情况下运行。因此,原生权限提示、用户对话或 MCP elicitation 不能等待人员响应,但依赖任一产品环境中的默认值仍可能选择交互模式。部署也需要选择更宽松的原生模式,同时不能让父模型或单次工具调用提升自身权限。 -失败的产品运行此前只能把终止原因送入 [subagent seam](2026-06-21-subagent-capability-seam.zh.md)。日志可以保留产品错误,但前台父 agent 与[一次性后台 Job](../../archived/feature/2026-08-12-product-subagent-one-shot-background-tasks.md)无法区分权限拒绝和其他失败。若复用 assistant 输出承载该事实,则会把基础设施说明错误归因给子模型。 +失败的产品运行此前只能把终止原因送入 [subagent seam](../../../../packages/subagent/subagent/README.zh.md)。日志可以保留产品错误,但前台父 agent 与[一次性后台 Job](../../archived/feature/2026-08-12-product-subagent-one-shot-background-tasks.md)无法区分权限拒绝和其他失败。若复用 assistant 输出承载该事实,则会把基础设施说明错误归因给子模型。 ## Decision diff --git a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml b/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml deleted file mode 100644 index 167308d649..0000000000 --- a/.agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-18-arbitrary-seq-session-fork.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-18-arbitrary-seq-session-fork.md -/agent-note-exact-event-session-forks-with-shared-crash-repair: - en: f75e952184cae150 - zh: 32c2519c85aa6822 -/agent-note-exact-event-session-forks-with-shared-crash-repair/problem: - en: e01b73d9d4b50c50 - zh: 14f7816869b76241 -/agent-note-exact-event-session-forks-with-shared-crash-repair/decision: - en: 0b7afc12950ce308 - zh: 384d26f698a344a7 -/agent-note-exact-event-session-forks-with-shared-crash-repair/alternatives-considered: - en: 49544ba70825c8e9 - zh: 41c7dceb9241fc5a -/agent-note-exact-event-session-forks-with-shared-crash-repair/consequences: - en: a829fc736a132d89 - zh: 03b116766bd44d64 -/agent-note-exact-event-session-forks-with-shared-crash-repair/testing: - en: f15e826fa12a4f55 - zh: fe053fa01304e072 diff --git a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.i18n.yaml b/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.i18n.yaml deleted file mode 100644 index 2095b6be29..0000000000 --- a/.agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-18-model-selected-subagent-routes.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md -/agent-note-model-selected-subagent-routes: - en: 1e719fd754836549 - zh: 603cce7a5f9ec6bd -/agent-note-model-selected-subagent-routes/problem: - en: f4010276228f4441 - zh: 50ac98640dbf519a -/agent-note-model-selected-subagent-routes/decision: - en: 39836773d1830515 - zh: 537c96bfa93573e7 -/agent-note-model-selected-subagent-routes/alternatives-considered: - en: 3b9fd91294fbfd27 - zh: 2854dd690ead3d15 -/agent-note-model-selected-subagent-routes/consequences: - en: 55969d3742da39ce - zh: a248167f9bb2fa4e -/agent-note-model-selected-subagent-routes/related-decisions: - en: 28c4eb2e90488ff0 - zh: ba59bb2fafdd4720 diff --git a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.i18n.yaml b/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.i18n.yaml deleted file mode 100644 index 6e1c636324..0000000000 --- a/.agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-20-unified-image-request-pipeline.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-20-unified-image-request-pipeline.md -/agent-note-unified-normalized-attachments-request-versions-and-provider-files: - en: 79d1a6f50ac76c3d - zh: 9af0070009537602 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/problem: - en: 7ce3a351a1492dd0 - zh: 7c7e3bb6799822f8 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision: - en: 98e7aad889f5b79c - zh: 84b24f6fd9209fcf -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision/provider-independent-normalized-attachment: - en: 75b1d3732f3a9d05 - zh: c8683df3c6f0c989 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision/deterministic-request-versions: - en: 8afa51d02643bd40 - zh: d27d9a5b09fc6d74 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision/stable-handles: - en: 1b204c138b0d8e6c - zh: 3d6a2e82372e6825 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision/deepseek-files-lifecycle: - en: b35a18963d122b21 - zh: faa21c45cef3555a -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/decision/diagnostics: - en: e4c79434bedfc385 - zh: 9e7de1061629ef5e -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/alternatives-considered: - en: 7bfd39c761549b85 - zh: 6ac48c5842f7df91 -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/verification: - en: 7697f72ce5a7d969 - zh: 733ca1e0a05ae4ed -/agent-note-unified-normalized-attachments-request-versions-and-provider-files/consequences: - en: 1a6ef14a3e13b502 - zh: bc4ab273755b2739 diff --git a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml b/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml deleted file mode 100644 index 99cd729035..0000000000 --- a/.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-22-fire-and-forget-webhook-sessions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md -/agent-note-fire-and-forget-webhook-sessions: - en: adb18c2b0d575306 - zh: 2605913ffe5bafaa -/agent-note-fire-and-forget-webhook-sessions/problem: - en: 394e062b7e8f31a0 - zh: bcb26075cf62115c -/agent-note-fire-and-forget-webhook-sessions/decision: - en: 08ff83adfec1a7da - zh: 871b9998631f22ac -/agent-note-fire-and-forget-webhook-sessions/provider-adapters: - en: 8eb2865f18fcf018 - zh: 1b73dfd634a8c07a -/agent-note-fire-and-forget-webhook-sessions/session-creation: - en: fbc57974dd81bf5a - zh: 1a4810df6b7a776f -/agent-note-fire-and-forget-webhook-sessions/alternatives-considered: - en: 59634068f7c22476 - zh: 844472f7a5e9de91 -/agent-note-fire-and-forget-webhook-sessions/verification: - en: e2a3192fc68b1ade - zh: 63c2b07bf5f32696 -/agent-note-fire-and-forget-webhook-sessions/consequences: - en: 8b40a7c91834a281 - zh: 882125d8070d77c1 diff --git a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.i18n.yaml b/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.i18n.yaml deleted file mode 100644 index b6b5b90bc1..0000000000 --- a/.agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-22-standard-acp-automation-controls.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-22-standard-acp-automation-controls.md -/agent-note-standard-acp-v1-automation-controls: - en: e6a4b457afbf12ac - zh: b1d83f4892e05f99 -/agent-note-standard-acp-v1-automation-controls/problem: - en: ba88135e9528b4c7 - zh: 8bc1be92b1f5178e -/agent-note-standard-acp-v1-automation-controls/decision: - en: 0a2b2a83aca7d189 - zh: 8de789db33953283 -/agent-note-standard-acp-v1-automation-controls/per-session-ownership: - en: 5b98c5e68eccfe76 - zh: c22dc47ad8003f22 -/agent-note-standard-acp-v1-automation-controls/persistent-session-controls: - en: 122752f1cb116417 - zh: 96971bcc1fed4a8a -/agent-note-standard-acp-v1-automation-controls/standard-configuration-options: - en: 61cb15891965c67a - zh: a0a958094b60951f -/agent-note-standard-acp-v1-automation-controls/standard-mcp-mapping: - en: 57acbe1c89af4c6b - zh: 3417e3d4b1874d94 -/agent-note-standard-acp-v1-automation-controls/semantic-update-projection: - en: 8e5128cd34629af3 - zh: 6132e78ddf9b3cf9 -/agent-note-standard-acp-v1-automation-controls/alternatives-considered: - en: fc94c87f43787d95 - zh: 17850ecc35e6ab9d -/agent-note-standard-acp-v1-automation-controls/verification: - en: 08e2b98606d9d362 - zh: 99d6aef7c89e287f -/agent-note-standard-acp-v1-automation-controls/consequences: - en: 9f839d4720f07d28 - zh: 5164ae3eb5b52385 diff --git a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.i18n.yaml deleted file mode 100644 index d5220cd2f4..0000000000 --- a/.agents/notes/implemented/feature/2026-08-24-activity-observation-seam.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-24-activity-observation-seam.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-24-activity-observation-seam.md -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client: - en: 7fe6230c2327e0b5 - zh: 13a07f7926f6861c -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/problem: - en: 8b50b64ed5a852bd - zh: 21d651e0109dc641 -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/decision: - en: 88b51276287344bd - zh: 0827faa6c822e4a5 -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/decision/why-not-durable-session-events-the-control-stream-or-the-jobs-seam: - en: 58d884216caa6d85 - zh: aa47eb76bdb1ece0 -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/decision/two-cursor-regimes-kept-separate: - en: 7ab433f08fa6140a - zh: d001a90815b0156b -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/alternatives-considered: - en: 4462632fd94010c1 - zh: 01d1285f2ac6e42c -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/testing: - en: 26687866bbf7be90 - zh: 32dea8052bd5465b -/agent-note-the-activity-observation-seam-ctx-activities-and-live-output-streaming-to-the-web-client/consequences: - en: e975f4f2410e8619 - zh: 42f16406950a4d6f diff --git a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.i18n.yaml b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.i18n.yaml index 75bb4b5177..d4f615792e 100644 --- a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.i18n.yaml @@ -18,5 +18,5 @@ en: f1927d31ffc1dee9 zh: b0fed04b80043c71 /agent-note-user-authorized-subagent-model-routes/related-decisions: - en: 0b54fad7171103ec - zh: 8cf1d007a42fb991 + en: 595a9485d91592c6 + zh: 7b32611ea380498c diff --git a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.md b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.md index ef6f90d2cc..e7f3b4fcae 100644 --- a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.md +++ b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.md @@ -40,4 +40,4 @@ Model selection has no unrestricted static mode. The default-off Host setting is ## Related decisions -The route arguments, adapter preflight, discovery tool, and fork cache restriction remain owned by [model-selected subagent routes](2026-08-18-model-selected-subagent-routes.md). +The route arguments, adapter preflight, discovery tool, and fork cache restriction remain owned by [model-selected subagent routes](../../../../packages/subagent/tool-subagent/README.md). diff --git a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.zh.md b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.zh.md index 23519bfc66..f55f60de1b 100644 --- a/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.zh.md +++ b/.agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.zh.md @@ -40,4 +40,4 @@ Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled` ## Related decisions -路由参数、适配器预检、发现工具与 fork 缓存限制仍由[模型选择的 subagent 路由](2026-08-18-model-selected-subagent-routes.zh.md)负责。 +路由参数、适配器预检、发现工具与 fork 缓存限制仍由[模型选择的 subagent 路由](../../../../packages/subagent/tool-subagent/README.zh.md)负责。 diff --git a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml b/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml deleted file mode 100644 index ee45f9f6ea..0000000000 --- a/.agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-25-feedback-gated-telemetry-default.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-25-feedback-gated-telemetry-default.md -/agent-note-feedback-gated-session-telemetry-default: - en: 213a794503e0d7bc - zh: e086babdf2ad52c9 -/agent-note-feedback-gated-session-telemetry-default/problem: - en: 46b3efd996067d48 - zh: fc59ee763b07987e -/agent-note-feedback-gated-session-telemetry-default/decision: - en: 4136623e5589a592 - zh: db7bc5e9e6ebc19f -/agent-note-feedback-gated-session-telemetry-default/alternatives-considered: - en: 14b51f0de8771943 - zh: 616afb4097857368 -/agent-note-feedback-gated-session-telemetry-default/consequences: - en: d3ed844cc1e29b0b - zh: 76838815edb0a450 diff --git a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml b/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml deleted file mode 100644 index 9c2732ed18..0000000000 --- a/.agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-25-promote-open-anywhere-plugin.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-25-promote-open-anywhere-plugin.md -/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package: - en: 279cf946b4f6c386 - zh: cf76602956b811de -/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/problem: - en: 08d8d5454fdc2e75 - zh: 22381ce00cb51148 -/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/decision: - en: 26a46d3b6f21288a - zh: 94c32f789b3e28ac -/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/alternatives-considered: - en: cc7c2ed172e4de3f - zh: b8fed28d9ee835c5 -/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/consequences: - en: 5be6b2891e3429c9 - zh: 68ffcff52676824f diff --git a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.i18n.yaml b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.i18n.yaml index 49360cffec..496d2e8136 100644 --- a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.i18n.yaml @@ -21,5 +21,5 @@ en: 3a50ac370d51cda9 zh: d490f58922f8795b /agent-note-generic-file-upload/related: - en: f7e186f0decb4e79 - zh: e5bc9fbd24b6d626 + en: 7579c49eefdb8488 + zh: 68ad384fd9d2f8f9 diff --git a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.md b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.md index 196d048da9..f60649a083 100644 --- a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.md +++ b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.md @@ -51,6 +51,6 @@ Unit and integration coverage pins verbatim and streamed storage, name sanitizat ## Related -The [Trajectory attachment presentation decision](../bug-fix/2026-09-15-trajectory-attachment-presentation.md) owns ledger counts and inspector attachment layouts. +The [Trajectory reference](../../../../packages/client/ui-trajectory/README.md) documents ledger counts and inspector attachment layouts. -The [input echo admission decision](../bug-fix/2026-09-22-input-echo-admission-ownership.md) owns placement-specific retirement and delayed Inbox suppression for submissions with or without attachments. +The [input echo admission reference](../../../../packages/api/session-controller/README.md) owns placement-specific retirement and delayed Inbox suppression for submissions with or without attachments. diff --git a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.zh.md b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.zh.md index fce7cef76c..3d1667d181 100644 --- a/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.zh.md +++ b/.agents/notes/implemented/feature/2026-08-26-generic-file-upload.zh.md @@ -51,6 +51,6 @@ Status: implemented ## 相关记录 -[Trajectory 附件展示决定](../bug-fix/2026-09-15-trajectory-attachment-presentation.zh.md)负责列表计数与详情面板附件布局。 +[Trajectory 参考](../../../../packages/client/ui-trajectory/README.zh.md)说明列表计数与详情面板附件布局。 -[输入回显入档决策](../bug-fix/2026-09-22-input-echo-admission-ownership.zh.md)负责有无附件提交的按展示归属回收和迟到 Inbox 排除。 +[输入回显入档参考](../../../../packages/api/session-controller/README.zh.md)负责有无附件提交的按展示归属回收和迟到 Inbox 排除。 diff --git a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.i18n.yaml b/.agents/notes/implemented/feature/2026-08-26-human-job-kill.i18n.yaml deleted file mode 100644 index 787b132459..0000000000 --- a/.agents/notes/implemented/feature/2026-08-26-human-job-kill.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-26-human-job-kill.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-26-human-job-kill.md -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path: - en: d47dedfab1caa170 - zh: 00bb8cd62ed79d54 -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path/problem: - en: 3690502ccc80b5a3 - zh: cfd3c378e3921864 -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path/decision: - en: ccd3751d2ecaba5c - zh: f8f820f9492a9bf3 -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path/alternatives-considered: - en: 74a385c561d8854a - zh: c131a4e386d9b976 -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path/testing: - en: 094513090d51adfc - zh: b6598110da6c5793 -/agent-note-human-job-kill-an-unclaimed-terminal-report-instead-of-a-second-cancellation-path/consequences: - en: 0bcec15bd9c760fe - zh: 754269206f110f69 diff --git a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml b/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml deleted file mode 100644 index 428a985b5b..0000000000 --- a/.agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-26-shell-execute-projection-and-jobs-at-start.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-26-shell-execute-projection-and-jobs-at-start.md -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job: - en: bbc82e3f79a66fcb - zh: 6c348491e0db97b0 -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job/problem: - en: e0e1292e5a785322 - zh: 34ec6572ed6cf406 -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job/decision: - en: 332ddaffd22e473f - zh: 15c0ff6808316770 -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job/alternatives-considered: - en: d888a561fafa5bb3 - zh: 719d687f2b3e4c10 -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job/testing: - en: 65f59286de9fa00a - zh: 664d7f82a1479153 -/agent-note-one-shell-execute-foreground-as-a-projection-timeout-as-a-bounded-wait-on-the-job/consequences: - en: 5a5278fc96afacc1 - zh: cc088ea97c6249c9 diff --git a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml b/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml deleted file mode 100644 index db46bc5f64..0000000000 --- a/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-27-continuable-subagent-human-inbox-control.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md -/agent-note-human-inbox-controls-for-continuable-subagents: - en: 42b7ed7255c6c527 - zh: c3faf95870ba0e0c -/agent-note-human-inbox-controls-for-continuable-subagents/problem: - en: 4241f01a3a205ff5 - zh: 415c642ad379a53c -/agent-note-human-inbox-controls-for-continuable-subagents/decision: - en: 4f8c53964ac18aff - zh: 31d747618ca608cc -/agent-note-human-inbox-controls-for-continuable-subagents/alternatives-considered: - en: c1f10fb70c8bb500 - zh: 4dc0ec6cac95595c -/agent-note-human-inbox-controls-for-continuable-subagents/consequences: - en: 5fdc5b07eae2633c - zh: 1359d126d182d219 diff --git a/.agents/notes/implemented/feature/2026-08-28-auto-review.i18n.yaml b/.agents/notes/implemented/feature/2026-08-28-auto-review.i18n.yaml index ab336fff79..f985dfc4a3 100644 --- a/.agents/notes/implemented/feature/2026-08-28-auto-review.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-28-auto-review.i18n.yaml @@ -9,8 +9,8 @@ en: 492ba2355360d81a zh: 825c0e7ab1cf4beb /agent-note-experimental-auto-review-before-each-tool-call/decision: - en: fd4103b65b24f6e2 - zh: 5c1a364355b7e9ab + en: 3868c5c4b087bdba + zh: c3dff98fc5f05f19 /agent-note-experimental-auto-review-before-each-tool-call/decision/effects-and-authority: en: 55a79d8572fb4332 zh: f63d03d0f3bb5811 @@ -21,8 +21,8 @@ en: 237018634b5f9dcf zh: cbc49d9c1b794065 /agent-note-experimental-auto-review-before-each-tool-call/decision/process-catalog-and-children: - en: c305e3506fb4febc - zh: aecc99cda8be3de2 + en: 6ebe6f90241fd322 + zh: eafdebc5277e83a1 /agent-note-experimental-auto-review-before-each-tool-call/alternatives-considered: en: fe97128ed05b9e43 zh: 4aaa344933fa89b4 diff --git a/.agents/notes/implemented/feature/2026-08-28-auto-review.md b/.agents/notes/implemented/feature/2026-08-28-auto-review.md index 54a2609e33..c19e780d01 100644 --- a/.agents/notes/implemented/feature/2026-08-28-auto-review.md +++ b/.agents/notes/implemented/feature/2026-08-28-auto-review.md @@ -10,7 +10,7 @@ Full access lets useful project work proceed without repeated approvals, but it ## Decision -[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.md) is an explicitly installed experimental Web layer, published under the [experimental package publication decision](../process/2026-09-12-publish-all-experimental-packages.md). Default Web retains Read Only, Workspace Write, and Full access. The layer contributes current-session `auto`, whose only durable identity is `permission/preset:auto`; it uses Full access's `danger-full-access` sandbox and tool definitions with the approval policy chosen by [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md). Headless, General settings, and new-session defaults exclude the integration. +[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.md) is an explicitly installed experimental Web layer, published under the [experimental package publication policy](../../../../packages/experimental/README.md). Default Web retains Read Only, Workspace Write, and Full access. The layer contributes current-session `auto`, whose only durable identity is `permission/preset:auto`; it uses Full access's `danger-full-access` sandbox and tool definitions with the approval policy chosen by [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md). Headless, General settings, and new-session defaults exclude the integration. Every native call and started PTC `tools.*` inner call receives one review before its body. The outer `run_code` transport and direct Node effects in a PTC program remain outside this guarantee. There are no tool-name exemptions, cached grants, retries, configurable policy, or second authorization check; [the user-approval fallback decision](2026-09-24-auto-review-user-approval-fallback.md) owns what follows a denial. A repeated call receives a fresh review. @@ -60,7 +60,7 @@ The permission owner publishes one complete process catalog through generated `p Auto appears with a superscript `EXP` badge. Both visible current-session pickers require the experimental confirmation; explicit `/permission auto` is already consent. The composer uses the generic Menu's existing portal placement to stay within the viewport while keeping its 218–360px bounds. The slash popup keeps `min(220px, 100%)` with `max-width: 100%`, including when a narrow composer collapses its trigger. -The [delegation-time policy capture](2026-07-25-subagent-policy-inheritance.md) records Auto or Full access before the first await and appends that existing preset event after fork seeding and sandbox/approval overrides. One-shot and continuable creation share the rule; cold resume reads only the child log. Later parent switches do not alter that child, while a later child switch can win. Read Only and Workspace Write retain sandbox inheritance plus `approval: never` and may therefore remain `custom`. Auto children classify each call independently using the existing lineage and messages, without parent call metadata, delegation records, receipts, Header/descriptor additions, or a Session-format change. Out-of-process children retain their own permission systems after the parent delegation is allowed. +The [delegation-time policy capture](../../../../packages/subagent/subagent/README.md) records Auto or Full access before the first await and appends that existing preset event after fork seeding and sandbox/approval overrides. One-shot and continuable creation share the rule; cold resume reads only the child log. Later parent switches do not alter that child, while a later child switch can win. Read Only and Workspace Write retain sandbox inheritance plus `approval: never` and may therefore remain `custom`. Auto children classify each call independently using the existing lineage and messages, without parent call metadata, delegation records, receipts, Header/descriptor additions, or a Session-format change. Out-of-process children retain their own permission systems after the parent delegation is allowed. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md b/.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md index 6555a7b5cd..bd99839132 100644 --- a/.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md +++ b/.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md @@ -10,7 +10,7 @@ Full access 让有用的项目工作无需反复审批即可继续,但也允 ## 决策 -[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.zh.md)是显式安装的实验性 Web 层,按[实验包发布决策](../process/2026-09-12-publish-all-experimental-packages.zh.md)参与发布。默认 Web 保持 Read Only、Workspace Write 与 Full access。此层贡献仅限当前会话的 `auto`,唯一持久身份为 `permission/preset:auto`;它使用 Full access 的 `danger-full-access` 沙箱与工具定义,审批策略由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)决定。Headless、通用设置与新会话默认值都排除此 integration。 +[`dsh-experimental-auto-review`](../../../../packages/experimental/auto-review/README.zh.md)是显式安装的实验性 Web 层,按[实验包发布策略](../../../../packages/experimental/README.zh.md)参与发布。默认 Web 保持 Read Only、Workspace Write 与 Full access。此层贡献仅限当前会话的 `auto`,唯一持久身份为 `permission/preset:auto`;它使用 Full access 的 `danger-full-access` 沙箱与工具定义,审批策略由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)决定。Headless、通用设置与新会话默认值都排除此 integration。 每个原生调用与已开始的 PTC `tools.*` inner call 都在 body 前接受一次审查。外层 `run_code` transport 与 PTC 程序内直接 Node 效果不在保证范围内。不提供按工具名豁免、缓存 grant、重试、可配置策略或第二授权检查;拒绝之后的行为由[用户审批兜底决策](2026-09-24-auto-review-user-approval-fallback.zh.md)负责。重复调用也重新审查。 @@ -60,7 +60,7 @@ Permission owner 通过生成的 `permissionPresets` Remote 方法发布一份 Auto 带右上标 `EXP`。两个可见当前会话选择器都要求实验确认;显式 `/permission auto` 已构成同意。Composer 使用通用 Menu 既有 portal 定位保持在视口内,同时保留 218–360px 边界。Slash popup 保留 `min(220px, 100%)` 与 `max-width: 100%`,窄 composer 将 trigger 折叠时也一样。 -[委派时权限捕获](2026-07-25-subagent-policy-inheritance.zh.md)在首次 await 前记录 Auto 或 Full access,并在 fork seed 与 sandbox/approval override 之后追加该既有 preset event。单次与可继续创建共用此规则;cold resume 只读取 child 日志。后续 parent 切换不改变该 child,后续 child 自己的切换仍可胜出。Read Only 与 Workspace Write 保留 sandbox 继承加 `approval: never`,因此可能保持 `custom`。Auto child 使用既有 lineage 与消息独立分类每次调用,不增加父 call metadata、委派记录、receipt、Header/descriptor 字段或 Session format。进程外 child 在父委派获准后保留自己的权限系统。 +[委派时权限捕获](../../../../packages/subagent/subagent/README.zh.md)在首次 await 前记录 Auto 或 Full access,并在 fork seed 与 sandbox/approval override 之后追加该既有 preset event。单次与可继续创建共用此规则;cold resume 只读取 child 日志。后续 parent 切换不改变该 child,后续 child 自己的切换仍可胜出。Read Only 与 Workspace Write 保留 sandbox 继承加 `approval: never`,因此可能保持 `custom`。Auto child 使用既有 lineage 与消息独立分类每次调用,不增加父 call metadata、委派记录、receipt、Header/descriptor 字段或 Session format。进程外 child 在父委派获准后保留自己的权限系统。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.i18n.yaml b/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.i18n.yaml deleted file mode 100644 index c220a01232..0000000000 --- a/.agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-01-workflow-run-in-background.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-01-workflow-run-in-background.md -/agent-note-workflow-runs-gain-run-in-background-over-the-job-registry: - en: f009de4a29acc7d0 - zh: d207b3722564338d -/agent-note-workflow-runs-gain-run-in-background-over-the-job-registry/problem: - en: 4ef955f5184800a3 - zh: 8e4e74557efac4ca -/agent-note-workflow-runs-gain-run-in-background-over-the-job-registry/decision: - en: b0bc1669a6e61b1e - zh: 99ba5afe693aed9d -/agent-note-workflow-runs-gain-run-in-background-over-the-job-registry/alternatives-considered: - en: 70c9a5dd502eae0c - zh: 2b4f7b6a83cfec18 -/agent-note-workflow-runs-gain-run-in-background-over-the-job-registry/consequences: - en: 439882b972d28d4e - zh: 3bd61f42e29f490c diff --git a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.i18n.yaml b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.i18n.yaml index 091e991d76..d5bc1499d0 100644 --- a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.i18n.yaml @@ -9,8 +9,8 @@ en: dae256ae605900f2 zh: 11e9c51126ad836c /agent-note-right-sidebar-docking-infrastructure/decision: - en: a30a377c8df0e654 - zh: f2577e74c6be3e88 + en: a4e8f98a9ed5a52f + zh: 5e8a9afff0939348 /agent-note-right-sidebar-docking-infrastructure/decision/package-topology: en: 7dfd8ad3a822b421 zh: 338151c69866c7d5 @@ -18,17 +18,17 @@ en: af79bc03cd8e8f72 zh: 88145221570301bf /agent-note-right-sidebar-docking-infrastructure/decision/the-frame-s-right-column: - en: 78c5b5bf86d0293a - zh: 28b8856d88b78d1e + en: add016d9b9a2bbc4 + zh: 2a089451b4381467 /agent-note-right-sidebar-docking-infrastructure/decision/state: - en: 7a73e989059c0871 - zh: ef4f8783fc5f3229 + en: c0548c6c6d55c952 + zh: 268473645c953a63 /agent-note-right-sidebar-docking-infrastructure/decision/beyond-the-surface: - en: 82b5fd9d88c3ea43 - zh: d8581e996abbe034 + en: 2a6ff6980587f18a + zh: 912723a000b32006 /agent-note-right-sidebar-docking-infrastructure/decision/entry-points-and-removals: - en: e9cb121a18afadc2 - zh: 37ff2e89d5641e11 + en: 372a1a775a7d8e4e + zh: f9ad77b399a07f1d /agent-note-right-sidebar-docking-infrastructure/alternatives-considered: en: 6569babd25fc0599 zh: f9d93c937b2cad60 diff --git a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.md b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.md index c5bfb75730..a01ba5bc6f 100644 --- a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.md +++ b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.md @@ -12,7 +12,7 @@ Files the agent produced were the sharpest case. A produced-file chip or a `read ## Decision -The right column is a per-session docking surface — split panes, tabs, floating panels, and an undoable operation sequence — owned by `ui-sidebar-right` over the `ui-dockkit` engine, replacing the Detail panel. This note owns the surface: the engine, the frame's right column, the panel's presentations and controls, and the per-session state. What lives in the surface is decided elsewhere: how plugins declare tab types, open content, and receive their props is [tab types and navigation](../architecture/2026-09-05-sidebar-tab-types-and-navigation.md); live data behind an address is the [client resource model](../architecture/2026-09-05-client-resource-model.md); reading workspace files is the [workspace file service](../architecture/2026-09-05-workspace-files-service.md); and the guide, the text preview, and the file tree are the [shipped types](2026-09-05-sidebar-text-preview-and-file-tree.md). +The right column is a per-session docking surface — split panes, tabs, floating panels, and an undoable operation sequence — owned by `ui-sidebar-right` over the `ui-dockkit` engine, replacing the Detail panel. This note owns the surface: the engine, the frame's right column, the panel's presentations and controls, and the per-session state. What lives in the surface is decided elsewhere: how plugins declare tab types, open content, and receive their props is [tab types and navigation](../../../../packages/client/ui-sidebar-right/README.md); live data behind an address is the [client resource model](../architecture/2026-09-05-client-resource-model.md); reading workspace files is the [workspace file service](../architecture/2026-09-05-workspace-files-service.md); and the guide, the text preview, and the file tree are the [shipped types](../../../../packages/client/ui-sidebar-documentpreview/README.md). ### Package topology @@ -33,23 +33,23 @@ Components render a snapshot and report settled intents, one per gesture: a drag ### The frame's right column -[Responsive Sidebar and tab information](../architecture/2026-09-07-sidebar-responsive-tab-info.md) supersedes this note's no-concession layout, overlay presentation and product pane limit. `ui-layout` still owns three-column geometry and pixel width preferences; the Sidebar occupant reports presentation through `ctx.layout.openRightbar(track, fullscreen)` and `closeRightbar()`, without the frame injecting the Sidebar package. Exact width rules belong to [ui-layout](../../../../packages/client/ui-layout/README.md). +[Responsive Sidebar and tab information](../../../../packages/client/ui-sidebar-right/README.md) supersedes this note's no-concession layout, overlay presentation and product pane limit. `ui-layout` still owns three-column geometry and pixel width preferences; the Sidebar occupant reports presentation through `ctx.layout.openRightbar(track, fullscreen)` and `closeRightbar()`, without the frame injecting the Sidebar package. Exact width rules belong to [ui-layout](../../../../packages/client/ui-layout/README.md). The right Sidebar uses one mounted content tree in normal and fullscreen modes; hiding preserves retained tab state, and fullscreen covers the viewport while retaining underlying column reservation. `DockLayout` renders stable Grid cells with fixed-position floating frames in the same tree; foreground floats remain open when the Sidebar closes. The product limits docking to two horizontal panes and a 20–80% divider; the generic engine keeps its own defaults. ### State -[Default pages](2026-09-08-sidebar-default-pages.md) supersede default-guide reseeding here; [last-tab close rules](2026-09-08-sidebar-last-tab-close-rules.md) own explicit closing, while moving tabs still settles emptied panes. +[Sidebar guide rules](../../../../packages/client/ui-sidebar-right/README.md#the-guide) define default-page selection and explicit closing, while moving tabs still settles emptied panes. -`ui-sidebar-right` keeps one `SurfaceState` per session id — the layout, its history, and the mint counter — in a store declared at the seat registration. Every action mints the ids its intent needs, asks a kit planner for the operations, runs the settle planner over the result, and records the whole intent as one history entry before assigning the session's surface back; no action edits a layout in place. The settle step is the product's rule: a docked pane whose last tab is closed, moved out, or floated is merged away, and an expanded empty root pane receives the current default page. A collapsed surface may remain empty until its next expansion; no separate pane-closing gesture exists. [Layout persistence and provider recovery](../architecture/2026-09-14-sidebar-layout-provider-recovery.md) owns Session-scoped browser storage and reload. Layout is presentation state and never enters the session log. +`ui-sidebar-right` keeps one `SurfaceState` per session id — the layout, its history, and the mint counter — in a store declared at the seat registration. Every action mints the ids its intent needs, asks a kit planner for the operations, runs the settle planner over the result, and records the whole intent as one history entry before assigning the session's surface back; no action edits a layout in place. The settle step is the product's rule: a docked pane whose last tab is closed, moved out, or floated is merged away, and an expanded empty root pane receives the current default page. A collapsed surface may remain empty until its next expansion; no separate pane-closing gesture exists. [Layout persistence and provider recovery](../../../../packages/client/ui-sidebar-right/README.md) owns Session-scoped browser storage and reload. Layout is presentation state and never enters the session log. ### Beyond the surface -The surface renders tabs whose bodies it does not know: each tab carries a `kind`, and the panel asks the type registry for the implementation in force and dispatches to its keyed body seat. Everything a body may rely on — its record, its pane, whether it is visible, how it was navigated to, an abort signal, and the actions it may take — is read through the framework-injected `useTabInfo()`. The registry, the navigation face `ctx.sidebarRight`, the seats, and the tab information are specified in [tab types and navigation](../architecture/2026-09-05-sidebar-tab-types-and-navigation.md); a body that shows data reads it through the [client resource model](../architecture/2026-09-05-client-resource-model.md). +The surface renders tabs whose bodies it does not know: each tab carries a `kind`, and the panel asks the type registry for the implementation in force and dispatches to its keyed body seat. Everything a body may rely on — its record, its pane, whether it is visible, how it was navigated to, an abort signal, and the actions it may take — is read through the framework-injected `useTabInfo()`. The registry, the navigation face `ctx.sidebarRight`, the seats, and the tab information are specified in [tab types and navigation](../../../../packages/client/ui-sidebar-right/README.md); a body that shows data reads it through the [client resource model](../architecture/2026-09-05-client-resource-model.md). ### Entry points and removals -`ui-chat`'s `openFile(path, { line? })` — reached by tool-row path links, produced-file chips, and closing-message mentions — now opens the file into the Sidebar through the navigation face (see [tab types and navigation](../architecture/2026-09-05-sidebar-tab-types-and-navigation.md)). The `Show in folder` action and its `canOpenWorkspacePath` probe are removed from `ui-deliverables`: the Sidebar has no directory form, and the product keeps no secondary entry. `DetailsPanel`, `ToolDetails`, the tool-node reader, the chat store's selection, `ToolDetailsProps`, and `CENTER_MIN` are removed. `session/openWorkspacePath` remains on the Host with no web caller. +`ui-chat`'s `openFile(path, { line? })` — reached by tool-row path links, produced-file chips, and closing-message mentions — now opens the file into the Sidebar through the navigation face (see [tab types and navigation](../../../../packages/client/ui-sidebar-right/README.md)). The `Show in folder` action and its `canOpenWorkspacePath` probe are removed from `ui-deliverables`: the Sidebar has no directory form, and the product keeps no secondary entry. `DetailsPanel`, `ToolDetails`, the tool-node reader, the chat store's selection, `ToolDetailsProps`, and `CENTER_MIN` are removed. `session/openWorkspacePath` remains on the Host with no web caller. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md index 4959032b53..57aa63fd4d 100644 --- a/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md +++ b/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md @@ -12,7 +12,7 @@ Agent 产出的文件是最尖锐的案例。产出文件 chip 或 `read` 行的 ## Decision -右列是每会话一份的停靠面(分栏 pane、tab、浮动面板与可撤销的操作序列),由 `ui-sidebar-right` 基于 `ui-dockkit` 引擎持有,取代原来的 Detail 面板。本篇只管这个面:引擎、框架的右列、面板的两种呈现与控件、每会话的状态。面里放什么由别处决定:插件如何声明 tab 类型、打开内容、拿到 props 见[tab 类型与导航](../architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md);地址背后的活数据见[客户端资源模型](../architecture/2026-09-05-client-resource-model.zh.md);读工作区文件见[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md);引导页、文本预览与文件树见[随包类型](2026-09-05-sidebar-text-preview-and-file-tree.zh.md)。 +右列是每会话一份的停靠面(分栏 pane、tab、浮动面板与可撤销的操作序列),由 `ui-sidebar-right` 基于 `ui-dockkit` 引擎持有,取代原来的 Detail 面板。本篇只管这个面:引擎、框架的右列、面板的两种呈现与控件、每会话的状态。面里放什么由别处决定:插件如何声明 tab 类型、打开内容、拿到 props 见[tab 类型与导航](../../../../packages/client/ui-sidebar-right/README.zh.md);地址背后的活数据见[客户端资源模型](../architecture/2026-09-05-client-resource-model.zh.md);读工作区文件见[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md);引导页、文本预览与文件树见[随包类型](../../../../packages/client/ui-sidebar-documentpreview/README.zh.md)。 ### 包拓扑 @@ -33,23 +33,23 @@ Agent 产出的文件是最尖锐的案例。产出文件 chip 或 `read` 行的 ### 框架的右列 -[响应式 Sidebar 与标签信息](../architecture/2026-09-07-sidebar-responsive-tab-info.zh.md)取代本记录中的无让步布局、覆盖模式与产品窗格上限。`ui-layout` 仍拥有三列几何与像素宽度偏好,Sidebar 占位项通过 `ctx.layout.openRightbar(track, fullscreen)` 和 `closeRightbar()` 报告呈现方式,框架不注入 Sidebar 包。具体宽度规则见 [ui-layout](../../../../packages/client/ui-layout/README.zh.md)。 +[响应式 Sidebar 与标签信息](../../../../packages/client/ui-sidebar-right/README.zh.md)取代本记录中的无让步布局、覆盖模式与产品窗格上限。`ui-layout` 仍拥有三列几何与像素宽度偏好,Sidebar 占位项通过 `ctx.layout.openRightbar(track, fullscreen)` 和 `closeRightbar()` 报告呈现方式,框架不注入 Sidebar 包。具体宽度规则见 [ui-layout](../../../../packages/client/ui-layout/README.zh.md)。 右栏在普通与全屏模式下使用同一棵已挂载内容树;隐藏保留已保活的标签状态,全屏覆盖视口并保留底层列占位。`DockLayout` 在同一棵树中渲染稳定 Grid cell 与 fixed 定位的浮动 frame,前台浮窗不随右栏关闭。产品限制为两个水平窗格与 20–80% 分割比例,通用引擎保留自己的默认值。 ### 状态 -[默认页](2026-09-08-sidebar-default-pages.zh.md)取代此处的默认补入引导页;[最后一个 tab 的关闭规则](2026-09-08-sidebar-last-tab-close-rules.zh.md)负责显式关闭,移动 tab 仍会处理被清空的格。 +[Sidebar 引导页规则](../../../../packages/client/ui-sidebar-right/README.zh.md#the-guide)定义默认页选择与显式关闭,移动 tab 仍会处理被清空的格。 -`ui-sidebar-right` 为每个会话 id 保存一份 `SurfaceState`——布局、历史与铸造计数——住在坑位注册时声明的 store 里。每个 action 先铸造意图所需的 id,向库的 planner 索取操作,对结果跑一遍 settle planner,把整个意图记为一条历史账,再把该会话的 surface 整体赋回;没有 action 就地改布局。settle 是产品规则:最后一个 tab 被关闭、拖走或悬浮出去的停靠 pane 会被合并掉;展开且为空的根 pane 会填入当前默认页。折叠的布局可以保持为空,直到下次展开;没有单独的关闭 pane 手势。[布局持久化与 provider 恢复](../architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md)负责 Session 作用域的浏览器存储和刷新。布局是呈现状态,永不进入会话日志。 +`ui-sidebar-right` 为每个会话 id 保存一份 `SurfaceState`——布局、历史与铸造计数——住在坑位注册时声明的 store 里。每个 action 先铸造意图所需的 id,向库的 planner 索取操作,对结果跑一遍 settle planner,把整个意图记为一条历史账,再把该会话的 surface 整体赋回;没有 action 就地改布局。settle 是产品规则:最后一个 tab 被关闭、拖走或悬浮出去的停靠 pane 会被合并掉;展开且为空的根 pane 会填入当前默认页。折叠的布局可以保持为空,直到下次展开;没有单独的关闭 pane 手势。[布局持久化与 provider 恢复](../../../../packages/client/ui-sidebar-right/README.zh.md)负责 Session 作用域的浏览器存储和刷新。布局是呈现状态,永不进入会话日志。 ### 面之外 -这个面渲染的 tab 正文它自己并不认识:每个 tab 带一个 `kind`,面板向类型注册表询问该 kind 生效的实现,再派发到其 keyed 正文坑位。正文能依赖的一切——它的记录、所在格、是否可见、如何被导航到、中止信号、可做的动作——均通过框架注入的 `useTabInfo()` 从标签域读取。注册表、导航面 `ctx.sidebarRight`、坑位与 标签信息 在[tab 类型与导航](../architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md)里定;展示数据的正文经[客户端资源模型](../architecture/2026-09-05-client-resource-model.zh.md)读取。 +这个面渲染的 tab 正文它自己并不认识:每个 tab 带一个 `kind`,面板向类型注册表询问该 kind 生效的实现,再派发到其 keyed 正文坑位。正文能依赖的一切——它的记录、所在格、是否可见、如何被导航到、中止信号、可做的动作——均通过框架注入的 `useTabInfo()` 从标签域读取。注册表、导航面 `ctx.sidebarRight`、坑位与 标签信息 在[tab 类型与导航](../../../../packages/client/ui-sidebar-right/README.zh.md)里定;展示数据的正文经[客户端资源模型](../architecture/2026-09-05-client-resource-model.zh.md)读取。 ### 入口与删除 -`ui-chat` 的 `openFile(path, { line? })`——工具行路径链接、产出文件 chip 与收尾消息提及都经由它——现在经导航面把文件开进 Sidebar(见[tab 类型与导航](../architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md))。`Show in folder` 动作及其 `canOpenWorkspacePath` 探针从 `ui-deliverables` 移除:Sidebar 没有目录形态,产品也不保留次级入口。`DetailsPanel`、`ToolDetails`、tool-node reader、chat store 的 selection、`ToolDetailsProps` 与 `CENTER_MIN` 一并删除。`session/openWorkspacePath` 留在 Host 上,已无 web 调用方。 +`ui-chat` 的 `openFile(path, { line? })`——工具行路径链接、产出文件 chip 与收尾消息提及都经由它——现在经导航面把文件开进 Sidebar(见[tab 类型与导航](../../../../packages/client/ui-sidebar-right/README.zh.md))。`Show in folder` 动作及其 `canOpenWorkspacePath` 探针从 `ui-deliverables` 移除:Sidebar 没有目录形态,产品也不保留次级入口。`DetailsPanel`、`ToolDetails`、tool-node reader、chat store 的 selection、`ToolDetailsProps` 与 `CENTER_MIN` 一并删除。`session/openWorkspacePath` 留在 Host 上,已无 web 调用方。 ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml b/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml deleted file mode 100644 index 7c077c2da7..0000000000 --- a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-sidebar-text-preview-and-file-tree.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md -/agent-note-sidebar-text-preview-and-file-tree: - en: 2f9ef6cc50ad825d - zh: 5ec8592ac9963f85 -/agent-note-sidebar-text-preview-and-file-tree/problem: - en: 4e647765dfcd2ef9 - zh: 2c362ffdee7ff3d4 -/agent-note-sidebar-text-preview-and-file-tree/decision: - en: f8ea6a52a404b064 - zh: c91528d66487bb33 -/agent-note-sidebar-text-preview-and-file-tree/decision/the-guide: - en: cc4eaeefe4de9a02 - zh: bc9e727d6316ae88 -/agent-note-sidebar-text-preview-and-file-tree/decision/the-text-preview: - en: efc84d800d52a69e - zh: 0061138d7cd12ae7 -/agent-note-sidebar-text-preview-and-file-tree/decision/the-file-tree: - en: f03013eee6c293de - zh: 54796ac3da91047f -/agent-note-sidebar-text-preview-and-file-tree/alternatives-considered: - en: 87fddbd01f117061 - zh: 1810d283202db648 -/agent-note-sidebar-text-preview-and-file-tree/consequences: - en: 00b1675fcd76ad0d - zh: caf3eb340c74aef7 -/agent-note-sidebar-text-preview-and-file-tree/testing: - en: 2987a6417b7e4c14 - zh: 97eeb97e67536c70 -/agent-note-sidebar-text-preview-and-file-tree/deferred: - en: 6c788b26fb65227b - zh: 1bafda76bac92edd -/agent-note-sidebar-text-preview-and-file-tree/related: - en: 0a2531b19968c69b - zh: 4065017075aa11cc diff --git a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.i18n.yaml b/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.i18n.yaml deleted file mode 100644 index b54791c599..0000000000 --- a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-composer-session-stats-pills.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.md -/agent-note-composer-session-stats-two-icon-pills-with-click-open-stat-dialogs: - en: 76e613674da56884 - zh: 4c9c5d750eace3e0 -/agent-note-composer-session-stats-two-icon-pills-with-click-open-stat-dialogs/problem: - en: 654462fa4eeb58dc - zh: 7c9cd57947ab8639 -/agent-note-composer-session-stats-two-icon-pills-with-click-open-stat-dialogs/decision: - en: 27eb1718cd80b39b - zh: e39576842a3578c1 -/agent-note-composer-session-stats-two-icon-pills-with-click-open-stat-dialogs/alternatives-considered: - en: 7798b1fb9824b920 - zh: 7e75cd154ab58a0b -/agent-note-composer-session-stats-two-icon-pills-with-click-open-stat-dialogs/consequences: - en: c078fb870536eec6 - zh: 17076d824c74ee9d diff --git a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.md b/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.md deleted file mode 100644 index 50d3d3294e..0000000000 --- a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: Composer session stats — two icon pills with click-open stat dialogs - -Status: implemented - -English | [中文](2026-09-07-composer-session-stats-pills.zh.md) - -## Problem - -The session stats strip under the composer (`StatsLine`, ui-chat, mounted on `conversation.composer.dock`) rendered every figure as one resident text line: turn/step counts, LLM and tool wall times, TTFT/TPS averages, and compact token totals with cache-hit share. The line crowded as figures accumulated, exact token counts appeared nowhere (the `ResizeObserver`-measured hover tooltip only restated the same compact line when it truncated), and the flat text gave no grouping — time figures and billing figures read as one undifferentiated row. An in-page A/B against a two-pill variant settled the direction: the pills won on scannability and on giving each figure family a home. - -## Decision - -The [performance and usage preference](2026-09-16-performance-usage-preference.md) governs statistic visibility and removes the completed-turn elapsed-time action. - -`StatsPills` (packages/client/ui-chat/src/client/chat/StatsPills.tsx) replaces `StatsLine` on the same `conversation.composer.dock` slot; the losing variant is deleted, its shared helpers (`deriveStats`, `formatDuration`, `cacheHitPercent`, `billedInputTokens`) absorbed into the new module, and the dead `stats.llm`, `stats.toolCall`, `stats.ttftAverage`, `stats.tokensPerSecond`, and `stats.tokens` locale keys removed. - -- **Two icon pills, two dialogs.** A gauge pill (new `IconGaugeOutlineRegular`, dial center optically dropped to y=8.75 because the bottom-open arc reads high) shows `{turns} 轮 {steps} 步` plus output TPS and click-opens the 会话统计 dialog (LLM time, tool time, average TTFT, TPS); a log with no timed figure would open an empty dialog, so that pill renders as a static reading instead of a button. A database pill (`IconDatabaseOutlineRegular`) shows the compact billed total plus cache-hit share and click-opens the Token 用量 dialog (cache hit, uncached input, cache read, output, and cache write when non-zero — exact counts). Both dialogs wear the shared `stat-dialog` module (portal panel, anchored placement, outside-dismiss, optionally externally owned open state) extracted for exactly this two-consumer split; the pills row owns one exclusive open slot, so opening either dialog closes the other, and each button carries an explicit `aria-label` that separates with ` · ` the segments the aria-hidden sep glyph joins visually. [StatsPills](../../../../packages/client/ui-chat/src/client/chat/StatsPills.tsx) owns the zero cache-write omission. -- **Shared information tier.** Composer stats and per-turn tail metadata use the secondary font size minus one pixel. Assistant-tail metadata uses the action icons' tertiary label color and starts after an extra 8px separation from the action cluster. -- **Data sourcing is unchanged in architecture.** Counts and times prefer the durable `sessionStats` projection with the window fold as the assembly-without-the-unit fallback ([whole-session counts](../../archived/bug-fix/2026-08-12-full-session-turn-step-counts.md)); token figures ride `tokenUsage` only, so an absent projection drops the usage pill rather than showing window-derived billing. Cache writes stay in the billed total and the cache-hit denominator ([projection decision](../architecture/2026-07-29-projected-token-usage-and-request-context.md)). Context occupancy remains owned by ui-conversation's `ContextMeter`, with its ring and percentage after the two stats pills below the input card. The common dock groups statistics together and leaves the toolbar for input actions; ui-chat does not import the context component. The context panel renders through a portal and uses ui-primitives for viewport-clamped positioning and outside-pointer dismissal, including when the statistics contribution is absent. -- **Render discipline.** The row folds settled nodes only (`chat.legacy.nodes` identity), so streaming chunk frames cause zero rerenders — pinned by a render-count unit test. A session with no closed step and no billed tokens renders nothing. -- **The composer owns dock spacing.** `InputBar` places slot contributions and `ContextMeter` in one centered flex row, supplies 4px above the dock and 4px below it even when the slot has no visible contribution. The hero keeps no bottom padding and hides its empty dock. `StatsPills` supplies shrinkable time and billing content; it does not claim the full row width or add outer padding. - -## Alternatives considered - -- **The single-line variant (StatsLine, the A/B loser).** All figures resident in one text row, with a hover tooltip restating the full line when it truncated. Lost on crowding and reach: exact token counts appeared nowhere (the line and its tooltip both carried compact totals only), and one row gave time and billing figures no visual grouping. -- **Three resident groups with one shared dialog.** An intermediate iteration kept counts, time, and tokens as three inline groups. Two pills won because the time/usage split matches the two underlying projections one-to-one and each pill's icon telegraphs its dialog. -- **Extracting the dl bucket rows shared with `TurnUsagePanel`.** The session-total dialog and the per-turn panel render the same skin but different contracts (required session input, cache-read, and output rows plus a non-zero cache-write row, versus optional per-turn fields and model routes); a shared component would be conditionals around nine lines. The mirror is marked `jscpd:ignore` with the reason inline. - -## Consequences - -- `ChatSnapshotBuilder`'s legacy slice now serves StatsPills; the [node-assembly note](../architecture/2026-08-09-client-conversation-node-assembly.md) tracks that consumer rename. -- Exact token counts become reachable at all — one click — where `StatsLine` showed only compact totals; the strip itself carries only the two headline readings. -- Web e2e strip assertions match substring text inside the time pill; the fresh-round-trip aria goldens pin time, billing, and context order below the submit action, and stats-paged-history pins the counts reading alone over a log with no billed tokens. -- The `conversation.composer.dock` occupant in the generated slot catalog is `client-ui-chat StatsPills id 'stats'`. diff --git a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.zh.md b/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.zh.md deleted file mode 100644 index 97b91b601e..0000000000 --- a/.agents/notes/implemented/feature/2026-09-07-composer-session-stats-pills.zh.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: 输入框下的会话统计 —— 双图标 pill 与点击展开的统计弹层 - -Status: implemented - -[English](2026-09-07-composer-session-stats-pills.md) | 中文 - -## 问题 - -输入框下方的会话统计条(`StatsLine`,ui-chat,挂载于 `conversation.composer.dock`)把所有数字渲染成一整行常驻文本:轮/步计数、模型与工具用时、TTFT/TPS 均值、紧凑 token 总量与缓存命中率。数字越多行越拥挤,精确 token 计数无处可看(带 `ResizeObserver` 溢出测量的悬停提示只在截断时复述同一行紧凑文本),纯文本也没有分组——时间类数字和计费类数字读起来混作一行。一次页面内 A/B 对比双 pill 变体后方向定案:pill 方案在可扫读性和"每类数字有归属"上胜出。 - -## 决定 - -[性能与用量偏好](2026-09-16-performance-usage-preference.zh.md)管理统计可见性,并移除已完成轮次的耗时操作。 - -`StatsPills`(packages/client/ui-chat/src/client/chat/StatsPills.tsx)在同一 `conversation.composer.dock` 插槽上取代 `StatsLine`;落选变体已删除,其共享工具函数(`deriveStats`、`formatDuration`、`cacheHitPercent`、`billedInputTokens`)并入新模块,废弃的 `stats.llm`、`stats.toolCall`、`stats.ttftAverage`、`stats.tokensPerSecond`、`stats.tokens` 文案键一并移除。 - -- **两个图标 pill、两个弹层。** 仪表盘 pill(新增 `IconGaugeOutlineRegular`,因下开口圆弧视觉偏高而把表盘中心光学下移到 y=8.75)展示 `{turns} 轮 {steps} 步` 加输出 TPS,点击打开「会话统计」弹层(模型用时、工具调用用时、首 token 平均、输出速度);日志里没有任何计时数字时弹层会是空的,此时该 pill 渲染为静态读数而非按钮。数据库 pill(`IconDatabaseOutlineRegular`)展示紧凑计费总量加缓存命中率,点击打开「Token 用量」弹层(缓存命中、未缓存输入、缓存读取、输出,以及非零时的缓存写入——精确计数)。两个弹层共用为这两处消费者抽出的 `stat-dialog` 模块(portal 面板、锚定定位、点击外部关闭、可选的外部持有开合状态);pill 行持有唯一的互斥开合槽位,打开任一弹层即关闭另一个,且每个按钮携带显式 `aria-label`,用 ` · ` 分隔 aria-hidden 分隔符在视觉上连接的两段文本。[StatsPills](../../../../packages/client/ui-chat/src/client/chat/StatsPills.tsx)负责缓存写入为零时省略该行的规则。 -- **统一信息层级。** 输入框底部统计与单轮回答末尾信息都使用次级字号减 1px。回答末尾信息使用与操作图标一致的 tertiary 文字颜色,并与操作图标组额外拉开 8px。 -- **数据来源架构不变。** 计数与用时优先读取持久的 `sessionStats` 投影,窗口折叠仅作无该单元装配时的回退([全会话计数](../../archived/bug-fix/2026-08-12-full-session-turn-step-counts.md));token 数字只走 `tokenUsage`,投影缺席时直接不渲染用量 pill,而非展示窗口推算的计费。缓存写入仍计入计费总量与缓存命中分母([投影决定](../architecture/2026-07-29-projected-token-usage-and-request-context.zh.md))。上下文占用仍归 ui-conversation 的 `ContextMeter` 所有,在输入卡片下方、两个统计 pill 之后显示圆环和百分比。共用 dock 将统计信息放在一起,工具栏保留给输入操作;ui-chat 不导入上下文组件。上下文面板通过 portal 渲染,复用 ui-primitives 的视口内定位与外部指针关闭工具,统计贡献项缺席时也不会越界。 -- **渲染纪律。** 该行只折叠已定稿节点(`chat.legacy.nodes` 身份),流式 chunk 帧零重渲染——由渲染计数单测钉住。无已完成步且无计费 token 的会话什么都不渲染。 -- **输入框负责 dock 间距。** `InputBar` 将 slot 贡献项和 `ContextMeter` 放在同一个居中的 flex 行中,在 dock 上下各提供 4px 留白,即使 slot 没有可见贡献项也保持该间距。hero 保持无底部留白,并隐藏空 dock。`StatsPills` 提供可收缩的时间与计费内容,不占满整行宽度,也不添加外部 padding。 - -## 备选方案 - -- **单行变体(StatsLine,A/B 落选方)。** 全部数字常驻一行文本,悬停提示只在截断时复述整行。败在拥挤与可达性:精确 token 计数无处可看(行内和提示里都只有紧凑总量),单行也无法给时间与计费数字分组。 -- **三个常驻分组共享一个弹层。** 中间迭代曾把计数、时间、token 保持为三个内联分组。双 pill 胜出是因为时间/用量的切分与两个底层投影一一对应,且每个 pill 的图标直接预告其弹层内容。 -- **抽出与 `TurnUsagePanel` 共享的 dl 分桶行。** 会话总量弹层与逐轮面板皮肤相同但约定不同(会话输入、缓存读取与输出行始终存在,缓存写入行仅在非零时出现;逐轮字段可选且含模型路由);共享组件只是给九行代码包一层条件。该镜像以 `jscpd:ignore` 标注并内联说明理由。 - -## 后果 - -- `ChatSnapshotBuilder` 的 legacy 切片现在服务于 StatsPills;[节点装配 note](../architecture/2026-08-09-client-conversation-node-assembly.zh.md) 已跟进该消费者更名。 -- 精确 token 计数第一次变得可达——一次点击即可;`StatsLine` 只展示过紧凑总量。统计条本身只承载两个头条读数。 -- Web e2e 对统计条的断言匹配时间 pill 内的子串文本;fresh-round-trip 的 aria golden 钉住提交操作下方的时间、计费、上下文顺序,stats-paged-history 则在无计费 token 的日志上钉住单独的计数读数。 -- 生成的插槽目录中 `conversation.composer.dock` 占用者为 `client-ui-chat StatsPills id 'stats'`。 diff --git a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml index 65061d8855..cc1c2f1695 100644 --- a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.i18n.yaml @@ -9,11 +9,11 @@ en: 6c9d47e829752afe zh: 98bd3b5300bfea8c /agent-note-deepseek-through-the-anthropic-messages-protocol/decision: - en: 3c28330671a1e6db - zh: 0d7ef9a8cf22c51f + en: a1499bb2fe6514ce + zh: f67e0acbabf97f1d /agent-note-deepseek-through-the-anthropic-messages-protocol/alternatives-considered: en: 4297bc439e8b23e4 zh: 2bb4dec78040d81e /agent-note-deepseek-through-the-anthropic-messages-protocol/consequences: - en: 76aa9259724323d2 - zh: 0aac83847bbd4ab6 + en: 6a1044aba27f62a6 + zh: 032ffa849afb8a30 diff --git a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md index 29f712e57c..d2d1ded714 100644 --- a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md +++ b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.md @@ -10,13 +10,13 @@ Messages represents thinking, signatures, tool calls, tool results, and cumulati ## Decision -The [DeepSeek adapter](../../../../packages/llm/llm-deepseek/README.md) serves Messages under one `deepseek-official` route and `llm-deepseek` settings namespace. The [single-protocol decision](../simplification/2026-09-19-deepseek-messages-only.md) owns the transport scope. `PreparedAdapterCall` freezes the endpoint, credential reference, and model capabilities; retries retain that generation while subsequent calls read new configuration. +The [DeepSeek adapter](../../../../packages/llm/llm-deepseek/README.md) serves Messages under one `deepseek-official` route and `llm-deepseek` settings namespace. The [single-protocol reference](../../../../packages/llm/llm-deepseek/README.md) owns the transport scope. `PreparedAdapterCall` freezes the endpoint, credential reference, and model capabilities; retries retain that generation while subsequent calls read new configuration. The adapter follows the [DeepSeek compatibility documentation](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) and [Anthropic streaming protocol](https://platform.claude.com/docs/en/build-with-claude/streaming). The pi-ai Anthropic implementation informed the handling of adjacent user messages, cumulative usage, fragmented tool arguments, and optional thinking signatures. DeepSeek effort uses `output_config.effort`; an Anthropic thinking token budget does not control DeepSeek effort. The adapter forwards explicit `temperature` values; DeepSeek accepts that parameter with thinking enabled and ignores its value, so callers retain their existing thinking configuration. -Assistant blocks remain the durable model-visible content. A versioned `ReplayEnvelope` stores only the protocol format, model identity, aligned block kinds, and signatures absent from those blocks. Same-model Messages continuation restores signatures verbatim, including empty signatures; foreign history carries no invented signature. Unusable metadata follows the existing [replay degradation rule](../architecture/2026-07-14-provider-routed-llm-adapters.md): the request omits signatures with a warning while preserving durable content; historical tool arguments use the [empty-input fallback](../bug-fix/2026-09-16-messages-historical-tool-input.md) when Messages cannot represent them. This keeps provider replay data opaque to the loop while preserving it through Session persistence and block pruning. +Assistant blocks remain the durable model-visible content. A versioned `ReplayEnvelope` stores only the protocol format, model identity, aligned block kinds, and signatures absent from those blocks. Same-model Messages continuation restores signatures verbatim, including empty signatures; foreign history carries no invented signature. Unusable metadata follows the existing [replay degradation rule](../architecture/2026-07-14-provider-routed-llm-adapters.md): the request omits signatures with a warning while preserving durable content; historical tool arguments use the [empty-input fallback](../../../../packages/llm/llm-deepseek/README.md) when Messages cannot represent them. This keeps provider replay data opaque to the loop while preserving it through Session persistence and block pruning. -The adapter prefers Files references for deterministic request images, with upload caching, refresh, quota recovery, and attachment offload. The Files client follows the [exact `/v1` root rule](../bug-fix/2026-09-15-messages-v1-base-url.md) and sends the required beta header. Cached ids remain scoped by the resolved Files root and credential, so equivalent `/v1` and unversioned roots share uploads. Metadata omits expiry, so local reuse is bounded from the original upload time without asserting remote deletion. A Files-resolution failure rebuilds the complete request under the independent inline-image budget; caller cancellation stops it. The image policy preserves the 128 MiB retained-image budget, 20 MiB inline base64 budget, and oldest-prefix offload in both requests and token measurement. +The adapter prefers Files references for deterministic request images, with upload caching, refresh, quota recovery, and attachment offload. The Files client follows the [exact `/v1` root rule](../../../../packages/llm/llm-deepseek/README.md) and sends the required beta header. Cached ids remain scoped by the resolved Files root and credential, so equivalent `/v1` and unversioned roots share uploads. Metadata omits expiry, so local reuse is bounded from the original upload time without asserting remote deletion. A Files-resolution failure rebuilds the complete request under the independent inline-image budget; caller cancellation stops it. The image policy preserves the 128 MiB retained-image budget, 20 MiB inline base64 budget, and oldest-prefix offload in both requests and token measurement. JSON syntax failure after a successful HTTP response does not establish a transport failure. Files decoding uses the existing `INVALID_RESPONSE` code with operation context instead of relabeling the failure as `TRANSPORT` or adding a retry; the [provider README](../../../../packages/llm/llm-deepseek/README.md) owns the error fields. @@ -38,6 +38,6 @@ The adapter uses the existing [request-extension registry](../architecture/2026- ## Consequences -The package owns wire validation, stop-reason mapping, cancellation, and error classification, so protocol changes require adapter maintenance. User and tool-result input follows the [saved-input compatibility rule](../bug-fix/2026-09-18-messages-input-history-compatibility.md); other unsupported content and incomplete streams fail explicitly. The existing retry consumer owns retries; the existing assembler drops incomplete tool calls at the output limit. Messages serves the shared base, Web, and standalone first-party compositions. Explicit endpoint overrides must support Messages. +The package owns wire validation, stop-reason mapping, cancellation, and error classification, so protocol changes require adapter maintenance. User and tool-result input follows the [saved-input compatibility rule](../../../../packages/llm/llm-deepseek/README.md); other unsupported content and incomplete streams fail explicitly. The existing retry consumer owns retries; the existing assembler drops incomplete tool calls at the output limit. Messages serves the shared base, Web, and standalone first-party compositions. Explicit endpoint overrides must support Messages. Verification covers wire fixtures, real Loader composition, per-file unit coverage, [recorded Session replay](../../../../snapshots/session/deepseek-messages-replay/snapshot.yml) with [unknown replay versions](../../../../snapshots/session/deepseek-messages-degraded-replay/snapshot.yml), a Web Messages Session replay, and credential-gated text, thinking, tool continuation, image, and cancellation requests. Live gateway checks establish compatibility with the configured gateway; they do not establish compatibility with every Anthropic proxy. diff --git a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md index 3cf60fc210..3f5251b645 100644 --- a/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md +++ b/.agents/notes/implemented/feature/2026-09-07-deepseek-messages-adapter.zh.md @@ -10,13 +10,13 @@ Messages 以原生协议字段表示思考、签名、工具调用、工具结 ## 决策 -[DeepSeek 适配器](../../../../packages/llm/llm-deepseek/README.zh.md)在唯一的 `deepseek-official` 路由和 `llm-deepseek` 设置命名空间下提供 Messages。[单协议决策](../simplification/2026-09-19-deepseek-messages-only.zh.md)拥有传输范围。`PreparedAdapterCall` 固定端点、凭据引用与模型能力;重试保留该代配置,后续调用读取新配置。 +[DeepSeek 适配器](../../../../packages/llm/llm-deepseek/README.zh.md)在唯一的 `deepseek-official` 路由和 `llm-deepseek` 设置命名空间下提供 Messages。[单协议说明](../../../../packages/llm/llm-deepseek/README.zh.md)拥有传输范围。`PreparedAdapterCall` 固定端点、凭据引用与模型能力;重试保留该代配置,后续调用读取新配置。 适配器遵循 [DeepSeek 兼容文档](https://api-docs.deepseek.com/zh-cn/guides/anthropic_api) 和 [Anthropic 流协议](https://platform.claude.com/docs/en/build-with-claude/streaming)。pi-ai 的 Anthropic 实现为相邻用户消息、累计用量、工具参数分片和可选思考签名的处理提供参考。DeepSeek 通过 `output_config.effort` 设置思考强度;Anthropic 思考 token 预算不控制 DeepSeek 思考强度。适配器转发显式 `temperature` 值;DeepSeek 在启用思考时接受该参数但忽略其值,因此调用方可以保留已有思考配置。 -助手内容块保留持久化的模型可见内容。带版本的 `ReplayEnvelope` 仅保存协议格式、模型标识、对齐的块类型以及内容块未包含的签名。同模型续接原样恢复签名,包括空签名;外部历史不生成虚构签名。不可用的元数据遵循现有[回放降级规则](../architecture/2026-07-14-provider-routed-llm-adapters.zh.md):请求省略签名并记录警告,保留持久化内容;Messages 无法表示历史工具参数时使用[空输入兜底](../bug-fix/2026-09-16-messages-historical-tool-input.zh.md)。提供者回放数据对循环保持不透明,同时能够随 Session 持久化和内容块裁剪保留。 +助手内容块保留持久化的模型可见内容。带版本的 `ReplayEnvelope` 仅保存协议格式、模型标识、对齐的块类型以及内容块未包含的签名。同模型续接原样恢复签名,包括空签名;外部历史不生成虚构签名。不可用的元数据遵循现有[回放降级规则](../architecture/2026-07-14-provider-routed-llm-adapters.zh.md):请求省略签名并记录警告,保留持久化内容;Messages 无法表示历史工具参数时使用[空输入兜底](../../../../packages/llm/llm-deepseek/README.zh.md)。提供者回放数据对循环保持不透明,同时能够随 Session 持久化和内容块裁剪保留。 -适配器优先使用确定性请求图片的 Files 引用,并管理上传缓存、刷新、配额恢复与附件省略。Files 客户端遵循[精确 `/v1` 根地址规则](../bug-fix/2026-09-15-messages-v1-base-url.zh.md)并发送必需的 beta 标头。缓存 id 按解析后的 Files 根地址与凭据限定作用域,因此等价的 `/v1` 与无版本根地址共用上传。元数据不含过期时间,因此本地复用以原始上传时间为界,不断言远端删除。Files 解析失败会按独立内联图片预算重建完整请求;调用方取消会停止它。图片策略在请求和 token 计量中保留 128 MiB 保留图片预算、20 MiB 内联 base64 预算及最旧前缀省略。 +适配器优先使用确定性请求图片的 Files 引用,并管理上传缓存、刷新、配额恢复与附件省略。Files 客户端遵循[精确 `/v1` 根地址规则](../../../../packages/llm/llm-deepseek/README.zh.md)并发送必需的 beta 标头。缓存 id 按解析后的 Files 根地址与凭据限定作用域,因此等价的 `/v1` 与无版本根地址共用上传。元数据不含过期时间,因此本地复用以原始上传时间为界,不断言远端删除。Files 解析失败会按独立内联图片预算重建完整请求;调用方取消会停止它。图片策略在请求和 token 计量中保留 128 MiB 保留图片预算、20 MiB 内联 base64 预算及最旧前缀省略。 成功 HTTP 响应之后的 JSON 语法错误不足以证明传输故障。Files 解码复用已有的 `INVALID_RESPONSE` code 并补充操作上下文,不将其改标为 `TRANSPORT` 或添加重试;错误字段由 [provider README](../../../../packages/llm/llm-deepseek/README.zh.md) 维护。 @@ -38,6 +38,6 @@ Web 显示 DeepSeek,并提供端点与凭据引用。没有端点覆盖时, ## 结果 -该包负责协议校验、停止原因映射、取消和错误分类,因此协议变化需要维护适配器。用户与工具结果输入遵循[已保存输入兼容规则](../bug-fix/2026-09-18-messages-input-history-compatibility.zh.md);其他不支持的内容和不完整的流会明确报错。现有重试消费者负责重试;现有装配器在输出达到上限时丢弃未完成的工具调用。Messages 服务共享 base、Web 和独立的官方组合。显式端点覆盖必须支持 Messages。 +该包负责协议校验、停止原因映射、取消和错误分类,因此协议变化需要维护适配器。用户与工具结果输入遵循[已保存输入兼容规则](../../../../packages/llm/llm-deepseek/README.zh.md);其他不支持的内容和不完整的流会明确报错。现有重试消费者负责重试;现有装配器在输出达到上限时丢弃未完成的工具调用。Messages 服务共享 base、Web 和独立的官方组合。显式端点覆盖必须支持 Messages。 验证覆盖协议夹具、真实 Loader 组合、逐文件单元覆盖率、[已记录 Session 回放](../../../../snapshots/session/deepseek-messages-replay/snapshot.yml)与[未知回放版本](../../../../snapshots/session/deepseek-messages-degraded-replay/snapshot.yml),Web Messages Session 回放,以及凭证控制的文本、思考、工具续接、图片和取消请求。真实网关检查证明与已配置网关的兼容性,不能证明与所有 Anthropic 代理兼容。 diff --git a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.i18n.yaml b/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.i18n.yaml deleted file mode 100644 index d1cc740eac..0000000000 --- a/.agents/notes/implemented/feature/2026-09-07-model-switch-notice.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-model-switch-notice.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-07-model-switch-notice.md -/agent-note-model-visible-route-change-notices: - en: 8e372ee5814c99d1 - zh: 3fd0faa18fc02f75 -/agent-note-model-visible-route-change-notices/problem: - en: 0c0be027f271e132 - zh: c4f6aa9217633f43 -/agent-note-model-visible-route-change-notices/decision: - en: 86dbb17229ae9634 - zh: 6b0af5f12441461c -/agent-note-model-visible-route-change-notices/alternatives-considered: - en: 9e8ac73976d39c99 - zh: e637370a5f2b0de8 -/agent-note-model-visible-route-change-notices/consequences: - en: 93976aae03285052 - zh: 27791c4f1f36dc92 diff --git a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.i18n.yaml b/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.i18n.yaml deleted file mode 100644 index ddf9d83bdd..0000000000 --- a/.agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-session-prose-local-media-display.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-07-session-prose-local-media-display.md -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route: - en: 58736e33d45c5578 - zh: 224ab6304833ce7f -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route/problem: - en: 934cbf480461e9e6 - zh: 986d53c72b94cfe4 -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route/decision: - en: 37e189446dd25601 - zh: 0321b091e2250650 -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route/alternatives-considered: - en: b184da259c41e4de - zh: 5c75e50fa595a30e -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route/consequences: - en: 9929a8ad123b21b0 - zh: ac5bf7f3a317d180 -/agent-note-session-prose-local-media-paths-display-through-a-same-origin-file-route/testing: - en: 3c29c3c506a993b2 - zh: 2dfac506ae2ff93a diff --git a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.i18n.yaml deleted file mode 100644 index 9956298fd4..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-composer-menu-sections-and-localized-rows.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.md -/agent-note-composer-menu-sections-localized-rows-and-the-file-row: - en: c917d19351c56a78 - zh: f443c8b88e4f71dd -/agent-note-composer-menu-sections-localized-rows-and-the-file-row/problem: - en: 3047bf5dd9f33e93 - zh: 34b017add5329b49 -/agent-note-composer-menu-sections-localized-rows-and-the-file-row/decision: - en: 7f9b8265d826561e - zh: e2eca2e3f58d296f -/agent-note-composer-menu-sections-localized-rows-and-the-file-row/alternatives-considered: - en: a7806a39ab7a1bac - zh: 421815a13d53e306 -/agent-note-composer-menu-sections-localized-rows-and-the-file-row/consequences: - en: 8d4daba9e67cdfaa - zh: 6ef43f58f9abf3bc diff --git a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.md b/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.md deleted file mode 100644 index 50b97ce2c7..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Note: Composer menu sections, localized rows, and the File row - -Status: implemented - -English | [中文](2026-09-08-composer-menu-sections-and-localized-rows.zh.md) - -## Problem - -The composer's `+` button and a typed `/` listed every command in Host registration order as `name description`, all lowercase, with no glyphs and no grouping, beside a separate paperclip button for files. Under Chinese the rows stayed English because Host descriptors carry English text only, and a user who knew a command by its Chinese title could neither find it by that title nor see what to type. Issue #3567 and the design doc for it ask for two sections in usage order, a glyph and a left-aligned title per row with the description right-aligned, capitalized English titles, Chinese titles and descriptions that stay searchable in both languages and show the English command name, a Chinese fill for the Plan and Goal claims, and a File entry inside the menu. - -## Decision - -`ui-commands` owns the menu's presentation in `src/client/presentation.ts`: an Add section (`file`, `goal`, `plan`, `feedback`) and a Commands section (`compact`, `permission`, `model`, `export`), each in usage order, with rows outside both lists closing Commands in catalog order. The section lists are keyed by name; an empty query returns the sectioned rows, and a typed query returns the flat ranking of every visible row so the best match is always first. - -The six built-in Host commands get their localized title, description, glyph, and claim token from the client. Stable identity selection and input resolution follow the [command identity decision](../architecture/2026-09-10-command-identities-and-composer-file-action.md). Contributions carry `label()`, `description()`, and `icon`, read on every candidate pass, so the `/model` row localizes without re-registration. - -A menu pick fills the locale's claim token: under Chinese, picking Plan fills `/计划 ` and the submission executes `/plan `. A typed token keeps its typed spelling as the claim, because the composer reads the arguments after the token it holds. The effective Session catalog resolves Chinese and English aliases through the same input path in every locale. - -The File row is an `action` contribution: a bare invocation consumes the trigger token and runs a client callback without submitting a message. Conversation owns that registration, its live availability, and the hidden file input. The menu replaces the separate paperclip button; the `+` button's accessible name and tooltip read "Add files or run commands". - -`ui-input-trigger` renders the new row anatomy: `InputTriggerCandidate.label` is the title and a second search key of the shared `rankByName`, the name renders as a trailing alias when the label differs from it, `icon` accepts an icon component beside the reference glyph tokens, and the description is right-aligned. Aliases and descriptions use a font one pixel below the primary labels. `ui-primitives` gains the Plan glyph from the design doc, a static ring for Compact, and the permission shield contour. - -The menu uses a 400 px border-box height cap, which fits both headings and the eight built-in rows before the viewport clamp reduces it. A real overflow keeps a 6 px draggable WebKit rail around a 2 px visible thumb, insets the track from the rounded ends, and shows a bottom fade until the viewport reaches the final row; Firefox keeps its standard thin scrollbar. - -## Alternatives considered - -**Release a claim when its separator is deleted.** The complete command name still identifies the selected command. Keeping the claim until the name changes preserves its highlight through argument replacement and avoids relying on an IME-generated space to run ordinary keydown adjudication. The shared input machine applies this rule to every command token, including failure recovery; neither the command name nor the locale selects a separate implementation. - -**Restore placeholders directly on native composition end.** Browsers can deliver that event before Lexical reconciles the final text. The shared editor binding keeps command hints and ordinary placeholders hidden while either native or editor composition remains active, and reevaluates visibility after an editor commit, including a cancellation that changes no text. Keyboard submit guards retain their separate post-composition window. - -**Localize descriptions on the Host.** The Host has no locale and its catalog is shared by every client; localized product copy belongs to the client. - -**Keep sections under a typed query.** Ranking inside sections put a prefix hit in Commands below weaker matches in Add (typing `e` listed Feedback and File above Export); a flat ranking keeps the best match first, and the headings only carry information while the list is complete. - -**Keep the paperclip beside the menu.** The design doc and the issue's acceptance criteria integrate the entry into the menu and forbid the old icon from showing twice; the hidden file input and the drop gate stay where they were. - -**A contribution-owned copy for the built-in Host rows.** Host packages have no client half to register from, so the copy has to live on the client; one table keyed by name in the package that already owns the `/` source keeps the design decision in one place. - -**A `token` alias only under the active locale.** Resolving through every dictionary costs nothing and lets a draft persisted under one locale submit under another. - -## Consequences - -The menu and localized presentation remain client-owned, while the Host owns effective command definitions. Adding a localized first-party command requires its identity mapping, dictionary entries, icon, and menu position. Session-driven browser scenarios and owner-local ARIA expectations cover the menu. Unrelated same-name definitions keep their own copy and no first-party glyph. The command identity decision owns this distinction and the File action's lifecycle. diff --git a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.zh.md b/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.zh.md deleted file mode 100644 index 92bcb04bfe..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-composer-menu-sections-and-localized-rows.zh.md +++ /dev/null @@ -1,43 +0,0 @@ -# Agent Note: Composer 菜单分节、本地化行与「文件」行 - -Status: implemented - -[English](2026-09-08-composer-menu-sections-and-localized-rows.md) | 中文 - -## 问题 - -composer 的 `+` 按钮与键入的 `/` 按宿主注册顺序把每个命令列成 `name description`,全部小写,没有图标也不分组,旁边还有一个单独的回形针按钮用于添加文件。中文界面下各行仍是英文,因为宿主描述符只携带英文文案;只知道命令中文名的用户既搜不到它,也看不出该输入什么。Issue #3567 及其设计稿要求:按使用频次分成两个小节,每行带图标、标题左对齐、说明右对齐,英文标题首字母大写,中文标题与说明在两种语言下都能搜索并同时显示英文命令名,计划与目标的声明用中文填入,以及把「文件」入口放进菜单。 - -## 决策 - -`ui-commands` 在 `src/client/presentation.ts` 里拥有菜单的展示:「添加」小节(`file`、`goal`、`plan`、`feedback`)与「指令」小节(`compact`、`permission`、`model`、`export`),各按使用频次排列,不在两个清单里的行按目录顺序排在「指令」末尾。小节清单按名字索引;空查询返回分节的行,输入查询后返回所有可见行的平铺排序,最佳匹配始终在第一位。 - -六个内置宿主命令从客户端取得本地化标题、说明、图标和认领 token。稳定标识选择与输入解析遵循[命令标识决策](../architecture/2026-09-10-command-identities-and-composer-file-action.zh.md)。贡献项携带 `label()`、`description()` 和 `icon`,每次生成候选项时读取,因此 `/model` 行无需重新注册即可本地化。 - -菜单选中填入当前语言的认领 token:中文下选中「计划」填入 `/计划 `,提交时执行 `/plan `。手输 token 保留原写法作为认领,因为输入框从它持有的 token 之后读取参数。会话有效目录在所有界面语言下通过同一输入路径解析中英文别名。 - -「文件」行是一个 `action` 贡献项:裸调用消费触发 token 后运行客户端回调,不提交消息。Conversation 负责该注册、实时可用性和隐藏文件输入框。菜单取代单独的回形针按钮,`+` 按钮的无障碍名称与提示为「添加文件或调用指令」。 - -`ui-input-trigger` 渲染新的行结构:`InputTriggerCandidate.label` 是标题,也是共享 `rankByName` 的第二个搜索键;label 与名字不同时名字渲染为尾随别名;`icon` 在引用图标 token 之外接受图标组件;说明右对齐。别名与说明使用比主标签小 1px 的字号。`ui-primitives` 新增设计稿给出的计划图标、压缩用的静态环形,以及权限盾形轮廓。 - -菜单采用 400 px 的 border-box 高度上限,在视口限制缩小高度前可容纳两个小节标题与八个内置入口。内容确实溢出时,WebKit 系浏览器用 6 px 的可拖动区域承载 2 px 的可见滑块,轨道避开圆角两端,视口抵达最后一行前显示底部渐隐;Firefox 保留标准细滚动条。 - -## 考虑过的替代方案 - -**删除分隔空格时释放命令认领。** 完整命令名仍能标识已选命令。保留认领直到命令名改变,可以在替换参数时保持高亮,也无需依赖输入法生成的空格触发普通按键裁决。共享输入状态机对所有命令 token 使用这个规则,提交失败后的恢复也相同;命令名和界面语言都不选择另一套实现。 - -**在原生组合输入结束事件中直接恢复占位文字。** 浏览器可能先发送该事件,Lexical 随后才完成最终文字更新。共享编辑器绑定在原生输入法或编辑器仍处于组合输入时隐藏命令提示和普通占位文字,并在编辑器提交更新后重新判断显隐,包括没有文字变化的取消操作。键盘提交保护保留独立的组合输入结束后保护时段。 - -**在宿主侧本地化说明。** 宿主没有语言设置,目录由所有客户端共享,本地化产品文案属于客户端。 - -**输入查询后保留小节。** 在小节内排序会把「指令」里的前缀命中排在「添加」里较弱的匹配之下(输入 `e` 时反馈与文件排在导出之上);平铺排序让最佳匹配始终靠前,小节标题只在列表完整时才有信息量。 - -**在菜单旁保留回形针。** 设计稿与 issue 的验收条件都要求把入口整合进菜单且旧图标不再重复显示;隐藏的文件输入框与拖放门槛保持原位。 - -**由贡献项拥有内置宿主行的文案。** 宿主包没有客户端一半可供注册,文案只能放在客户端;在已经拥有 `/` source 的包里用一张按名字索引的表,能把设计决策集中在一处。 - -**只在当前语言下解析 `token` 别名。** 遍历所有词典解析没有成本,还能让在一种语言下持久化的草稿在另一种语言下提交。 - -## 后果 - -菜单与本地化展示由客户端负责,宿主负责有效命令定义。新增本地化内置命令需要对应的标识映射、词典条目、图标和菜单位置。会话驱动的浏览器场景与归属模块的 ARIA 预期覆盖菜单。无关的同名定义保留自己的文案,不获得内置图标。命令标识决策负责这一划分和文件动作的生命周期。 diff --git a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml deleted file mode 100644 index ea1536b2ae..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-desktop-uninstall-preserve-dsh-home.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-desktop-uninstall-preserve-dsh-home.md -/agent-note-windows-uninstall-retains-the-harness-home: - en: ae26a8eee5e5ef6e - zh: 2549190b3d8890e1 -/agent-note-windows-uninstall-retains-the-harness-home/problem: - en: 7e0091e3af8b52d8 - zh: b40193a58711c6a5 -/agent-note-windows-uninstall-retains-the-harness-home/decision: - en: fd2608389491a1f8 - zh: b007e1d35a049292 -/agent-note-windows-uninstall-retains-the-harness-home/alternatives-considered: - en: f3a2c8263d598b30 - zh: 226a3203c940bdd3 -/agent-note-windows-uninstall-retains-the-harness-home/deferred: - en: 7d13267e5928961a - zh: 79398501c8e099dd -/agent-note-windows-uninstall-retains-the-harness-home/consequences: - en: 9f94a327edbd7685 - zh: fa53c77c2d9df54d diff --git a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml deleted file mode 100644 index 5de5db011a..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-feedback-dialog-and-categories.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md -/agent-note-feedback-dialog-categories-and-the-acknowledgement-toast: - en: 0ca76fe002b0b0fc - zh: 14fa2772a6a23e25 -/agent-note-feedback-dialog-categories-and-the-acknowledgement-toast/problem: - en: 90ac7f116156c209 - zh: 20274add2ffbcdc0 -/agent-note-feedback-dialog-categories-and-the-acknowledgement-toast/decision: - en: 4cde40e5b1ad6beb - zh: fb49e47434faf06f -/agent-note-feedback-dialog-categories-and-the-acknowledgement-toast/alternatives-considered: - en: 3713257ee75869da - zh: 5892d375fe8d0764 -/agent-note-feedback-dialog-categories-and-the-acknowledgement-toast/consequences: - en: a55c05291a78e922 - zh: 1373131586665daf diff --git a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.i18n.yaml deleted file mode 100644 index efe36291ac..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-file-display-through-filesystem.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-file-display-through-filesystem.md -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads: - en: 1db6d91b6011384e - zh: 68152c2e3fff2931 -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads/problem: - en: c8e25473775da151 - zh: af807ea30d423858 -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads/decision: - en: d448ed98f635fd14 - zh: 81795f947a0c1c99 -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads/alternatives-considered: - en: 80e52b422305ca28 - zh: d0e38b1cd96be951 -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads/consequences: - en: 15ec0dd18d621f24 - zh: e4925a96395edd8c -/agent-note-authenticated-file-display-reuses-filesystem-byte-reads/testing: - en: 9d28b1552a2decde - zh: 6ea9f91992bcfa1a diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml index e2c08a0acd..72705e7a3a 100644 --- a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.i18n.yaml @@ -9,8 +9,8 @@ en: 8598b8d2e92bcfa1 zh: e3ae5fc294711095 /agent-note-present-declares-workspace-source-files/decision: - en: 481549ced705b010 - zh: 8cad3ea14f84decf + en: 13ea1b1be39b214b + zh: cd4cea316f8eae51 /agent-note-present-declares-workspace-source-files/alternatives-considered: en: b959eae9786351da zh: 030c06051d6ac4be diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md index 52cc3e9cdb..45780be4da 100644 --- a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md @@ -10,13 +10,13 @@ Users need to open and edit the files produced in their workspace, including she ## Decision -The [present tool](../../../../packages/deliverables/tool-present/README.md) declares existing regular source files under the [Session filesystem access policy](2026-09-09-present-filesystem-access.md). It records paths and optional descriptions without reading or copying contents. The [deliverables plugin](../../../../packages/client/ui-deliverables/README.md) opens current workspace sources in the Host's default application. Edits are visible on the next open; deletion or movement makes the declaration unavailable. File-content preservation and copy-on-write storage are deferred until a persistence design owns them. +The [present tool](../../../../packages/deliverables/tool-present/README.md) declares existing regular source files under the [Session filesystem access policy](../../../../packages/deliverables/tool-present/README.md). It records paths and optional descriptions without reading or copying contents. The [deliverables plugin](../../../../packages/client/ui-deliverables/README.md) opens current workspace sources in the Host's default application. Edits are visible on the next open; deletion or movement makes the declaration unavailable. File-content preservation and copy-on-write storage are deferred until a persistence design owns them. The [tool description](../../../../packages/deliverables/tool-present/README.md#model-experience) guides the model's choice of files for explicit delivery. A declaration remains distinct from a prose file reference. The recorded [SVG delivery scenario](../../../../snapshots/web/present-svg/snapshot.yml) covers the resulting file, delivery event, and card. New recordings explicitly request a separate file card; replay preserves the recorded request. This scenario verifies explicit delivery, not a requirement to call `present` for every SVG. Its UI snapshot covers the expanded Chat transcript; navigation and composer controls belong to their own scenarios, so unrelated chrome changes cannot invalidate file-delivery expectations. The tool remains an ordinary package with shared filesystem and tool error classes. Its pure type entry owns the delivery event without importing Host code into the browser. The `standard`, `ptc`, and `cordis` presets mount it; `minimal` retains its two tools. Each plugin instance correlates its executions with successful final `tools/result` notifications before appending `deliverables/presented`. Native and nested calls share this rule. A later enclosing program failure does not revoke a completed nested declaration; blocked results publish none, and same-name scoped replacements cannot publish another instance's results. -An authenticated POST selects a declaration by viewed Session, event sequence, and original file index. The event carries no owning Session ID; relative paths in inherited history resolve against the viewed Session's workspace. The Host verifies regular-file existence and Host-path mapping before native opening. Route disposal cancels and awaits pending commands. The [changed-files card](2026-09-11-turn-changed-files-card.md) lists the turn's recorded changes beside the delivery cards; neither surface implies final delivery. +An authenticated POST selects a declaration by viewed Session, event sequence, and original file index. The event carries no owning Session ID; relative paths in inherited history resolve against the viewed Session's workspace. The Host verifies regular-file existence and Host-path mapping before native opening. Route disposal cancels and awaits pending commands. The [changed-files card](../../../../packages/deliverables/workspace-changes/README.md) lists the turn's recorded changes beside the delivery cards; neither surface implies final delivery. File cards use the same split-control pattern as the Session header. The card and the left Open segment preview the source in the right Sidebar; the chevron opens the standard menu for default-app and file-manager actions. The Host selects the file in Finder or Explorer, or opens its containing folder through the default Linux file manager. Both native actions resolve the same saved declaration and verify the Session filesystem and Host path; neither accepts a browser-supplied replacement path. Host-derived desktop metadata keeps remote-browser labels and availability honest, and the route enforces the configured availability on each native gesture. One delivery spans the row; multiple deliveries use at most two columns, retain every declaration, and collapse after the first four cards until the user expands the list. Desktop metadata is invalidated with the connection generation so an old Host cannot keep native actions disabled or supply the wrong file-manager labels. Old metadata requests are cancelled and cannot replace the new generation’s response. diff --git a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md index 99b7b9df1d..74214756a7 100644 --- a/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md +++ b/.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md @@ -10,13 +10,13 @@ Status: implemented ## 决策 -[present 工具](../../../../packages/deliverables/tool-present/README.zh.md)声明交付[Session 文件系统访问策略](2026-09-09-present-filesystem-access.zh.md)允许的已有普通源文件。它记录路径和可选说明,不读取或复制内容。[交付插件](../../../../packages/client/ui-deliverables/README.zh.md)使用 Host 默认应用打开当前工作区源文件。下次打开会看到编辑后的内容;删除或移动文件会使声明不可用。文件内容保留与写时复制存储延期到有持久化设计负责时实现。 +[present 工具](../../../../packages/deliverables/tool-present/README.zh.md)声明交付[Session 文件系统访问策略](../../../../packages/deliverables/tool-present/README.zh.md)允许的已有普通源文件。它记录路径和可选说明,不读取或复制内容。[交付插件](../../../../packages/client/ui-deliverables/README.zh.md)使用 Host 默认应用打开当前工作区源文件。下次打开会看到编辑后的内容;删除或移动文件会使声明不可用。文件内容保留与写时复制存储延期到有持久化设计负责时实现。 [工具说明](../../../../packages/deliverables/tool-present/README.zh.md#model-experience)指导模型选择需要显式交付的文件。交付声明与正文中的文件引用各自独立。录制的 [SVG 交付场景](../../../../snapshots/web/present-svg/snapshot.yml)覆盖生成文件、交付事件和卡片。新录制明确请求独立文件卡片,回放保留录制时的请求。该场景验证显式交付,不要求所有 SVG 都调用 `present`。其 UI 快照覆盖展开后的 Chat 对话内容;导航和输入框控件由各自场景负责,避免无关界面改动使文件交付预期失效。 工具保持为普通包,共享文件系统和工具错误类型。其纯类型入口拥有交付事件,不向浏览器导入 Host 代码。`standard`、`ptc` 与 `cordis` preset 挂载工具;`minimal` 保持两个工具。每个插件实例将其执行与成功的最终 `tools/result` 通知关联,再追加 `deliverables/presented`。原生与嵌套调用遵循同一规则。外层程序随后失败不会撤销已完成的嵌套声明;被阻止的结果不发布声明,同名作用域替换也不能发布其他实例的结果。 -经过认证的 POST 按当前查看的 Session、事件序号和原始文件索引选择声明。事件不携带所属 Session ID;继承历史中的相对路径按当前查看的 Session 工作区解析。Host 在原生打开前检查普通文件是否存在,并验证 Host 路径映射。路由释放时取消并等待进行中的命令。[改动文件卡片](2026-09-11-turn-changed-files-card.zh.md)在交付卡片旁列出本轮记录的改动;两个表面均不表示最终交付。 +经过认证的 POST 按当前查看的 Session、事件序号和原始文件索引选择声明。事件不携带所属 Session ID;继承历史中的相对路径按当前查看的 Session 工作区解析。Host 在原生打开前检查普通文件是否存在,并验证 Host 路径映射。路由释放时取消并等待进行中的命令。[改动文件卡片](../../../../packages/deliverables/workspace-changes/README.zh.md)在交付卡片旁列出本轮记录的改动;两个表面均不表示最终交付。 文件卡片采用与 Session 顶栏相同的分段控件。点击卡片或左侧“打开”区域会在右侧 Sidebar 预览源文件;右侧箭头打开包含默认应用与文件管理器操作的标准菜单。Host 在 Finder 或文件资源管理器中选中文件,或通过 Linux 默认文件管理器打开所在文件夹。两个原生操作都解析同一份已保存声明并验证 Session 文件系统与 Host 路径;均不接受浏览器提供的替代路径。来自 Host 的桌面信息使远程浏览器中的文案和可用性保持准确,路由在每次原生操作时执行配置的可用性检查。单个交付占满整行;多个交付每行最多两列,并保留所有声明,前四张卡片之后的内容在用户展开列表前保持收起。 桌面元数据随连接代次失效,避免旧主机信息让原生操作持续禁用或显示错误的文件管理器名称。旧元数据请求会被取消,不能覆盖新代次的响应。 diff --git a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.i18n.yaml deleted file mode 100644 index 569b09933e..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-shared-file-type-icons.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.md -/agent-note-shared-file-type-icons: - en: d83f437754356503 - zh: 7f5b673537312d8c -/agent-note-shared-file-type-icons/problem: - en: 67d8c2d11e0ae0a2 - zh: fb2a576b528db3ad -/agent-note-shared-file-type-icons/decision: - en: 418ba69021928ba0 - zh: 7fc1b5cef5f22004 -/agent-note-shared-file-type-icons/alternatives-considered: - en: 67dc36a02b841541 - zh: cf262c343957ff69 -/agent-note-shared-file-type-icons/testing: - en: 16768b2ea13d16ef - zh: e7455e31220bb2e6 -/agent-note-shared-file-type-icons/consequences: - en: fb30a1c743dbe885 - zh: 858fb79479434d69 diff --git a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.md b/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.md deleted file mode 100644 index 5dbe0ade14..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.md +++ /dev/null @@ -1,44 +0,0 @@ -# Agent Note: Shared file-type icons - -Status: implemented - -English | [中文](2026-09-08-shared-file-type-icons.zh.md) - -## Problem - -Client feature plugins can share React components only through `@deepseek-ai/dsh-client-ui-primitives`, but file cards had no shared file-type presentation. The package-owned classifier provides the only extension table and deliberately folds detailed file types into six link categories for `LinkIconMedium`, while attachment cards, sent-message attachments, queued files, and workspace file rows use detailed file glyphs. Adding a precise file icon anywhere else would require another extension table or a runtime import between feature plugins. - -## Decision - -`ui-primitives` owns one Cordis-free file classification and rendering API. `fileExtension(path)` applies the shared basename and final-dot semantics to either path separator. `classifyFileType(path)` matches without case sensitivity and returns the closed `FileType` union: the traditional `code`, `excel`, `folder`, `html`, `image`, `markdown`, `other`, `pdf`, `ppt`, `video`, and `word` categories plus the detailed `CodeFileType` categories rendered by `CodeFileIcon`. Exact filenames, filename prefixes, filename suffixes, optional project context, and extensions run in that order. The path classifier returns every member except `folder`; callers use the explicit `FileTypeIcon` `kind` override when they know an entry is a directory. Unknown extensions, missing extensions, and trailing dots resolve to `other`, except the shared table recognizes named files such as `Dockerfile`, `Makefile`, `package.json`, `.gitignore`, `README`, and `CHANGELOG`. - -`FileTypeIcon` accepts a path, shared `IconProps`, the explicit `kind`, and an optional project-file snapshot. Traditional file types render the supplied 28px document and folder contours as inline SVG. The spreadsheet glyph uses the supplied unscaled 1.2px table grid; Markdown, PDF, PPT, and Word foreground marks scale to 122% around their visual center, and the remaining marked traditional glyphs use 112%. The file body and folded corner retain their source geometry, and the generic file has no invented center mark. The sheet is a solid category color, the foreground mark and ordinary folded corner are white, and the generic file has a darker grey corner. CSS assigns the supplied category palette through static design tokens: DeepSeek blue for code/HTML/Markdown, the lighter DeepSeek blue for Word, green for spreadsheets, two amber steps for folder/PPT, red for PDF, and neutral grey for unknown files. Image and video share the supplied violet through a component-local variable because the design platform has no matching violet token. A caller may override a traditional sheet through `--dsh-file-type-icon-color`. - -Recognized code and configuration files render the corresponding 20px square artwork scaled to the requested icon size. The embedded static table contains exactly the 48 established `CodeFileType` entries; archive-only artwork does not add a category, and an adjacent manifest records the responsible design owner and source digests. Tests reject scripts, event attributes, external references, and duplicate ids in that table. `CodeFileIcon` replaces local SVG ids with a per-component prefix before insertion so repeated gradients and clip paths remain independent. These technology marks retain their embedded multicolor fills and are the explicit exception to the ordinary current-color icon rule. The map selects React before TypeScript/JavaScript, Angular filename suffixes before their base extension, Docker/Node/Git/Make/CMake by filename rules, and Flutter only when the optional project snapshot contains a `pubspec.yaml` whose text includes `flutter:`. Markdown and SVG remain owned by the traditional Markdown and image categories. CSV, TSV, Excel workbooks and templates, OpenDocument spreadsheets, and Numbers use the spreadsheet glyph in file cards, rows, and preview titles; their clickable links use the document category. Both `.env` and names ending in `.env` use the environment glyph. Every traditional and technology SVG is `aria-hidden`, and the card, row, or button that owns the file identity supplies the accessible name. - -`LinkIconMedium` delegates extension classification to `classifyFileType` and folds the detailed result into its existing link vocabulary: code and HTML use `code`, images use `image`, PDF/Word/Excel/PPT use `document`, and Markdown/video/unknown files use `other`. Extensionless names remain `other` in link contexts, so the 14px clickable-link appearance defined by the [link styling rules](../../../../docs/web-styling.md) does not change. - -Attachment upload cards, sent-message file cards, queued-file rows, and workspace file rows render `FileTypeIcon`. The Files tab title renders its explicit `folder` kind at 16px. Explicit delivery cards use `FileTypeIcon` at 20px and `fileExtension` for their fallback metadata. The two metadata rows use `fileExtension` rather than local parsers; a leading-dot basename such as `.env` therefore displays `ENV`, while an absent or trailing suffix displays no extension label. Image content continues to render as a preview rather than a file-type glyph, and produced-file links and Markdown file mentions continue to use `LinkIconMedium` because they are link surfaces. - -## Alternatives considered - -**Use `LinkIconMedium` for every file surface.** Rejected. Its six categories and 14px outline drawings communicate link destinations at text size; a 28px file card has room for the supplied HTML, Markdown, PDF, Word, Excel, PPT, and video identities. - -**Keep a second extension table beside `LinkIconMedium`.** Rejected. The same path could drift to different categories as either list grows. One detailed table plus an explicit detailed-to-link adapter preserves both consumers' semantics. - -**Put the supplied traditional-file colors directly in each SVG path.** Rejected. Traditional SVG geometry stays reusable and follows the icon set's `currentColor` rule; the component stylesheet owns the default category palette, and render sites retain one CSS-variable override instead of rewriting path fills. The technology artwork is exempt because its embedded multicolor marks identify the language or tool rather than decorating a generic file silhouette. - -**Add archive, audio, and data categories for symmetry.** Rejected. The supplied artwork and current consumers require no such glyphs. New `FileType` members require a shipped render site and artwork that remains legible at the 28px seat. - -## Testing - -The `ui-primitives` specs cover case-insensitive suffixes, both path separators, leading-dot files, missing and trailing suffixes, common named files, unknown fallback, all detailed code mappings, rule priority, Flutter context, the exact 48-key artwork set, rejected archive-only categories, static-markup safety, instance-safe SVG ids and references, `aria-hidden`, sizing/class forwarding, distinct artwork, the supplied spreadsheet grid, the 112% and 122% foreground-mark transforms, and the traditional solid-sheet/contrast-mark layers without literal SVG colors. A stylesheet spec pins every traditional category-to-color mapping, the caller override, and the local violet value. The `LinkIconMedium` classification table pins its coarse output, including `Makefile` remaining `other`. Attachment, chat, queue, and sidebar component suites exercise the migrated render paths; their accessibility output does not change because the glyphs remain decorative. - -## Consequences - -- Client packages use one filename parser and one detailed file-type table instead of importing or recreating feature-local logic. -- A new suffix joins the detailed table only when an existing glyph truthfully represents it. If its link category differs from the current adapter, the change must also decide whether the 14px link appearance changes. -- Code and configuration artwork preserves its embedded palette and does not accept the traditional `--dsh-file-type-icon-color` override. -- The fixed 48-entry artwork table adds about 35 kB uncompressed and 17 kB gzip to the shared browser bundle; adding categories must justify that static baseline cost. -- The primitive owns no copy but does own the default file-type palette. Consumers continue to own accessible labels and surrounding text, and may replace the category color through `--dsh-file-type-icon-color`. -- The detailed category names describe presentation, not MIME validation. A suffix is a display hint and does not establish file contents or trust. diff --git a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.zh.md b/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.zh.md deleted file mode 100644 index 9dbccdf10f..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-shared-file-type-icons.zh.md +++ /dev/null @@ -1,44 +0,0 @@ -# Agent Note: 共享文件类型图标 - -Status: implemented - -[English](2026-09-08-shared-file-type-icons.md) | 中文 - -## 问题 - -客户端功能插件只能通过 `@deepseek-ai/dsh-client-ui-primitives` 共享 React 组件,但文件卡片没有共享的文件类型呈现。本包拥有唯一一份扩展名分类表,并为 `LinkIconMedium` 将细分文件类型有意折叠为六种链接类别;附件卡片、已发送消息附件、排队文件和工作区文件行使用细分文件图形。在其他位置增加精细文件图标,需要再复制一份扩展名表或让功能插件之间产生运行时 import。 - -## 决策 - -`ui-primitives` 统一拥有一套 Cordis-free 的文件分类与渲染 API。`fileExtension(path)` 对两种路径分隔符采用共享的 basename 与最终点号语义。`classifyFileType(path)` 不区分大小写,并返回闭合的 `FileType` 联合:传统的 `code`、`excel`、`folder`、`html`、`image`、`markdown`、`other`、`pdf`、`ppt`、`video`、`word` 类别,以及由 `CodeFileIcon` 渲染的细分 `CodeFileType` 集合。解析按完整文件名、文件名前缀、文件名后缀、可选项目上下文、扩展名的顺序执行。路径分类器返回 `folder` 之外的全部成员;调用方确认条目是目录时,通过 `FileTypeIcon` 的显式 `kind` 覆盖指定目录。未知扩展名、无扩展名和末尾点号回退到 `other`,但共享表识别 `Dockerfile`、`Makefile`、`package.json`、`.gitignore`、`README`、`CHANGELOG` 等具名文件。 - -`FileTypeIcon` 接受路径、共享 `IconProps`、显式 `kind`和可选的项目文件快照。传统文件类型把所提供的 28px 文档与文件夹轮廓渲染为 inline SVG。表格图形使用所提供的未缩放 1.2px 表格网格;Markdown、PDF、PPT、Word 的前景标记围绕自身视觉中心缩放至 122%,其余带标记的传统图形使用 112%。文件底板与折角保持源图几何,通用文件不凭空增加中心标记。底板使用实色分类颜色,前景标记与普通折角使用白色,通用文件使用较深的灰色折角。CSS 通过静态设计 token 分配所提供的分类调色板:code/HTML/Markdown 使用 DeepSeek 蓝,Word 使用较浅的 DeepSeek 蓝,表格使用绿色,folder/PPT 使用两档琥珀色,PDF 使用红色,未知文件使用中性灰。image 与 video 通过组件本地变量共用所提供的紫色,因为设计平台没有匹配的紫色 token。调用方可通过 `--dsh-file-type-icon-color` 覆盖传统底板颜色。 - -已识别的代码与配置文件把对应的 20px 方形图稿缩放到请求的图标尺寸。内嵌静态表只包含现有 48 个 `CodeFileType` 条目;资源包中额外的图稿不会新增类别,相邻 manifest 记录负责的设计归属方与来源摘要。测试会拒绝该表中的脚本、事件属性、外部引用与重复 id。`CodeFileIcon` 在插入前为本地 SVG id 加上组件实例前缀,使重复渐变与裁剪路径互不干扰。这些技术标记保留自身内嵌的多色填充,是普通 current-color 图标规则的明确例外。映射让 React 优先于 TypeScript/JavaScript、Angular 文件名后缀优先于基础扩展名,并按文件名识别 Docker/Node/Git/Make/CMake;只有可选项目快照包含内容带 `flutter:` 的 `pubspec.yaml` 时才选择 Flutter。Markdown 与 SVG 仍由传统 Markdown 和图片类别拥有。CSV、TSV、Excel 工作簿与模板、OpenDocument 表格和 Numbers 在文件卡片、文件行及预览标题中使用表格图标,其可点击链接使用文档类别。`.env` 和以 `.env` 结尾的文件名均使用环境配置图标。所有传统与技术 SVG 都是 `aria-hidden` 的,拥有文件身份的卡片、行或按钮提供无障碍名称。 - -`LinkIconMedium` 委托 `classifyFileType` 做扩展名分类,再把精细结果折叠进原有链接词汇:code 与 HTML 使用 `code`,图片使用 `image`,PDF/Word/Excel/PPT 使用 `document`,Markdown、video 与未知文件使用 `other`。无扩展名文件在链接语境中仍是 `other`,因此[链接样式规则](../../../../docs/web-styling.zh.md)定义的 14px 外观不变。 - -附件上传卡片、已发送消息文件卡片、排队文件行和工作区文件行渲染 `FileTypeIcon`。Files 标签页标题使用显式的 `folder` 类别,尺寸为 16px。显式交付卡片使用 20px 的 `FileTypeIcon`,并通过 `fileExtension` 提供默认元数据。两处元数据行统一使用 `fileExtension`;`.env` 这样的前导点 basename 会显示 `ENV`,无后缀或末尾点号则不显示扩展名 label。图片内容继续渲染为预览而不是文件类型图形,产物文件链接与 Markdown 文件提及继续使用 `LinkIconMedium`,因为它们属于链接表面。 - -## 备选方案 - -**所有文件表面都使用 `LinkIconMedium`。** 否决。它的六种类别和 14px outline 图形用于在文字尺寸下表达链接目标;28px 文件卡片有空间表达所提供的 HTML、Markdown、PDF、Word、Excel、PPT 与 video 身份。 - -**在 `LinkIconMedium` 旁保留第二份扩展名表。** 否决。任一列表增长后,同一路径可能漂移到不同类别。一份精细表加一个显式的精细到链接适配,能同时保住两类消费方的语义。 - -**把所提供的传统文件颜色直接写入每一条 SVG path。** 否决。传统 SVG 几何保持可复用,并遵守图标集的 `currentColor` 规则;组件样式表拥有默认分类调色板,渲染点只需一个 CSS 变量即可覆盖颜色,不需要重写 path 填充。技术图稿是例外,因为其内嵌的多色标记用于识别语言或工具,而不是装饰通用文件轮廓。 - -**为了对称增加 archive、audio 与 data 类别。** 否决。提供的图稿与当前消费方都不需要这些图形。新增 `FileType` 成员必须有已发布的渲染点,以及在 28px slot 中仍可辨认的图稿。 - -## 测试 - -`ui-primitives` 测试覆盖不区分大小写的后缀、两种路径分隔符、前导点文件、无后缀与末尾点号、常见具名文件、未知回退、全部细分代码映射、规则优先级、Flutter 上下文、精确的 48 键图稿集、被拒绝的资源包额外类别、静态 markup 安全性、实例安全的 SVG id 与引用、`aria-hidden`、尺寸/class 转发、不同图稿、所提供的表格网格、112% 与 122% 前景标记变换,以及不含 SVG 字面颜色的传统实色底板/对比标记层。样式表测试钉住每一项传统类别到颜色的映射、调用方覆盖变量与本地紫色值。`LinkIconMedium` 分类表钉住它的粗粒度输出,包括 `Makefile` 仍为 `other`。附件、聊天、队列和侧边栏组件测试覆盖迁移后的渲染路径;由于图形仍是装饰性的,其无障碍输出不变。 - -## 后果 - -- 客户端包使用一个文件名解析器与一份精细文件类型表,不再 import 或重新实现功能包本地逻辑。 -- 新后缀只在已有图形能准确表达它时加入精细表。若它的链接类别与当前适配不同,这次改动还必须决定是否改变 14px 链接外观。 -- 代码与配置图稿保留内嵌调色板,不接受传统图形的 `--dsh-file-type-icon-color` 覆盖。 -- 固定的 48 项图稿表为共享浏览器 bundle 增加约 35 kB 未压缩体积和 17 kB gzip 体积;新增类别必须证明这份静态基线成本是必要的。 -- primitive 不拥有文案,但拥有默认文件类型调色板。消费方继续拥有无障碍 label 与周围文字,并可通过 `--dsh-file-type-icon-color` 替换分类颜色。 -- 精细类别名称描述展示意图,不是 MIME 校验。后缀只是展示提示,不能证明文件内容或可信度。 diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.i18n.yaml deleted file mode 100644 index b7a60a735c..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-sidebar-default-pages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.md -/agent-note-sidebar-default-pages: - en: 0b6b9c156b3653d9 - zh: f4650c41660ea99d -/agent-note-sidebar-default-pages/problem: - en: 48127a7f5a873a52 - zh: a722f139876a415a -/agent-note-sidebar-default-pages/decision: - en: b6707d5c48eb414a - zh: c920061739304dfc -/agent-note-sidebar-default-pages/alternatives-considered: - en: 8d981eef6c5088b8 - zh: 0bc9541a7f7d7b53 -/agent-note-sidebar-default-pages/consequences: - en: eb418364a7c202e2 - zh: d2372099a1ed3e6e diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.md b/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.md deleted file mode 100644 index 13374ab294..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.md +++ /dev/null @@ -1,25 +0,0 @@ -# Agent Note: Sidebar default pages - -Status: implemented - -English | [中文](2026-09-08-sidebar-default-pages.zh.md) - -## Problem - -A guide with one registered entry adds a click without offering a choice. - -## Decision - -The Sidebar selects each default page from the registered guide-entry list. Exactly one entry opens that entry's page; zero or multiple entries open the guide. Resource viewers without guide entries do not affect this count. Explicitly adding a guide always opens a guide, and each pane holds at most one. - -The [last-tab close rule](2026-09-08-sidebar-last-tab-close-rules.md) owns close protection: the sole docked guide remains open, while any other sole tab closes together with the column. The generic docking kit accepts a presentation callback and has no file-browser or guide policy. Moving tabs still settles empty panes, and layout state remains memory-only. - -This replaces default-guide selection in [the shipped types](2026-09-05-sidebar-text-preview-and-file-tree.md) and explicit last-tab closing in [docking infrastructure](2026-09-04-right-sidebar-docking-infrastructure.md). Their registration, content-state, engine and layout ownership decisions remain active. - -## Alternatives considered - -**Count all registered tab types or currently open tabs.** Neither counts choices available on the guide; resource viewers need not contribute an entry. - -## Consequences - -One-entry compositions open directly into their registered page without hardcoding Files. Guide selection can still replace its own tab. Store and component tests cover registration counts; the assembled browser scenarios cover default Files, explicit guide creation, and returning to Files after closing a lone tab. diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.zh.md b/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.zh.md deleted file mode 100644 index 6730a87fbe..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-default-pages.zh.md +++ /dev/null @@ -1,25 +0,0 @@ -# Agent Note: Sidebar 默认页 - -Status: implemented - -[English](2026-09-08-sidebar-default-pages.md) | 中文 - -## 问题 - -只有一个注册入口的引导页增加一次点击,却不提供选择。 - -## 决策 - -Sidebar 从已注册的引导入口列表选择每个默认页。恰好一个入口时打开对应页面;没有入口或有多个入口时打开引导页。没有引导入口的资源查看器不影响计数。显式添加引导页始终打开引导,每个格最多持有一个。 - -[最后一个 tab 的关闭规则](2026-09-08-sidebar-last-tab-close-rules.zh.md)负责关闭保护:作为唯一停靠 tab 的引导页保持打开,其他任何唯一 tab 都会连同整列一起关闭。通用停靠套件接收呈现回调,不拥有文件浏览器或引导页策略。移动 tab 仍会处理空格,布局状态仅存于内存。 - -本决策取代[随包类型](2026-09-05-sidebar-text-preview-and-file-tree.zh.md)中的默认引导选择,以及[停靠基础设施](2026-09-04-right-sidebar-docking-infrastructure.zh.md)中的显式关闭最后一个 tab。它们的注册、内容状态、引擎与布局所有权决策继续有效。 - -## 考虑过的替代方案 - -**统计所有已注册 tab 类型或已打开的 tab。** 两者都不代表引导页提供的选择;资源查看器不一定贡献入口。 - -## 后果 - -单入口组合直接打开已注册页面,不写死 Files。引导页选择仍可替换自身 tab。store 与组件测试覆盖注册数量;组装后的浏览器场景覆盖默认 Files、显式新增引导及关闭唯一 tab 后返回 Files。 diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.i18n.yaml b/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.i18n.yaml deleted file mode 100644 index a4049b0446..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-sidebar-last-tab-close-rules.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.md -/agent-note-last-tab-close-rules-on-the-sidebar-s-docked-surface: - en: 4f38145de396b08f - zh: 3e65231a5ebb8fdd -/agent-note-last-tab-close-rules-on-the-sidebar-s-docked-surface/problem: - en: 90a9c3dc146c7d11 - zh: a12b784b66fa3979 -/agent-note-last-tab-close-rules-on-the-sidebar-s-docked-surface/decision: - en: a760dcb29bb9645c - zh: ad4dafd7d5b407e7 -/agent-note-last-tab-close-rules-on-the-sidebar-s-docked-surface/alternatives-considered: - en: 4e2673d521d6540e - zh: 0d5f0b99ea80d5ff -/agent-note-last-tab-close-rules-on-the-sidebar-s-docked-surface/consequences: - en: 3e43dd3066e8b05b - zh: ed6ed81abb18131b diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.md b/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.md deleted file mode 100644 index ed76b0ad0b..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.md +++ /dev/null @@ -1,27 +0,0 @@ -# Agent Note: Last-tab close rules on the Sidebar's docked surface - -Status: implemented - -English | [中文](2026-09-08-sidebar-last-tab-close-rules.zh.md) - -## Problem - -The settle planner guarantees the docked surface is never empty: closing the last tab reseeds the current default page. That guarantee made the last tab's close control a dead end in both directions. Closing the guide standing alone put the same guide straight back — a control that does nothing. Closing any other lone tab left the user with a column showing only the default page — after "close the last thing", an expanded panel with nothing in it is not what the gesture meant. The guide's chip also drew a hover capsule and a context menu whose only item was that no-op close. - -## Decision - -The docked surface's last tab carries one rule, decided in the Sidebar store's `closeTab` and mirrored to the kit through a new `canCloseTab(tabId)` control-policy prop (joining `canSplit` and `canAddTab`): the guide standing as the only docked tab is unclosable — no chip close control, no menu close item, and a programmatic close records nothing; any other lone tab closes together with the column in one history entry, resets fullscreen to push mode, and leaves the layout empty until the next expansion seeds the then-current default page. `soleDockedTab(state, tabId)` in [stores.ts](../../../../packages/client/ui-sidebar-right/src/client/stores.ts) names the condition; floating panels take no part in it. Per the packages rule "enforce a decision in the operation that makes it", the store's `closeTab` is the enforcement and `canCloseTab` only mirrors it into the chrome. This rule supersedes the close-protection part of [the default-page decision](2026-09-08-sidebar-default-pages.md); its selection rule remains active. - -Two kit-side presentation rules complete it in [TabPanel.tsx](../../../../packages/client/ui-dockkit/src/components/TabPanel.tsx) and [TabMenu.tsx](../../../../packages/client/ui-dockkit/src/components/TabMenu.tsx): a pane's lone chip whose close is withheld draws quiet — no capsule, no hover fill — since there is nothing to select against and nothing to do to it; and a menu that would hold no item at all produces no visible popup, so a secondary press on such a chip shows nothing rather than an empty box. - -## Alternatives considered - -**Keep the guide closable and let settle reseed it.** The visible result is a close control that does nothing; the control lies about what a press will do. - -**Hide the close in the Sidebar's renderer instead of a kit prop.** The kit draws the chip's close and the menu's close item, so the embedder cannot withhold them without a seam; a CSS override would leave the menu item live and split one decision across two owners. - -**Collapse the column from the kit when the last tab closes.** The kit has no concept of the column or its expansion; the collapse is the embedder's intent, recorded by the store alongside the close in the same entry. - -## Consequences - -`canCloseTab` is a third control-policy prop every embedder may set; leaving it out keeps every tab closable. The quiet-chip and empty-menu rules are unconditional kit behavior keyed on the same policy, so any embedder withholding a lone tab's close gets the same presentation. Reopening the column after a lone-tab close shows the default page selected from the current guide entries. Kit specs cover the withheld control, the quiet chip, and the self-dismissing menu; Sidebar unit specs cover `closeTab`'s refusal and the close-with-column entry; a [browser case](../../../../apps/web/tests/sidebar-right.e2e.ts) walks the whole rule on the rendered panel. diff --git a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.zh.md b/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.zh.md deleted file mode 100644 index bcc54c0c39..0000000000 --- a/.agents/notes/implemented/feature/2026-09-08-sidebar-last-tab-close-rules.zh.md +++ /dev/null @@ -1,27 +0,0 @@ -# Agent Note:Sidebar 停靠面最后一个 tab 的关闭规则 - -Status: implemented - -[English](2026-09-08-sidebar-last-tab-close-rules.md) | 中文 - -## 问题 - -settle planner 保证停靠面永不为空:关掉最后一个 tab 会重新播种当前默认页。这条保证让最后一个 tab 的关闭控件在两个方向上都走进死胡同。独自留下的引导页被关闭后,同一个引导页立刻回来——一个什么也不做的控件。任何其它 tab 独自留下时被关闭,用户面前只剩一列只显示默认页的面板——在「关掉最后一个东西」之后,一块展开着却空无内容的面板不是这个手势的本意。引导页的 chip 还画着悬停胶囊,右键菜单里唯一的条目就是那个无效的关闭。 - -## 决定 - -停靠面的最后一个 tab 带一条规则,由 Sidebar store 的 `closeTab` 决定,并经新的控制策略 prop `canCloseTab(tabId)`(与 `canSplit`、`canAddTab` 并列)镜像给套件:作为唯一停靠 tab 的引导页不可关闭——chip 上没有关闭控件,菜单里没有关闭项,编程式关闭什么都不记录;任何其它 tab 独自留下时,关闭会连同整列一起收起、把全屏重置为挤压模式并记为一条历史,布局保持为空,直到下次展开时创建当时的默认页。[stores.ts](../../../../packages/client/ui-sidebar-right/src/client/stores.ts) 里的 `soleDockedTab(state, tabId)` 命名这个条件;浮动面板不参与。按照 packages 规则「在做出决定的操作里执行它」,store 的 `closeTab` 是执行点,`canCloseTab` 只是把它镜像到界面。本规则取代[默认页决策](2026-09-08-sidebar-default-pages.zh.md)中的关闭保护部分;其默认页选择规则仍然有效。 - -两条套件侧的呈现规则在 [TabPanel.tsx](../../../../packages/client/ui-dockkit/src/components/TabPanel.tsx) 与 [TabMenu.tsx](../../../../packages/client/ui-dockkit/src/components/TabMenu.tsx) 里补全它:某格仅剩的一个 chip 在关闭被收起时画成安静样式——没有胶囊底色,没有悬停填充——因为既没有别的 tab 可供选择,也没有任何可对它做的事;一个连一项都没有的菜单不会产生可见弹层,于是对这样的 chip 次键按下什么都不显示,而不是画一个空框。 - -## 考虑过的替代方案 - -**让引导页保持可关闭,由 settle 重新播种。** 可见的结果是一个什么也不做的关闭控件;这个控件在按下会发生什么这件事上撒谎。 - -**在 Sidebar 的渲染器里藏掉关闭,而不加套件 prop。** chip 的关闭控件与菜单的关闭项都由套件绘制,没有接缝嵌入方就无法收起它们;CSS 覆盖会留下仍然生效的菜单项,把一个决定拆给两个所有者。 - -**由套件在最后一个 tab 关闭时收起整列。** 套件没有「列」或「展开」的概念;收起是嵌入方的意图,由 store 在同一条历史里与关闭一并记录。 - -## 后果 - -`canCloseTab` 成为每个嵌入方都可设置的第三个控制策略 prop;不设置时每个 tab 都可关闭。安静 chip 与空菜单两条规则是套件的无条件行为,键在同一策略上,任何收起了独 tab 关闭的嵌入方都得到同样的呈现。独 tab 关闭后重新展开的列显示根据当前引导入口选出的默认页。套件 spec 覆盖收起的控件、安静 chip 与自行消失的菜单;Sidebar 单元 spec 覆盖 `closeTab` 的拒绝与「关闭连带整列」的历史条目;一个[浏览器用例](../../../../apps/web/tests/sidebar-right.e2e.ts)在渲染出的面板上走完整条规则。 diff --git a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml deleted file mode 100644 index f4cc113bc2..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-headless-machine-readable-run-surface.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-09-headless-machine-readable-run-surface.md -/agent-note-headless-machine-readable-run-surface: - en: bcf2251478004e35 - zh: 70ade47c837c6bab -/agent-note-headless-machine-readable-run-surface/problem: - en: 9d2c64ed47b82129 - zh: bb0de126368e9f07 -/agent-note-headless-machine-readable-run-surface/decision: - en: ed08acff9c4ea030 - zh: 484c2646de4aa5d8 -/agent-note-headless-machine-readable-run-surface/decision/command-line-contract: - en: 59470d5c2eba0a57 - zh: 342c1ac9042c6b46 -/agent-note-headless-machine-readable-run-surface/decision/event-stream: - en: b6837e3de1305559 - zh: 183956da3319a126 -/agent-note-headless-machine-readable-run-surface/decision/session-identity: - en: ebb2c5da4ea076a9 - zh: a706416d290e7424 -/agent-note-headless-machine-readable-run-surface/consequences: - en: 1ba14087f533dd8e - zh: 66f4aa9d63a0da23 -/agent-note-headless-machine-readable-run-surface/alternatives-considered: - en: 42f321bd399f906e - zh: 3b13791afa256781 diff --git a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.i18n.yaml deleted file mode 100644 index 4b6f724f0b..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-present-filesystem-access.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-present-filesystem-access.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-09-present-filesystem-access.md -/agent-note-present-follows-session-filesystem-access: - en: 33cf0a75284f8392 - zh: 5299af2f77bf6fd8 -/agent-note-present-follows-session-filesystem-access/problem: - en: dda1953cb567c3dc - zh: 3c447175454c844a -/agent-note-present-follows-session-filesystem-access/decision: - en: 2668dd51b07e2653 - zh: 3cd895017f568c42 -/agent-note-present-follows-session-filesystem-access/alternatives-considered: - en: 85336cd165610ddf - zh: 75608e3bfeab5dc5 -/agent-note-present-follows-session-filesystem-access/consequences: - en: e31784fe619d4d1c - zh: 7cadc3941a0c7b33 diff --git a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml deleted file mode 100644 index 6ae3a85d71..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-ptc-trajectory-code-inspection.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-09-ptc-trajectory-code-inspection.md -/agent-note-inspect-recorded-ptc-source-in-trajectory: - en: 29f601adfcaa6019 - zh: bdd1c59d1bbbff62 -/agent-note-inspect-recorded-ptc-source-in-trajectory/problem: - en: 6b49bc4faca04ae0 - zh: 60165937e0a7c71d -/agent-note-inspect-recorded-ptc-source-in-trajectory/decision: - en: 43dd488c270c1664 - zh: 3fdf6d374c49fee3 -/agent-note-inspect-recorded-ptc-source-in-trajectory/alternatives-considered: - en: bbf705774c5c608a - zh: 1af9b4aa3d4fa025 -/agent-note-inspect-recorded-ptc-source-in-trajectory/consequences: - en: c680634fe8b95880 - zh: 7660cd3354522ef8 diff --git a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.i18n.yaml deleted file mode 100644 index e82abbb700..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-sidebar-and-preview-interaction-polish.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.md -/agent-note-sidebar-and-preview-interaction-polish: - en: bce3b5bc9f49bbff - zh: bebc5b0ce3a370a7 -/agent-note-sidebar-and-preview-interaction-polish/problem: - en: 2d6e8ec1b08dcce9 - zh: 75e1c454d71da103 -/agent-note-sidebar-and-preview-interaction-polish/decision: - en: 7358c236daed510b - zh: 93cf67bbffa2c20b -/agent-note-sidebar-and-preview-interaction-polish/alternatives-considered: - en: c828e83875412d49 - zh: d0277bf0d1f5d7a9 -/agent-note-sidebar-and-preview-interaction-polish/consequences: - en: cb7cb72724552f2b - zh: 05705b4795ade85f diff --git a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.md b/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.md deleted file mode 100644 index eba8bd3a23..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: Sidebar and preview interaction polish - -Status: implemented - -English | [中文](2026-09-09-sidebar-and-preview-interaction-polish.zh.md) - -## Problem - -Five small interaction defects around the right Sidebar and the document preview. Every session's surface was born with a seeded default page, so a collapsed column the user never opened already held a page, and the first open into a fresh surface showed the seed beside the content it opened. Page tabs deduplicated surface-wide: opening a page whose tab sat in the other pane pulled focus across panes instead of opening it where asked. Dragging a pane's sole tab onto its own edge did nothing, though the user plainly asked for a split. A dropdown over the HTML preview did not dismiss on a click inside the sandboxed iframe, because that pointerdown never reaches the parent document. And the code preview's copy banner and card background scrolled away under horizontal scrolling, while the code kept the chat card's gray fill instead of sitting on the pane's own background. - -## Decision - -**Default pages seed lazily.** `createSurface` in [stores.ts](../../../../packages/client/ui-sidebar-right/src/client/stores.ts) mints a collapsed, empty surface; the store's `advance` passes the seed factory to `planSettle` only when the intent leaves the column expanded. The expansion that would first show an empty layout is what seeds the then-current default page, and closing the last closable tab collapses the column and leaves the layout empty until the next expansion. The [last-tab close rule](2026-09-08-sidebar-last-tab-close-rules.md) and [default-page selection](2026-09-08-sidebar-default-pages.md) stay as decided; default pages are created on expansion. Splitting an empty pane is a no-op: it mints no tabs, records no history, and reports no new pane. - -**Page uniqueness is pane-scoped.** The guide-only merge generalized to every page kind (`pageKind`/`panePage`): opening a page focuses an existing tab only inside the pane the open targets, and a page dragged, dropped, or docked into a pane already showing that kind's page merges into the pane's own. Resource tabs keep the kit's surface-wide reveal. - -**A sole tab splits its own pane when a factory backfills it.** `planDropTab` in [planner.ts](../../../../packages/client/ui-dockkit/src/engine/planner.ts) takes an optional `TabFactory`; with one, the previously refused self-edge release splits, the factory's tab backfills the vacated pane before the move so the dragged tab ends focused. Without a factory the release still changes nothing. - -**Menus close on focus entering an iframe.** [Menu.tsx](../../../../packages/client/ui-primitives/src/Menu.tsx) adds a window `blur` listener gated on `document.activeElement instanceof HTMLIFrameElement` — the focus move is the only signal a pointerdown inside a cross-origin iframe leaves, and the gate keeps app or tab switches from closing the list. - -**The code preview separates its banner from scrolling source and drops the card fill.** The shared CodeBlock wraps its rendered source in a stable `data-code-block-content` node that defaults to `display: contents`, so existing consumers keep their layout. [CodeBody.module.css](../../../../packages/client/ui-sidebar-documentpreview/src/client/code/CodeBody.module.css) materializes that node as a full-height inner scrollport below the banner; the code renderer reports it through a callback ref, so the document owner follows Slot replacement for position restoration, paging, and line navigation. The shared CodeBlock's fill is routed through `--dsl-code-block-background` (default unchanged, so chat keeps its gray card), while the preview sets it to `transparent` so code sits on the pane's own background. - -## Alternatives considered - -**Keep seeding at surface creation.** A collapsed column held a page nobody asked for, and the seed took slot 0 of every fresh surface ahead of the first real open. - -**Keep surface-wide page dedupe.** Focus jumped to the other pane on an explicit "open here", the exact complaint that started the change. - -**A bare window-blur close for menus.** Closes the list on every app or tab switch; the `activeElement` gate scopes the close to the one case the document cannot see. - -**Keep code in the shared document scroller.** A child banner cannot cover its parent's native scrollbar. Giving the stable source wrapper both scroll axes keeps the scrollbar at the visible viewport edge below the adjacent banner, rather than at the end of a long code block. - -## Consequences - -`planDropTab`'s factory parameter is new kit API any embedder may pass; `planSettle` already accepted an absent factory, which now also names the Sidebar's collapsed-state behavior. The `--dsl-code-block-background` variable keeps chat's default gray card while the preview uses the pane background, and `data-code-block-content` lets an owner materialize a dedicated source viewport without changing other CodeBlock layouts. Kit planner specs cover the backfilled self-split and its focus order; Sidebar store, service, and seat specs cover lazy seeding, pane-scoped page merges, and the empty collapsed layout; document-preview specs cover inner scrolling and line navigation; a Menu spec covers the gated blur close. The `ui-sidebar-right`, `ui-dockkit`, and `ui-sidebar-documentpreview` READMEs restate the rules. diff --git a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.zh.md b/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.zh.md deleted file mode 100644 index 93d88d41e9..0000000000 --- a/.agents/notes/implemented/feature/2026-09-09-sidebar-and-preview-interaction-polish.zh.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: 侧边栏与文件预览交互打磨 - -Status: implemented - -[English](2026-09-09-sidebar-and-preview-interaction-polish.md) | 中文 - -## Problem - -右侧边栏与文档预览周边的五个小交互缺陷。每个会话的停靠面出生时就播种默认页,于是用户从未打开过的折叠列里已经躺着一个页面,向全新停靠面的第一次打开会让种子页出现在所开内容旁边。页 tab 在整个停靠面范围去重:打开一个页时,若它的 tab 在另一格里,焦点会被拽到那一格,而不是在被要求的格里打开。把一个格的唯一 tab 拖到本格边缘什么都不发生,尽管用户明明要的是分栏。HTML 预览上方的下拉菜单在点击沙箱 iframe 内部时不消失,因为那次 pointerdown 根本到不了父文档。代码预览的复制条和卡片背景会被横向滚动甩开,而且代码保留了会话卡片的灰色填充,而不是坐在分栏自身的背景上。 - -## Decision - -**默认页惰性播种。**[stores.ts](../../../../packages/client/ui-sidebar-right/src/client/stores.ts) 的 `createSurface` 铸造一个折叠且为空的停靠面;store 的 `advance` 只在意图让列保持展开时才把种子工厂传给 `planSettle`。首次会展示空布局的那次展开播种彼时的默认页;关闭最后一个可关 tab 时收起整列,布局保持为空直到下次展开。[最后一个 tab 的关闭规则](2026-09-08-sidebar-last-tab-close-rules.zh.md)与[默认页选择](2026-09-08-sidebar-default-pages.zh.md)维持原决定;默认页在展开时创建。对空分栏执行 split 不产生变化,不创建 tab,不记录历史,也不返回新分栏。 - -**页唯一性以格为界。**只对引导页的合并规则推广到每种页 kind(`pageKind`/`panePage`):打开一个页只在这次打开的目标格内聚焦既有 tab;把页拖入、放入或收回到已展示该 kind 页的格会并入该格自己的 tab。资源 tab 保留套件的全停靠面聚焦。 - -**有工厂回填时,唯一 tab 可对本格分栏。**[planner.ts](../../../../packages/client/ui-dockkit/src/engine/planner.ts) 的 `planDropTab` 接受可选 `TabFactory`:带工厂时,先前被拒绝的本格边缘释放会分栏,工厂的 tab 先于移动回填腾出的格,因此被拖的 tab 最终保持聚焦。不带工厂时该释放仍不改变任何东西。 - -**焦点进入 iframe 时关闭菜单。**[Menu.tsx](../../../../packages/client/ui-primitives/src/Menu.tsx) 增加 window `blur` 监听,以 `document.activeElement instanceof HTMLIFrameElement` 为门:焦点移动是跨源 iframe 内 pointerdown 留下的唯一信号,这道门也让应用或标签页切换不会误关列表。 - -**代码预览把复制条与滚动源码分开,并去掉卡片填充。**共享 CodeBlock 用稳定的 `data-code-block-content` 节点包裹渲染后的源码;该节点默认使用 `display: contents`,因此既有消费者保持原布局。[CodeBody.module.css](../../../../packages/client/ui-sidebar-documentpreview/src/client/code/CodeBody.module.css) 将该节点实体化为复制条下方占满剩余高度的内部滚动区;代码渲染器通过 callback ref 报告该节点,使文档 owner 在 Slot 替换后仍能用当前节点恢复位置、分页和跳转代码行。共享 CodeBlock 的填充通过 `--dsl-code-block-background` 设置(默认值不变,会话保持灰色卡片),预览将它设为 `transparent`,代码因此直接使用分栏背景。 - -## Alternatives considered - -**保留出生即播种。**折叠列里躺着没人要的页面,且种子占据每个新停靠面的 0 号位,排在第一次真实打开之前。 - -**保留全停靠面的页去重。**显式"在这里打开"时焦点跳到另一格——正是引发这次改动的抱怨。 - -**菜单用裸的 window blur 关闭。**每次应用或标签页切换都会关掉列表;`activeElement` 门把关闭收窄到父文档看不见的那一种情形。 - -**让代码继续使用共享文档滚动区。**子级复制条无法覆盖父级的原生滚动条。让稳定的源码包装节点同时承载两个滚动轴,滚动条会停在复制条下方的可见视口边缘,而不是长代码块的末端。 - -## Consequences - -`planDropTab` 的工厂参数是任何嵌入方都可传的新套件 API;`planSettle` 本就接受缺省工厂,如今它同时命名了侧边栏的折叠态行为。`--dsl-code-block-background` 变量保留 Chat 默认的灰色卡片,Preview 则使用分栏背景;`data-code-block-content` 允许 owner 实体化专用源码视口,而不改变其它 CodeBlock 布局。套件 planner 规格覆盖带回填的本格分栏及其聚焦顺序;侧边栏 store、service 与 seat 规格覆盖惰性播种、格内页合并与折叠后的空布局;文档预览规格覆盖内部滚动与行跳转;一条 Menu 规格覆盖带门的 blur 关闭。`ui-sidebar-right`、`ui-dockkit` 与 `ui-sidebar-documentpreview` 的 README 重述了这些规则。 diff --git a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.i18n.yaml b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.i18n.yaml index 73ba0975be..d74aa0ba75 100644 --- a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.i18n.yaml @@ -9,11 +9,11 @@ en: 71813b1cb1f750ba zh: 64d2b999933ccd98 /agent-note-web-sidebar-terminals/decision: - en: d73dc0a37bd7c9dd - zh: f5c6cf8cce36090a + en: 8d28949572d990ef + zh: f6e26790415134d2 /agent-note-web-sidebar-terminals/alternatives-considered: - en: 0c129fba63a60777 - zh: c334f34dd71bbaf6 + en: b0ffb94cd5033e9c + zh: 9294bb76503c858a /agent-note-web-sidebar-terminals/consequences: en: d710787595b5dc89 zh: 4df9518c32698f85 diff --git a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.md b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.md index 09be4a142f..448bcf84c1 100644 --- a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.md +++ b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.md @@ -18,7 +18,7 @@ The application theme supplies terminal default colors. The body reads resolved Close and replacement remove the tab synchronously and run process cleanup in the background. The Client first records the unfinished close request under a terminal-specific localStorage key; success removes it, and startup retries requests that remain. Cleanup failures have no notification or manual retry action. Host terminals missing from the saved layout are not reopened automatically and have no UI recovery entry. Independent keys prevent another window from overwriting unrelated cleanup requests. Collapse, tab/Session switching, floating, fullscreen and browser disconnection preserve the process. Component cleanup and `TabDomain.signal` only detach browser work because the same lifetime can end during plugin reload. Failed process cleanup retains ownership, including failures after allocation but before create publication. Session owner disposal and Host plugin disposal also clean up terminals. A definitive missing-Session response retires its saved close request because the Session owns process cleanup; transport failures remain retryable. Client plugin disposal awaits every detached stream so a replacement plugin does not inherit unfinished Client cleanup. -[Sidebar layout persistence and provider recovery](../architecture/2026-09-14-sidebar-layout-provider-recovery.md) owns browser reload: layout and tab identities are restored before the terminal provider reconnects its views. Listing still takes the Session ID directly because history can outlive its Agent and terminal owner; an offline Session has no retained terminals to restore. Only a new view may allocate a process; a recovered target that disappears reports an error. The Host remains authoritative for process state, titles and screen contents. +[Sidebar layout persistence and provider recovery](../../../../packages/client/ui-sidebar-right/README.md) owns browser reload: layout and tab identities are restored before the terminal provider reconnects its views. Listing still takes the Session ID directly because history can outlive its Agent and terminal owner; an offline Session has no retained terminals to restore. Only a new view may allocate a process; a recovered target that disappears reports an error. The Host remains authoritative for process state, titles and screen contents. The caller retains a terminal ID before creating it. Repeating create with the same Session and open ID does not allocate a second process. Closing an uncertain create uses that ID even when no creation response arrived. The Host records closed IDs before awaiting allocation, preventing a delayed create from reviving a closed terminal. Each attachment begins with a consistent, bounded serialized xterm screen; ordered output follows through the Gateway's existing multiplexed Remote stream. Output and screen snapshots share one operation queue. Followers retain final output on normal closure and fail explicitly on buffer overflow. Browser render acknowledgement prevents React batching from dropping increments. @@ -30,7 +30,7 @@ The latest attachment controls input and dimensions; other attachments remain re **Keep the tab visible until process cleanup finishes.** A slow or failed termination would delay the user's close action. Saving the cleanup intent allows immediate removal while preserving failure reporting and retry. -**Persist an authoritative active-process registry.** Browser process state can become stale independently of Host state. The [layout persistence decision](../architecture/2026-09-14-sidebar-layout-provider-recovery.md) supersedes the exclusion of saved tab associations while retaining Host authority for process liveness. +**Persist an authoritative active-process registry.** Browser process state can become stale independently of Host state. The [layout persistence reference](../../../../packages/client/ui-sidebar-right/README.md) supersedes the exclusion of saved tab associations while retaining Host authority for process liveness. **Share the Agent terminal registry.** Its controlled prompts and semantic send/wait behavior would change human shell configuration and blur process ownership. User terminals share the subprocess capability instead. diff --git a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.zh.md b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.zh.md index c2ac146875..81bdf81ed9 100644 --- a/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.zh.md +++ b/.agents/notes/implemented/feature/2026-09-09-web-sidebar-terminal.zh.md @@ -18,7 +18,7 @@ Web 用户需要在 Session 旁使用交互式 shell 检查工作区和运行命 关闭和替换会同步移除标签页,并在后台清理进程。Client 先以终端独立的 localStorage key 保存未完成的关闭请求;成功后删除,启动时重试剩余请求。清理失败没有通知或手动重试操作。保存布局之外的 Host 终端不会自动重新打开,也没有 UI 恢复入口。独立 key 避免其他窗口覆盖无关的清理请求。折叠、切换标签页或 Session、浮动、全屏和浏览器断线均保留进程。组件清理和 `TabDomain.signal` 只停止浏览器工作,因为插件重新加载也会结束这些生命周期。进程清理失败时保留所有权,包括分配完成但 create 尚未发布时的失败。Session owner 和 Host 插件卸载也会清理终端。 明确的 Session 不存在响应会清除已保存的关闭请求,因为进程清理由 Session 负责;传输失败仍可重试。Client 插件卸载等待所有断开的流结束,避免替换插件继承未完成的 Client 清理。 -[侧栏布局持久化与 provider 恢复](../architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md)负责浏览器刷新:先恢复布局和标签身份,再由 terminal provider 重连视图。列表仍直接使用 Session ID,因为历史记录可以比 Agent 和终端 owner 存活更久;离线 Session 没有需要恢复的保留终端。只有新视图可以分配进程,恢复目标消失时显示错误。进程状态、标题和屏幕内容仍以 Host 为准。 +[侧栏布局持久化与 provider 恢复](../../../../packages/client/ui-sidebar-right/README.zh.md)负责浏览器刷新:先恢复布局和标签身份,再由 terminal provider 重连视图。列表仍直接使用 Session ID,因为历史记录可以比 Agent 和终端 owner 存活更久;离线 Session 没有需要恢复的保留终端。只有新视图可以分配进程,恢复目标消失时显示错误。进程状态、标题和屏幕内容仍以 Host 为准。 调用者在创建之前保留终端 ID。相同 Session 和未关闭 ID 的重复 create 不再分配进程。创建结果不确定时,关闭仍使用该 ID,即使没有收到创建响应。Host 在等待分配完成前记录已关闭 ID,防止迟到的 create 复活已关闭终端。每次连接先接收一致、有界的 xterm 序列化屏幕,后续有序输出使用 Gateway 已有的复用 Remote stream。输出和屏幕快照共享操作队列。订阅者正常关闭时保留末尾输出,缓存超限时明确失败。浏览器在完成渲染后确认帧,避免 React 批处理丢失增量。 @@ -30,7 +30,7 @@ Web 用户需要在 Session 旁使用交互式 shell 检查工作区和运行命 **进程清理完成前保留标签页。** 缓慢或失败的终止会拖延用户关闭操作。保存清理意图后,可以立即移除标签页,同时保留错误反馈与重试。 -**持久化作为权威的活跃进程注册表。** 浏览器进程状态可能独立于 Host 状态失效。[布局持久化决策](../architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md)取代了不保存标签关联的限定,同时保留 Host 对进程存活状态的决定权。 +**持久化作为权威的活跃进程注册表。** 浏览器进程状态可能独立于 Host 状态失效。[布局持久化参考](../../../../packages/client/ui-sidebar-right/README.zh.md)取代了不保存标签关联的限定,同时保留 Host 对进程存活状态的决定权。 **共用 Agent 终端注册表。** 受控提示符和语义化 send/wait 会改变人工 shell 配置并混淆进程所有权。用户终端只共享 subprocess 能力。 diff --git a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.i18n.yaml b/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.i18n.yaml deleted file mode 100644 index e5beccba4f..0000000000 --- a/.agents/notes/implemented/feature/2026-09-10-composer-reference-previews.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-composer-reference-previews.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-10-composer-reference-previews.md -/agent-note-composer-reference-previews: - en: 4077f5f4bb6cbbc1 - zh: 0ed641c53ecc9ff9 -/agent-note-composer-reference-previews/problem: - en: b6c93af7ecb4f6df - zh: e6efd6ac2e2193fc -/agent-note-composer-reference-previews/decision: - en: f895747aad30a9ea - zh: 1a736bbf64f82041 -/agent-note-composer-reference-previews/alternatives-considered: - en: c76709c683a56316 - zh: a6708957a465c3ce -/agent-note-composer-reference-previews/consequences: - en: d67f32b4450e8c71 - zh: 7d3a8c6588959e04 diff --git a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml b/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml deleted file mode 100644 index 9a45958b42..0000000000 --- a/.agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-symmetric-message-feedback-submission.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-10-symmetric-message-feedback-submission.md -/agent-note-symmetric-message-feedback-submission: - en: 40f6c11f8f4332a2 - zh: fa31c26c296f892c -/agent-note-symmetric-message-feedback-submission/problem: - en: f5afa64229a1e8db - zh: b73e730754f33cc5 -/agent-note-symmetric-message-feedback-submission/decision: - en: a8a9598cb30f8f8e - zh: 3073219d585bc5fd -/agent-note-symmetric-message-feedback-submission/alternatives-considered: - en: b425d01d854f8962 - zh: f74131c171c9f74a -/agent-note-symmetric-message-feedback-submission/consequences: - en: 833bc3370c02fd1e - zh: 2718d60cdf762436 diff --git a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml b/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml deleted file mode 100644 index fd0a14a09f..0000000000 --- a/.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-desktop-mandatory-update-client.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md -/agent-note-desktop-mandatory-update-client-and-blocking-window: - en: 059254153764a162 - zh: 17dfd84fd2ba6eea -/agent-note-desktop-mandatory-update-client-and-blocking-window/problem: - en: f1aeaa7df97a744f - zh: 039b2cccd9a45198 -/agent-note-desktop-mandatory-update-client-and-blocking-window/decision: - en: 7d8ece211be36774 - zh: d25c3ebac6420187 -/agent-note-desktop-mandatory-update-client-and-blocking-window/alternatives-considered: - en: 136afb04a37db4a1 - zh: 2b049e0d9fd8aa27 -/agent-note-desktop-mandatory-update-client-and-blocking-window/consequences: - en: 6f7a3decf4f0504f - zh: 16241131d1053989 diff --git a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml b/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml deleted file mode 100644 index bffd9997fe..0000000000 --- a/.agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-sidebar-document-preview-polish.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-11-sidebar-document-preview-polish.md -/agent-note-sidebar-document-preview-polish: - en: f6de5780a080c43b - zh: 43c5fe472e41c955 -/agent-note-sidebar-document-preview-polish/problem: - en: 4a52e0a397d95c28 - zh: ecaeb3f570c775da -/agent-note-sidebar-document-preview-polish/decision: - en: 6292b936ed7ee1bb - zh: 1758900c96a6811b -/agent-note-sidebar-document-preview-polish/alternatives-considered: - en: e2e5b8bbd191385e - zh: 29b37f3ec312f5ca -/agent-note-sidebar-document-preview-polish/consequences: - en: 38951cfb55e9214c - zh: 6ca3845e206be2da diff --git a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.i18n.yaml b/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.i18n.yaml deleted file mode 100644 index 26ab339a79..0000000000 --- a/.agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-turn-changed-files-card.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-11-turn-changed-files-card.md -/agent-note-turn-changed-files-card: - en: 21aea14160ab0796 - zh: c8b389f1b684f677 -/agent-note-turn-changed-files-card/problem: - en: f3416b4d37371155 - zh: 52e22bd5a90358e5 -/agent-note-turn-changed-files-card/decision: - en: 75a1a9aa9b204844 - zh: 941fdb3556ebe035 -/agent-note-turn-changed-files-card/alternatives-considered: - en: 7ade7ce52c2a320d - zh: 18832d5f45568429 -/agent-note-turn-changed-files-card/consequences: - en: 33504bcb31f30f84 - zh: c6626b532701fcc7 diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml b/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml deleted file mode 100644 index 05daf52fa7..0000000000 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-mcp-resources-and-instructions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-12-mcp-resources-and-instructions.md -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions: - en: 052fc1f95f280688 - zh: fdfacc841597d4da -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions/problem: - en: 0d6c75e85a04c36a - zh: fe5043f324369cdd -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions/decision: - en: 69a021dd23820bb8 - zh: 447d2d8d03f12896 -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions/alternatives-considered: - en: 02bc8e76ce4d68ab - zh: 0991cc64ff9e3011 -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions/verification: - en: 41694d9121383436 - zh: 781ea7503ea149f1 -/agent-note-on-demand-mcp-resources-and-scoped-server-instructions/consequences: - en: 432b24aa763eff2e - zh: 236825ede84e2c0d diff --git a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml b/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml deleted file mode 100644 index 25b2735cf8..0000000000 --- a/.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-mcp-sdk-protocol-negotiation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.md -/agent-note-mcp-sdk-protocol-negotiation: - en: 9228ca2f4da2f41b - zh: 7cd10429865c3f2d -/agent-note-mcp-sdk-protocol-negotiation/problem: - en: bd614e17c62d137e - zh: ee71fdcd310a9592 -/agent-note-mcp-sdk-protocol-negotiation/decision: - en: ed7b45ffcaeffedd - zh: 2287d525ec04006c -/agent-note-mcp-sdk-protocol-negotiation/alternatives-considered: - en: 1fba2ebdcc100c37 - zh: 45c384641d3b0300 -/agent-note-mcp-sdk-protocol-negotiation/consequences: - en: 1a50179c0254517c - zh: 046e4f72bfc2ea89 diff --git a/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.i18n.yaml b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.i18n.yaml new file mode 100644 index 0000000000..6f3cb5f94d --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-09-13-explicit-worktree-creation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.md +/agent-note-explicit-worktree-creation: + en: 68d9e5eab7671458 + zh: fc889567986f2bbe +/agent-note-explicit-worktree-creation/problem: + en: 12ab239ab0c58e31 + zh: c32874442ce66041 +/agent-note-explicit-worktree-creation/decision: + en: e3be190201a36710 + zh: 9ef0036b9b95377e +/agent-note-explicit-worktree-creation/alternatives-considered: + en: 72d42e892a37b94c + zh: 92a14a51c6a89c53 +/agent-note-explicit-worktree-creation/consequences: + en: 083851db27cc4846 + zh: b3db48d41356c9cf diff --git a/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.md b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.md new file mode 100644 index 0000000000..9b84d0c9fd --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.md @@ -0,0 +1,33 @@ +# Agent Note: Explicit worktree creation + +Status: implemented + +English | [中文](2026-09-13-explicit-worktree-creation.zh.md) + +## Problem + +A Session that needs an isolated checkout otherwise has to coordinate Git commands with its own working directory. Changing a shell process directory does not update the Session's later tools or recorded context, and a linked worktree's Git administration directory can lie outside the Session's existing write grant. + +## Decision + +The experimental [worktree runtime](../../../../packages/experimental/worktree/README.md) creates a new named branch and checkout from a pinned local commit, then enters its canonical directory through the shared working-directory service. The [tool](../../../../packages/experimental/tool-worktree/README.md) exposes `create_worktree({ name?, from? })`. These packages use ordinary explicit composition, with no additional runtime feature flag. + +The source repository comes from the calling Session's current directory. Creation does not fetch or copy uncommitted files. The default pool is `.agents/worktrees` under the current checkout’s top-level directory, including when the caller is in a linked checkout. The process that creates a pool writes its self-ignoring `.gitignore`; existing directories and ignore files are preserved. Names must be new branches and unused checkout paths. + +Git and directory creation use the mounted subprocess and sandbox providers without widening the calling Session's policy. Both destination and shared Git metadata writes must already be permitted. Successful creation changes only the current working directory; the Session identity and original sandbox root remain owned by their existing services. + +Leaving uses the shared working-directory operation and retains the checkout and branch. Failed setup can retain partial artifacts and reports their location. The service does not own a checkout registry, cleanup schedule, branch merge policy, or task association database. + +## Alternatives considered + +**A separate worktree-exit tool.** Leaving is the existing working-directory operation; another exit operation would needlessly couple directory changes to Git ownership or deletion. + +**Copy the dirty checkout into each new worktree.** A pinned local commit gives the caller an explicit baseline without mixing staged, unstaged, untracked, and ignored state or inventing conflict rules. + +**Automatic subagent isolation.** [Agent Teams](2026-08-05-agent-teams.md) keeps its existing explicit coordination model. Creating a checkout is available as an operation rather than an implicit consequence of delegation. + +**Expand write grants during creation.** A convenience operation cannot acquire authority over a shared Git administration directory merely because the destination is writable. + +## Consequences + +The caller can continue one Session in a new checkout with a reconstructable directory change. Retention avoids deleting work on exit or a failed final publication, but callers must manage unused checkouts and branches themselves. Git and Node must be available in the filesystem provider's execution world. Submodule setup and dependency installation remain separate operations. diff --git a/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.zh.md b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.zh.md new file mode 100644 index 0000000000..f93a4a2cd7 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-13-explicit-worktree-creation.zh.md @@ -0,0 +1,33 @@ +# Agent Note: 显式创建工作树 + +Status: implemented + +[English](2026-09-13-explicit-worktree-creation.md) | 中文 + +## Problem + +需要隔离检出目录的 Session 原本必须自行协调 Git 命令与工作目录。改变 shell 进程目录不会更新 Session 后续工具或已记录的上下文,而且链接工作树的 Git 管理目录可能位于 Session 既有写权限范围之外。 + +## Decision + +实验性[工作树运行时](../../../../packages/experimental/worktree/README.zh.md)从固定的本地提交创建新的具名分支与检出目录,然后通过共享工作目录服务进入其规范路径。[工具](../../../../packages/experimental/tool-worktree/README.zh.md)公开 `create_worktree({ name?, from? })`。这些包使用普通的显式组合,不添加额外运行时功能开关。 + +源仓库由调用 Session 的当前目录决定。创建操作不会获取远端提交或复制未提交文件。默认父目录是当前检出目录顶层路径下的 `.agents/worktrees`,调用方位于链接检出目录中时也一样。创建该目录的进程写入忽略自身的 `.gitignore`;已有目录与忽略文件保持不变。名称必须对应新分支与尚未使用的检出路径。 + +Git 与目录创建使用挂载的子进程及沙箱提供方,不扩大调用 Session 的策略。目标目录与共享 Git 元数据必须均已允许写入。成功创建只改变当前工作目录;Session 身份与原始沙箱根目录仍由已有服务拥有。 + +离开使用共享工作目录操作,保留检出目录与分支。初始化失败可能留下部分产物,并报告其位置。该服务不拥有工作树注册表、清理计划、分支合并策略或任务关联数据库。 + +## Alternatives considered + +**单独提供工作树退出工具。** 离开就是已有工作目录操作;另一个退出操作会无谓地将目录切换与 Git 所有权或删除耦合。 + +**将未提交状态复制到每个新工作树。** 固定本地提交为调用方提供显式基准,无需混合暂存、未暂存、未跟踪和被忽略的状态,也无需制定冲突规则。 + +**自动隔离子代理。** [Agent Teams](2026-08-05-agent-teams.zh.md)保留已有的显式协调模型。创建检出目录是一项可调用操作,而不是委派的隐含结果。 + +**创建时扩大写权限。** 便捷操作不能仅因目标目录可写,就获得共享 Git 管理目录的权限。 + +## Consequences + +调用方可以在新的检出目录继续同一个 Session,且目录切换可重建。保留策略避免在离开或最后发布失败时删除工作,但调用方必须自行管理闲置检出目录与分支。Git 与 Node 必须存在于文件系统提供方的执行环境中。子模块配置与依赖安装仍是独立操作。 diff --git a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml b/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml deleted file mode 100644 index ccb4558c12..0000000000 --- a/.agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-13-macos-hidden-titlebar-vibrancy.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-13-macos-hidden-titlebar-vibrancy.md -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar: - en: 1872fd56128fb565 - zh: dc7b04d80eda9880 -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar/problem: - en: 199f00b0e7f3c09c - zh: bb331fc8d7d46bbd -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar/decision: - en: f59c3d276d0fb5c0 - zh: 85509dca9810cd4d -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar/alternatives-considered: - en: 28d920253012bd11 - zh: ae6a27f959d5afe3 -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar/consequences: - en: e08ce133c72863d8 - zh: e67b5c3bff579d86 -/agent-note-macos-hidden-titlebar-with-vibrancy-sidebar/testing: - en: a075febd9b78dc5f - zh: 7c59a9b7f0798dac diff --git a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml b/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml deleted file mode 100644 index 854c136a37..0000000000 --- a/.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-13-mcp-resources-in-profiles.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.md -/agent-note-mcp-resource-availability-follows-configured-servers: - en: 2f520034ae7711ed - zh: 0e76516042ef51c1 -/agent-note-mcp-resource-availability-follows-configured-servers/problem: - en: 729c308d0bb4e142 - zh: 42df44189f83a701 -/agent-note-mcp-resource-availability-follows-configured-servers/decision: - en: 6d79d22b7a2d118a - zh: 9e7aaccbcb6535c0 -/agent-note-mcp-resource-availability-follows-configured-servers/alternatives-considered: - en: e60cb2c45a8a8cf7 - zh: e74f3b9ca5157278 -/agent-note-mcp-resource-availability-follows-configured-servers/verification: - en: d07e735ad317655c - zh: c9419acac96f0740 -/agent-note-mcp-resource-availability-follows-configured-servers/consequences: - en: 270341820e2713d4 - zh: d56cdd72625fd570 diff --git a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.i18n.yaml b/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.i18n.yaml deleted file mode 100644 index ce405a2db5..0000000000 --- a/.agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-composer-selection-keyboard.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-14-composer-selection-keyboard.md -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys: - en: d3af1337a24bd411 - zh: ebf464a7f3d9b5c3 -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys/problem: - en: e5f4084d8eda4d0e - zh: 4dc98999d008fecd -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys/decision: - en: d6a84d4b3d935fbb - zh: b842b20d197fc1a6 -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys/alternatives-considered: - en: f062131fb8b9cbcf - zh: a14b718ed6381b3f -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys/verification: - en: 81508f6216c1cde0 - zh: 764a8cafea8594e5 -/agent-note-composer-selection-surfaces-mirror-the-composer-s-enter-and-escape-keys/consequences: - en: 1310807e3a7a259d - zh: c35aca580ee553b7 diff --git a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.i18n.yaml b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.i18n.yaml index 683d995312..ba3e9c8d4d 100644 --- a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.i18n.yaml @@ -9,8 +9,8 @@ en: 36bf235f0821ef6a zh: a86bf07af03d54a3 /agent-note-desktop-primary-runtime/decision: - en: 3d2241842f5a2a83 - zh: 8617717f0337d76c + en: 594ed7501283de88 + zh: d2abc271a96a82d7 /agent-note-desktop-primary-runtime/alternatives-considered: en: 566fd91c30cb0a0a zh: 70758ae2617f6ebb diff --git a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.md b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.md index 9c961517b4..7778eee564 100644 --- a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.md +++ b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.md @@ -22,7 +22,7 @@ Windows signed packaging separates materialization from execution with supervise Desktop ZIP extraction pins `extract-zip` to `yauzl` 3.4.0 through a scoped dependency override. The 2.x reader can leave large deflate entries unfinished on Node 26 ([upstream issue](https://github.com/thejoshwolfe/yauzl/issues/176)); retaining the existing extractor preserves its path validation and wheel-entry checks. The development launcher uses top-level await so unfinished preparation cannot exit successfully. A large compressed wheel regression checks the complete extracted bytes. -The [shared-runtime decision](../architecture/2026-09-17-shared-office-runtime.md) owns builder, lock and query-package placement for Desktop and SDK carriers, including GNU/Linux x64 and Python-only payloads. Desktop retains the installation and signing behavior described here and requires the full Python, Node.js and pnpm payload. +The [shared-runtime reference](../../../../packages/skill/tool-workspace-dependencies/README.md) owns builder, lock and query-package placement for Desktop and SDK carriers, including GNU/Linux x64 and Python-only payloads. Desktop retains the installation and signing behavior described here and requires the full Python, Node.js and pnpm payload. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.zh.md b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.zh.md index c2f1a4f105..05ad34b343 100644 --- a/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.zh.md +++ b/.agents/notes/implemented/feature/2026-09-14-desktop-primary-runtime.zh.md @@ -22,7 +22,7 @@ Windows 签名打包通过受监督的签名阶段覆盖第一方运行时和应 Desktop ZIP 解压通过定向依赖覆盖为 `extract-zip` 固定 `yauzl` 3.4.0。2.x 读取器在 Node 26 上可能无法完成较大 deflate 条目的读取([上游问题](https://github.com/thejoshwolfe/yauzl/issues/176));保留现有解压器可保留其路径校验和 wheel 条目检查。开发启动器使用顶层 await,避免准备未完成却成功退出。大压缩 wheel 回归测试检查完整的解压字节。 -[共享运行时决策](../architecture/2026-09-17-shared-office-runtime.zh.md)持有 Desktop 与 SDK 载体的构建器、锁文件和查询包归属,覆盖 GNU/Linux x64 与 Python-only payload。Desktop 保留本文的安装与签名行为,并要求完整的 Python、Node.js 和 pnpm payload。 +[共享运行时参考](../../../../packages/skill/tool-workspace-dependencies/README.zh.md)持有 Desktop 与 SDK 载体的构建器、锁文件和查询包归属,覆盖 GNU/Linux x64 与 Python-only payload。Desktop 保留本文的安装与签名行为,并要求完整的 Python、Node.js 和 pnpm payload。 ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.i18n.yaml b/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.i18n.yaml deleted file mode 100644 index f43d81c749..0000000000 --- a/.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-14-docs-mermaid-viewer.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md -/agent-note-documentation-diagram-and-image-viewer: - en: c43de97ff7fbe82d - zh: 5e2ecb9d39255ad7 -/agent-note-documentation-diagram-and-image-viewer/problem: - en: 3630dd02a8e8dfdf - zh: f25b00903e05bd05 -/agent-note-documentation-diagram-and-image-viewer/decision: - en: e82d48a7ebee05c8 - zh: af5c517d1d728f07 -/agent-note-documentation-diagram-and-image-viewer/alternatives-considered: - en: bdebec164a27c787 - zh: 8acb59417c96b807 -/agent-note-documentation-diagram-and-image-viewer/consequences: - en: f7bc4c0588dda49d - zh: 9261c0bde08031b1 diff --git a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.i18n.yaml b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.i18n.yaml index 11e6f533a5..fb8f1db3e7 100644 --- a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.i18n.yaml @@ -9,8 +9,8 @@ en: bd5cfc222df1a98c zh: b58a3570a98298ee /agent-note-two-hour-reclamation-of-unattended-browser-terminals/decision: - en: 079c8a1e95099239 - zh: 613d888fd7bf6b61 + en: b5ba6ad815f8255f + zh: 5da86fa7f2f26840 /agent-note-two-hour-reclamation-of-unattended-browser-terminals/alternatives-considered: en: 165fedee22bdb52e zh: 6b9b7054ddb2a3a9 diff --git a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.md b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.md index 6eca6faa22..a249df0ef4 100644 --- a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.md +++ b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.md @@ -33,7 +33,7 @@ The Host [terminal controller](../../../../packages/api/terminal-controller/READ Cleanup awaits provider quiescence and final screen output. Failure retains ownership, rejects new holders and schedules one retry without a new idle grace. Failed allocation cleanup follows the same retry policy. Owner disposal stops timers and streams and joins cleanup and observations before reporting failures, including when cleanup rejects before an observation settles. No Agent terminal tool, model input or Session event changes. -Peer research on Codex thread unloading, OpenCode Location scopes and [VS Code PTY grace periods](https://github.com/microsoft/vscode/blob/main/src/vs/platform/terminal/node/ptyService.ts) informed separate references and delayed cleanup. The two-hour value is DSH product policy, not an industry default. The [browser-terminal decision](2026-09-09-web-sidebar-terminal.md) and [layout/provider recovery decision](../architecture/2026-09-14-sidebar-layout-provider-recovery.md) remain active because their resource ownership and persistence separation still apply. +Peer research on Codex thread unloading, OpenCode Location scopes and [VS Code PTY grace periods](https://github.com/microsoft/vscode/blob/main/src/vs/platform/terminal/node/ptyService.ts) informed separate references and delayed cleanup. The two-hour value is DSH product policy, not an industry default. The [browser-terminal decision](2026-09-09-web-sidebar-terminal.md) and [layout/provider recovery reference](../../../../packages/client/ui-sidebar-right/README.md) define the resource ownership and persistence separation used here. ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.zh.md b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.zh.md index 9065fe53a7..268709124c 100644 --- a/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.zh.md +++ b/.agents/notes/implemented/feature/2026-09-14-unattended-browser-terminal-reclamation.zh.md @@ -33,7 +33,7 @@ Host [终端控制器](../../../../packages/api/terminal-controller/README.zh.md 清理等待 provider 范围完全停稳和最终屏幕输出排空。失败时保留所有权、拒绝新持有关系,并安排一次重试,不重新给予空闲宽限期。分配失败后的清理采用相同重试策略。owner 卸载会停止计时器和流,在清理与观察都结束后才报告失败,包括清理早于观察结束而拒绝的情况。Agent 终端工具、模型输入和 Session 事件均不改变。 -Codex 线程卸载、OpenCode Location scope 和 [VS Code PTY 宽限期](https://github.com/microsoft/vscode/blob/main/src/vs/platform/terminal/node/ptyService.ts) 的调研支持了独立引用和延迟清理的设计。两小时是 DSH 的产品策略,不是行业默认值。[浏览器终端决策](2026-09-09-web-sidebar-terminal.zh.md) 与[布局/provider 恢复决策](../architecture/2026-09-14-sidebar-layout-provider-recovery.zh.md) 继续有效,因为其资源所有权和持久化职责划分仍然适用。 +Codex 线程卸载、OpenCode Location scope 和 [VS Code PTY 宽限期](https://github.com/microsoft/vscode/blob/main/src/vs/platform/terminal/node/ptyService.ts) 的调研支持了独立引用和延迟清理的设计。两小时是 DSH 的产品策略,不是行业默认值。[浏览器终端决策](2026-09-09-web-sidebar-terminal.zh.md) 与[布局/provider 恢复参考](../../../../packages/client/ui-sidebar-right/README.zh.md) 定义此处沿用的资源所有权和持久化职责划分。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.i18n.yaml deleted file mode 100644 index d220a04f6f..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-bundled-office-skills.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-bundled-office-skills.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-bundled-office-skills.md -/agent-note-bundled-office-skills-with-structural-verification: - en: 8cc7012d7343c387 - zh: 8565543be5d94a56 -/agent-note-bundled-office-skills-with-structural-verification/problem: - en: d1e1e74b0fa286de - zh: 61ce855e4726d4a3 -/agent-note-bundled-office-skills-with-structural-verification/decision: - en: 486c2b40a0a4fe3c - zh: 317f47318b27e8f8 -/agent-note-bundled-office-skills-with-structural-verification/alternatives-considered: - en: c5389aef638deb0c - zh: a11a99a269e81c6a -/agent-note-bundled-office-skills-with-structural-verification/consequences: - en: a6035aefa4443df3 - zh: f87c6f099e4f3c7d diff --git a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.i18n.yaml deleted file mode 100644 index b52514834c..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-changed-file-diff-preview.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-changed-file-diff-preview.md -/agent-note-turn-review-tab: - en: cdcf921443075122 - zh: 177609e61939922a -/agent-note-turn-review-tab/problem: - en: 91d904d836519a4b - zh: c0aec4e184ff924b -/agent-note-turn-review-tab/decision: - en: 8169e63f8bf8416d - zh: 24c1e3fb394983e3 -/agent-note-turn-review-tab/alternatives-considered: - en: e648f82b78a2c0d3 - zh: f4e6511766ace168 -/agent-note-turn-review-tab/consequences: - en: 2147596d8c6574da - zh: db8b2e1ce86465df diff --git a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.i18n.yaml deleted file mode 100644 index a373848ad2..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-continuable-activation-capacity.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-continuable-activation-capacity.md -/agent-note-shared-continuable-activation-capacity: - en: 56a51d29c97b2c8e - zh: c99085a3b2deb97c -/agent-note-shared-continuable-activation-capacity/problem: - en: f940cc05cf90362d - zh: 1cdb4281d845c0da -/agent-note-shared-continuable-activation-capacity/decision: - en: e03ae0d516885585 - zh: bd81d197e097b1ee -/agent-note-shared-continuable-activation-capacity/alternatives-considered: - en: 54aefa812e00ce1e - zh: 08251d541b7991df -/agent-note-shared-continuable-activation-capacity/consequences: - en: 4792e84eeaac32ee - zh: 4c941792d03130a0 -/agent-note-shared-continuable-activation-capacity/verification: - en: f599edb8fb0112af - zh: a66f9102a79234f0 diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.i18n.yaml deleted file mode 100644 index 87e8807409..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-docs-page-markdown-actions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-docs-page-markdown-actions.md -/agent-note-page-markdown-actions-in-the-documentation-site: - en: 135670d1e466e923 - zh: b071c98c104c6aa5 -/agent-note-page-markdown-actions-in-the-documentation-site/problem: - en: d8c4d91741fe4a3f - zh: 0dae755314b52d9d -/agent-note-page-markdown-actions-in-the-documentation-site/decision: - en: b02d9faf0f2dfa91 - zh: 994a9755e0ddf16c -/agent-note-page-markdown-actions-in-the-documentation-site/alternatives-considered: - en: 96b23eb170e4edf2 - zh: 63e91e6c425afe1b -/agent-note-page-markdown-actions-in-the-documentation-site/consequences: - en: 57a6d0707d1793cd - zh: b63d4f3f82f8c38f diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.i18n.yaml deleted file mode 100644 index ceadaacf73..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-docs-platform-command-tabs.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.md -/agent-note-platform-command-tabs-in-the-python-sdk-guide: - en: 3d96f04869d2a2fb - zh: 4a6ebfb3ccf9c69d -/agent-note-platform-command-tabs-in-the-python-sdk-guide/problem: - en: 8fb6ad2440352a06 - zh: 0809a5c9866e52ee -/agent-note-platform-command-tabs-in-the-python-sdk-guide/decision: - en: 0ec36e0eccea6209 - zh: 5df6069c60a55f31 -/agent-note-platform-command-tabs-in-the-python-sdk-guide/alternatives-considered: - en: a98aa2b8d03f11aa - zh: 0aefaa161a20a24e -/agent-note-platform-command-tabs-in-the-python-sdk-guide/consequences: - en: f3af5502a73e7725 - zh: 4ca34dfd993fcc21 diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.md b/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.md deleted file mode 100644 index 4d44d1191a..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Platform command tabs in the Python SDK guide - -Status: implemented - -English | [中文](2026-09-15-docs-platform-command-tabs.zh.md) - -## Problem - -The [Python SDK guide](../../../../docs/user/guide/python-sdk.md) gives different commands for POSIX shells and Windows PowerShell. Showing both versions in sequence makes readers skip an alternative at each step and can make alternative commands look cumulative. - -## Decision - -Each of the four platform-dependent steps uses VitePress's native `code-group`: Linux/macOS first, Windows PowerShell second. Each group selects independently and starts on Linux/macOS. Credential setup and running the example remain separate sequential steps. Shared Python and YAML examples stay directly visible. - -Both languages retain every command in canonical Markdown. The documentation projector preserves the groups and their code in the website's raw Markdown. Explicit anchors before each group preserve the former platform heading targets; following a platform anchor locates the group without selecting a tab. - -The theme adds a visible keyboard focus outline to native radio labels and displays both command blocks in search excerpts, whose tab controls are unavailable. A Markdown renderer adapter makes each native tab strip a form with submission prevented. Local search copies a section's rendered HTML, including radio names; separate form owners stop those copies from clearing the page's selection. VitePress owns selection, syntax highlighting, and copying the active code block. MPA builds omit those client handlers, so build-only styles display both command blocks and hide the tab strip and copy buttons. There is no shared selection state or stored preference. - -## Alternatives considered - -**Keep vertically repeated platform sections.** Both versions are immediately visible, but the repeated alternatives interrupt the guide's ordered steps. - -**Build a custom tab component with shared or persistent selection.** A Windows reader could select once, but shared state couples independent examples and introduces selection and lifecycle code beyond the required presentation. Native groups keep the interaction local. - -## Consequences - -SPA readers see one command version per step while MPA, repository, and raw-Markdown readers retain both. Windows readers select PowerShell in each group. Existing platform links still reach the corresponding step, although the platform names leave the page outline. - -Verification covers command preservation, bilingual pairing, projected and raw Markdown, and actual browser switching, copying, keyboard focus, search, themes, and narrow layouts. Framework-owned interaction is exercised on the running site; session replay is inapplicable to this documentation-only presentation. - -The focused renderer regression checks radio ownership when local search duplicates a section. The adapter depends on VitePress's native tab-strip markup and fails the build if that markup changes; a framework upgrade must retain the search isolation and browser checks. diff --git a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.zh.md b/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.zh.md deleted file mode 100644 index 450765b52d..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Python SDK 指南的平台命令标签 - -Status: implemented - -[English](2026-09-15-docs-platform-command-tabs.md) | 中文 - -## 问题 - -[Python SDK 指南](../../../../docs/user/guide/python-sdk.zh.md)分别提供 POSIX shell 和 Windows PowerShell 命令。连续显示两个版本使读者在每一步都要跳过另一平台,也容易把二选一的命令理解为需要依次执行。 - -## 决策 - -四个平台相关步骤分别使用 VitePress 原生 `code-group`:Linux/macOS 在前,Windows PowerShell 在后。各组独立选择,初始均显示 Linux/macOS。配置凭据和运行示例仍是两个顺序步骤。共用的 Python 和 YAML 示例保持直接可见。 - -两种语言均在规范 Markdown 中保留每条命令。文档投影器在网站原始 Markdown 中保留分组及代码。各组前的显式锚点保留原有平台标题的链接目标;平台锚点定位到分组,但不选择标签。 - -主题为原生单选按钮标签补充可见的键盘焦点轮廓,并在无法操作标签的搜索摘要中显示两个命令块。Markdown 渲染适配器将各原生标签栏设为禁止提交的表单。本地搜索会复制章节渲染后的 HTML,其中包含单选按钮名称;独立的所属表单可避免副本清除页面的选择。VitePress 负责选择、语法高亮和复制当前代码块。MPA 构建不包含这些客户端处理器,因此构建专用样式显示两个命令块,并隐藏标签栏与复制按钮。不引入共享选择状态或持久化偏好。 - -## 考虑过的替代方案 - -**保留纵向重复的平台章节。** 两个版本都立即可见,但重复的备选命令会打断指南的顺序步骤。 - -**自建标签组件并共享或持久化选择。** Windows 读者只需选择一次,但共享状态会耦合独立示例,并引入超出所需展示功能的选择与生命周期代码。原生分组将交互限定在各组内。 - -## 影响 - -SPA 读者每一步只看到一个命令版本,MPA、仓库和原始 Markdown 读者仍能获取两个版本。Windows 读者需要在各组中选择 PowerShell。原有平台链接仍到达对应步骤,但平台名称不再出现在页面大纲中。 - -验证覆盖命令保留、双语配对、投影和原始 Markdown,以及真实浏览器中的切换、复制、键盘焦点、搜索、主题和窄屏布局。框架负责的交互在运行中的站点验收;会话回放不适用于这项纯文档展示变更。 - -聚焦的渲染回归验证本地搜索复制章节时的单选按钮归属。适配器依赖 VitePress 原生标签栏的 HTML,结构变化时会使构建失败;框架升级必须保留搜索隔离和浏览器检查。 diff --git a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.i18n.yaml deleted file mode 100644 index 43efd357fe..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-markdown-file-preview-links.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-markdown-file-preview-links.md -/agent-note-markdown-file-preview-links: - en: 98c528496f33598e - zh: 640619a2dfa0255e -/agent-note-markdown-file-preview-links/problem: - en: ba7789f83fadbe95 - zh: 34bcaa0aed5f5c3b -/agent-note-markdown-file-preview-links/decision: - en: b986fe2640c2ebf4 - zh: e69700b233e7bd69 -/agent-note-markdown-file-preview-links/alternatives-considered: - en: d8a4d6ab17eb2880 - zh: 089419d53f71305a -/agent-note-markdown-file-preview-links/consequences: - en: c0fc2dd3d860edfa - zh: 129aa543b4a39e10 diff --git a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.i18n.yaml deleted file mode 100644 index 5665220ce2..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-profile-command-shorthand.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-profile-command-shorthand.md -/agent-note-profile-command-shorthand: - en: 35f20a1162b8407f - zh: 33d263d534df0b1d -/agent-note-profile-command-shorthand/problem: - en: b5c950343e5fbe8c - zh: df2a055d2c0b3f53 -/agent-note-profile-command-shorthand/decision: - en: cc8d5ebd595fee34 - zh: 99cb3ade0f17fb7b -/agent-note-profile-command-shorthand/alternatives-considered: - en: 8d10eee94348e187 - zh: 5930dee3861058e1 -/agent-note-profile-command-shorthand/consequences: - en: bbfeb6e59bb8ba55 - zh: fc33289f717cb805 diff --git a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.i18n.yaml b/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.i18n.yaml deleted file mode 100644 index 5f0daf0064..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-sidebar-workspace-hierarchy.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.md -/agent-note-sidebar-workspace-hierarchy: - en: 6be9fd74b0422158 - zh: ba6dbc81479b75b5 -/agent-note-sidebar-workspace-hierarchy/problem: - en: dc209dd9d8c73af4 - zh: cc5384ba15ea0be0 -/agent-note-sidebar-workspace-hierarchy/decision: - en: 9f34c1e903475d25 - zh: 6b22b19f6da3d390 -/agent-note-sidebar-workspace-hierarchy/alternatives-considered: - en: b30322d71fc7c464 - zh: 36b218a82690cd19 -/agent-note-sidebar-workspace-hierarchy/consequences: - en: bf890a80351fa883 - zh: 05da81c7026a5204 diff --git a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.md b/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.md deleted file mode 100644 index 82e63684ec..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Sidebar Workspace Hierarchy - -Status: implemented - -English | [中文](2026-09-15-sidebar-workspace-hierarchy.zh.md) - -## Problem - -A flat Workspace list makes related projects hard to browse when many directories share one parent. Treating that parent as the owner of every descendant Session would conflict with the Workspace requirement that a member Session's canonical working directory equals the registered path. - -## Decision - -The sidebar defaults to sibling Workspace sections. Selecting **Workspace Tree** in **View options → Group by** derives a recursive hierarchy from registered Workspace paths. The selected mode persists in the existing browser-local viewing store. Each Workspace appears under its nearest strict ancestor; path equality never creates a self-parent. Comparison respects directory separators and Host case spelling, without resolving symlink aliases. Siblings retain their Host order. - -Adding a directory registers a normal Workspace and opens its Session directly. Parent Workspaces retain their own Sessions and standard row actions, with child Workspaces preceding their own Sessions. The existing browser-local expansion state controls both child Workspaces and the parent's Session rows; ancestors default to expanded when no explicit preference exists. Explicit collapse also hides the current Session, while ancestor folder icons identify its containing subtree. All rows have equal-width fills and hit targets, with content indentation per level. Workspace drag stays among siblings; descendant drop targets delegate to the nearest compatible ancestor, and unchanged sibling positions do not write Host order. Search navigation expands every ancestor. - -## Alternatives considered - -**Tree grouping by default:** this hides projects under ancestors even when users want to browse every Workspace directly. An explicit viewing mode preserves the default layout without adding a confirmation to directory adoption. - -**Recursive Workspace membership:** this changes the working-directory invariant and makes a Session eligible for multiple accounts. Display nesting needs neither change. - -**Separate parent-folder records and an add-time choice:** this duplicates directories already represented by Workspaces and adds a confirmation to ordinary addition. Deriving hierarchy from the registry preserves one add flow and lets every directory own Sessions. - -**Automatic directory discovery:** this could show unregistered projects, but requires filesystem listing, refresh, and adoption behavior. The sidebar consumes the current Workspace list only. - -## Consequences - -Users can collapse related projects without changing Session ownership. Deleting an ancestor preserves child registrations; the independent [Workspace deletion semantics](2026-07-27-workspace-registration-deletion.md) still govern the deleted Workspace's own Sessions. That decision remains active. Browser-local collapse preferences do not synchronize, and registered path spelling after separator normalization determines nesting. - -Pure path tests cover strict ancestry, segment boundaries, POSIX backslashes, and Windows separators. Component and browser scenarios exercise default sibling grouping, tree-mode selection and restoration, direct addition, later registrations, independent parent Sessions, collapse restoration, search revelation, sibling sorting through descendant targets, parent-subtree moves, collapsed-current hints, and equal-width rows. diff --git a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.zh.md b/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.zh.md deleted file mode 100644 index 07f9ff89b6..0000000000 --- a/.agents/notes/implemented/feature/2026-09-15-sidebar-workspace-hierarchy.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: 侧栏工作区层级 - -Status: implemented - -[English](2026-09-15-sidebar-workspace-hierarchy.md) | 中文 - -## 问题 - -许多目录共享同一父目录时,平铺的 Workspace 列表不便浏览相关项目。若让父目录拥有所有后代 Session,则会违背 Workspace 成员 Session 的规范工作目录必须等于注册路径这一要求。 - -## 决策 - -侧栏默认将 Workspace 显示为同级分组。在**视图选项 → 分组方式**中选择**按工作区树**后,侧栏从已注册的 Workspace 路径派生递归层级。所选模式保存在现有的浏览器本地视图存储中。每个 Workspace 位于最近的严格祖先下;相同路径不会成为自己的父级。比较遵循目录分隔符和 Host 的大小写拼写,不解析符号链接别名。同级项目保留 Host 顺序。 - -添加目录会直接注册普通 Workspace 并打开其 Session。父 Workspace 保留自己的 Session 和标准行操作,子 Workspace 位于父级自己的 Session 之前。现有的浏览器本地展开状态同时控制子 Workspace 和父级自己的 Session 行;没有显式偏好时,祖先默认展开。显式折叠也会隐藏当前 Session,但祖先文件夹图标会标识它所在的子树。所有行的高亮与点击区域等宽,内容按层级缩进。Workspace 拖拽限制在同级之间;后代落点交给最近的兼容祖先,同级位置未变时不写入 Host 顺序,搜索导航会展开全部祖先。 - -## 考虑过的替代方案 - -**默认采用树形分组:**这会把项目隐藏在祖先之下,即使用户希望直接浏览全部 Workspace。显式视图模式保留默认布局,也不为目录添加流程增加确认。 - -**递归 Workspace 成员关系:**这会改变工作目录不变量,并让一个 Session 同时符合多个记账。展示嵌套不需要这两项改变。 - -**独立父目录记录与添加时选择:**这会重复表示已有 Workspace 目录,并为普通添加增加确认。根据注册表派生层级可保留单一添加流程,并让每个目录都能拥有 Session。 - -**自动发现目录:**这能展示尚未注册的项目,但需要文件系统列举、刷新和采用行为。侧栏仅使用当前 Workspace 列表。 - -## 结果 - -用户可以折叠相关项目,而不改变 Session 归属。删除祖先会保留子工作区注册;独立的 [Workspace 删除语义](2026-07-27-workspace-registration-deletion.zh.md)仍决定被删除 Workspace 自身 Session 的处理,该决策继续有效。浏览器本地折叠偏好不跨浏览器同步,嵌套取决于分隔符归一化后的注册路径拼写。 - -纯路径测试覆盖严格祖先关系、路径段边界、POSIX 反斜杠和 Windows 分隔符。组件与浏览器场景覆盖默认同级分组、树形模式选择与恢复、直接添加、后续注册、父级独立 Session、折叠恢复、搜索展开、通过后代落点进行同级排序、父级子树移动、折叠当前会话提示及整行等宽。 diff --git a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.i18n.yaml index caa8355929..6646c5a4a1 100644 --- a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.i18n.yaml @@ -12,8 +12,8 @@ en: f962826fcf5e3949 zh: 8a9932d86332fb47 /agent-note-browser-excel-preview-with-saved-formula-results/alternatives-considered: - en: 8d7ac06eaa3ba42c - zh: b6e43ae09e4df4fa + en: 0747bca32af26bfc + zh: 1d2739208e7c7d7c /agent-note-browser-excel-preview-with-saved-formula-results/consequences: en: 62595b9a9b948422 zh: 0618904d9ebfa7fc diff --git a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.md b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.md index 8a4bbd5dc8..435d7e4f46 100644 --- a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.md +++ b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.md @@ -36,7 +36,7 @@ The core patch also skips drawing freeze dividers when `allowEdit === false`, re **Replace ExcelJS or require users to rewrite their files.** A parser replacement puts retained fonts, borders, merges, and frozen panes at risk. Rewriting a source file makes a viewer defect the user's responsibility. A temporary preview copy preserves both the existing adapter and the user's file. -**Keep PDF as an Excel fallback or calculate every formula on import.** PDF retains neither spreadsheet interaction nor formula inspection, while recalculation can change saved results. The [Office engine decision](../architecture/2026-09-11-node-office-kit.md) remains applicable to Word/PowerPoint previews and independent conversion consumers; its conversion capability still supports spreadsheets. +**Keep PDF as an Excel fallback or calculate every formula on import.** PDF retains neither spreadsheet interaction nor formula inspection, while recalculation can change saved results. The [Office engine reference](../../../../packages/document/office-to-pdf/README.md) remains applicable to Word/PowerPoint previews and independent conversion consumers; its conversion capability still supports spreadsheets. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.zh.md b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.zh.md index f04170b28f..e4dbd8843a 100644 --- a/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.zh.md +++ b/.agents/notes/implemented/feature/2026-09-16-browser-excel-preview.zh.md @@ -36,7 +36,7 @@ core 补丁还在 `allowEdit === false` 时跳过冻结分隔线的绘制,保 **替换 ExcelJS 或要求用户重写文件。** 更换解析器会使现有字体、边框、合并及冻结窗格的保留面临风险。重写源文件则把查看器缺陷交给用户处理。临时预览副本同时保留现有适配器和用户文件。 -**保留 PDF 作为 Excel 兜底,或在导入时计算所有公式。** PDF 无法保留表格交互与公式查看,而重新计算可能改变已保存结果。[Office 引擎决策](../architecture/2026-09-11-node-office-kit.zh.md)仍适用于 Word/PowerPoint 预览与独立转换使用方;转换能力仍支持表格。 +**保留 PDF 作为 Excel 兜底,或在导入时计算所有公式。** PDF 无法保留表格交互与公式查看,而重新计算可能改变已保存结果。[Office 引擎参考](../../../../packages/document/office-to-pdf/README.zh.md)仍适用于 Word/PowerPoint 预览与独立转换使用方;转换能力仍支持表格。 ## 影响 diff --git a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.i18n.yaml deleted file mode 100644 index 636682a60e..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-desktop-onboarding.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-desktop-onboarding.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-desktop-onboarding.md -/agent-note-device-local-desktop-onboarding: - en: 84a5519665d72899 - zh: beaadffa0e139459 -/agent-note-device-local-desktop-onboarding/problem: - en: 50c7c883a2115b7e - zh: 60533d618a3b2da7 -/agent-note-device-local-desktop-onboarding/decision: - en: e228d2a308f8ad8a - zh: c5eee26119226f32 -/agent-note-device-local-desktop-onboarding/alternatives-considered: - en: 82debfeba02ecfdb - zh: c15d8fcc012058e3 -/agent-note-device-local-desktop-onboarding/consequences: - en: 65d1dbaf6b39d57a - zh: fa5b562c059b3203 diff --git a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml deleted file mode 100644 index e3f4c53695..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-open-in-default-app-for-sidebar-files.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-open-in-default-app-for-sidebar-files.md -/agent-note-open-sidebar-previewed-files-in-the-default-application: - en: 948e52b8edac6120 - zh: 0898da7049f15ca5 -/agent-note-open-sidebar-previewed-files-in-the-default-application/problem: - en: 6caae0cf235cdb1d - zh: e80f02c05d65598a -/agent-note-open-sidebar-previewed-files-in-the-default-application/decision: - en: ebd2273122d077db - zh: 8a596d0a49fb4e4e -/agent-note-open-sidebar-previewed-files-in-the-default-application/alternatives-considered: - en: f42e5763bf52e401 - zh: 4b2d954904505fc9 -/agent-note-open-sidebar-previewed-files-in-the-default-application/consequences: - en: 618062f56d294415 - zh: 5eb57d17854fa805 diff --git a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.i18n.yaml deleted file mode 100644 index 369511d545..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-performance-usage-preference.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-performance-usage-preference.md -/agent-note-performance-and-usage-detail-preference: - en: 6179732355b5c019 - zh: 3a620cf25a5580c4 -/agent-note-performance-and-usage-detail-preference/problem: - en: 3b420f5fb2952b3e - zh: 6fa1ef1090e595ec -/agent-note-performance-and-usage-detail-preference/decision: - en: 953b5fcaed000c9e - zh: 2ff9d6ff0535d846 -/agent-note-performance-and-usage-detail-preference/alternatives-considered: - en: 7c676c0b192d03eb - zh: 2e994f30fc99c5d4 -/agent-note-performance-and-usage-detail-preference/consequences: - en: 2bc7b780efbae1e9 - zh: c7ecaffd29dfbbff diff --git a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.md b/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.md deleted file mode 100644 index 36ab30561f..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: Performance and usage detail preference - -Status: implemented - -English | [中文](2026-09-16-performance-usage-preference.zh.md) - -## Problem - -Always-visible counts and accounting controls add density to Chat even when readers only need output speed and cache effectiveness. - -## Decision - -Chat owns `ui-chat.performanceUsage` in its existing settings namespace. General Settings offers Compact and Detailed, with Detailed as the default to preserve access to accounting. Both the composer and turn-tail renderer consume the same accepted settings source through framework-bound hooks. Host settings own persistence, validation, and failed-write recovery. - -Compact retains only available output speed and cache-hit percentage under the composer. These readings are plain text with no statistics dialog. Detailed retains session statistic dialogs and per-Turn usage. Neither mode renders the elapsed-time action in the completed-turn footer. This preference changes presentation, never telemetry collection, accounting, or Session events. - -The [composer statistics decision](2026-09-07-composer-session-stats-pills.md) remains authoritative for accounting sources, dialog exclusivity, and composer spacing in Detailed mode. - -The requested footer design prioritizes message actions and token accounting in both modes. Completed turns therefore expose no per-turn elapsed-time control; Detailed restores accounting detail, not that control. The running clock and session-wide timing statistics remain independent of this choice. - -## Alternatives considered - -**Default to Compact.** This would hide accounting controls for existing users without an explicit choice. The request defines both modes but no default, so Detailed preserves their access. - -**Filter telemetry when Compact is selected.** This would discard information needed after switching to Detailed or reopening history. A display preference must not change recorded data. - -## Consequences - -Unavailable speed or cache-hit data is omitted rather than replaced with invented values. The settings scope provides one accepted value to all consumers without duplicating subscription state. Component coverage checks mode-specific visibility; the [recorded-session browser scenario](../../../../apps/web/tests/message-actions.e2e.ts) exercises selection, reload persistence, and Compact output. - -On non-loopback browsers, the preference remains process-local because the settings scope cannot persist writes. Explicit selections update every consumer immediately; accepted Host settings reconcile the live value on loopback browsers. diff --git a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.zh.md b/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.zh.md deleted file mode 100644 index ecfd19c41c..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-performance-usage-preference.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note:性能与用量详细程度偏好 - -Status: implemented - -[English](2026-09-16-performance-usage-preference.md) | 中文 - -## Problem - -当读者只需要输出速度和缓存效果时,始终显示的计数和记账控件会增加聊天界面的密度。 - -## Decision - -Chat 在既有设置命名空间中拥有 `ui-chat.performanceUsage`。通用设置提供简洁和详细选项,默认详细以保留记账入口。输入框和轮次页脚渲染器通过框架绑定的 Hook 消费同一个已接受设置源。Host 设置负责持久化、校验和写入失败恢复。 - -简洁模式仅在输入框下方保留可用的输出速度和缓存命中率。这些读数为纯文本,不提供统计卡片。详细模式保留会话统计卡片和每轮用量。两种模式均不渲染已完成轮次页脚中的耗时操作。该偏好只改变展示,不改变遥测收集、记账或 Session 事件。 - -[输入框统计决策](2026-09-07-composer-session-stats-pills.zh.md)仍负责详细模式中的记账来源、卡片互斥和输入框间距。 - -所要求的页脚设计在两种模式下均优先展示消息操作和 token 记账。因此,已完成轮次不提供单轮耗时控件;详细模式恢复记账详情,不恢复该控件。运行中的时钟和会话级计时统计独立于此选择。 - -## Alternatives considered - -**默认简洁。** 这会在已有用户未明确选择时隐藏记账控件。需求定义了两种模式但未指定默认值,因此详细模式保留既有入口。 - -**选择简洁时过滤遥测。** 这会丢弃切换回详细模式或重新打开历史时需要的信息。展示偏好不能改变记录数据。 - -## Consequences - -不可用的速度或缓存命中数据会被省略,不填入虚构值。设置作用域为所有消费者提供同一个已接受值,无需复制订阅状态。组件覆盖检查不同模式的可见性;[录制会话浏览器场景](../../../../apps/web/tests/message-actions.e2e.ts)验证选择、刷新持久化和简洁模式输出。 - -在非回环地址浏览器中,设置作用域无法持久化写入,因此该偏好仅在当前进程内生效。明确选择会立即更新所有使用方;回环地址浏览器收到 Host 已接受的设置后会同步当前值。 diff --git a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.i18n.yaml deleted file mode 100644 index dfc86aee32..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-sandbox-same-mode.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-sandbox-same-mode.md -/agent-note-repeated-sandbox-modes-need-no-approval: - en: 85325770757063d8 - zh: 9c55052551299ddf -/agent-note-repeated-sandbox-modes-need-no-approval/problem: - en: 0d5832fd63b9cd7b - zh: f07b20a91b92de25 -/agent-note-repeated-sandbox-modes-need-no-approval/decision: - en: b102948c4acbdd96 - zh: 7220f043716d571c -/agent-note-repeated-sandbox-modes-need-no-approval/alternatives-considered: - en: fb337915525ed95c - zh: d0584f998f0a3b8f -/agent-note-repeated-sandbox-modes-need-no-approval/consequences: - en: bde9f3b22f5159c2 - zh: 105c24546f0943bb diff --git a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.i18n.yaml deleted file mode 100644 index eab4632401..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-shared-subagent-settings-card.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.md -/agent-note-shared-subagent-settings-card: - en: 63fcf9d6ab8a601d - zh: 0f824893329679f2 -/agent-note-shared-subagent-settings-card/problem: - en: a63f47c29f3a6305 - zh: 9f2150e9a9c5bc4b -/agent-note-shared-subagent-settings-card/decision: - en: 2c4056b23e053d3a - zh: d46c391f9b5be135 -/agent-note-shared-subagent-settings-card/alternatives-considered: - en: 8674979fbd8197f1 - zh: 6c2169803839f70b -/agent-note-shared-subagent-settings-card/consequences: - en: 4a3671620dfa64b6 - zh: eb6eb20e864f77a1 -/agent-note-shared-subagent-settings-card/verification: - en: 8d9956c13fb6658b - zh: 3d95564e51595bca diff --git a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.md b/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.md deleted file mode 100644 index d9457b3e9b..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: Shared Subagent settings card - -Status: implemented - -English | [中文](2026-09-16-shared-subagent-settings-card.zh.md) - -## Problem - -Delegation limits and model authorization describe the same Subagent workflow, but separate settings cards make users locate and save them independently. - -## Decision - -One Subagent page groups delegation limits and model selection, with one save button. Leaving the page discards both drafts. Both existing controllers retain ownership of their drafts, validation and namespace writes. The card blocks saving if either draft is invalid or conflicted, disables both sections during saving, and stays open after both settle successfully. A failed section retains its draft for retry. Limit explanations stay behind information buttons beside the field labels, with concrete depth examples and counting rules. Validation errors remain visible so disclosure does not hide a blocked save. - -The plugin registers one `plugins.item` entry while either namespace is served. Its component renders only the available sections, preserving limits-only and model-only deployments without teaching the Plugins page about Subagent grouping. - -## Alternatives considered - -**Keep separate cards.** Independent controls obscure the relationship between delegation policy and model authorization and require separate save gestures. - -**Merge Host namespaces.** UI grouping needs no change to persisted settings, namespace revisions or the different times these policies take effect. The existing namespace controllers preserve those contracts. - -## Consequences - -The user reviews both sections in one place. A save remains separate namespace writes, so partial success is possible; successful drafts clear while rejected drafts remain visible and retryable. Model authorization still writes its switch and routes atomically within its own namespace. The [package reference](../../../../packages/client/ui-settings-plugins/README.md) describes the controls and their applicability. - -## Verification - -Focused component and controller tests cover shared validation, save, discard on leaving, pending writes, partial failure and deployments serving either namespace. The assembled Web scenario saves both sections through one button and reads back the settings document. diff --git a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.zh.md b/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.zh.md deleted file mode 100644 index ccde0c97df..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-shared-subagent-settings-card.zh.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: 统一 Subagent 设置卡片 - -Status: implemented - -[English](2026-09-16-shared-subagent-settings-card.md) | 中文 - -## Problem - -委派限制与模型授权描述的是同一套 Subagent 工作流程,但分开的设置卡片让用户必须分别查找和保存。 - -## Decision - -一个 Subagent 页面组织委派限制与模型选择,共用一个保存按钮。离开页面会丢弃两个草稿。两个现有控制器仍各自拥有草稿、校验和命名空间写入。任一草稿无效或冲突时,卡片禁止保存;保存期间禁用两个分区;两者均成功完成后仍保持打开。失败的分区保留草稿以供重试。限制说明通过字段名旁的信息按钮按需展开,使用具体层级示例和计数规则。校验错误仍直接显示,避免折叠说明后隐藏阻止保存的原因。 - -插件在任一命名空间可用时注册一个 `plugins.item` 条目。组件仅呈现可用的分区,既保留只提供限制或模型设置的部署,也无需让插件页了解 Subagent 分组。 - -## Alternatives considered - -**保留分开的卡片。** 独立控件使委派策略与模型授权之间的关系不够清晰,也需要分别保存。 - -**合并 Host 命名空间。** UI 分组不需要改变持久化设置、命名空间 revision 或两项策略不同的生效时机。现有命名空间控制器保留这些约定。 - -## Consequences - -用户可以在同一处检查两个分区。保存仍分别写入各自的命名空间,因此可能部分成功;成功的草稿清除,被拒绝的草稿保持可见并可重试。模型授权仍在自己的命名空间内原子写入开关与路由。[包参考文档](../../../../packages/client/ui-settings-plugins/README.zh.md) 描述控件及其生效范围。 - -## Verification - -聚焦的组件和控制器测试覆盖共同校验、保存、离开时丢弃草稿、进行中的写入、部分失败,以及仅提供任一命名空间的部署。组装后的 Web 场景通过一个按钮保存两个分区,并回读设置文档。 diff --git a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.i18n.yaml index 70686c479d..6e9266fa39 100644 --- a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.i18n.yaml @@ -9,8 +9,8 @@ en: 5236141e90e42b81 zh: 1af6306b20797603 /agent-note-sandboxed-sidebar-browser/decision: - en: 9e89a0fe77cd8b9f - zh: 7aba8507320cf504 + en: 123077d0c519f6c9 + zh: fc447d313dc72e0c /agent-note-sandboxed-sidebar-browser/web-navigation-states: en: 6aec277c70600c7d zh: e2b53f01072b2423 diff --git a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.md b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.md index eff143cc04..6f8fb00fc8 100644 --- a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.md +++ b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.md @@ -28,7 +28,7 @@ A controller created from a saved checkpoint offers its last title and URL with Occurrence cancellation releases the controller. It removes the saved checkpoint only when the Sidebar's authoritative `openTabs` inventory no longer contains that tab. The inventory publishes layout removal before `TabDomain` aborts its occurrence; plugin unload also aborts occurrences but leaves saved tab membership intact. Cleanup therefore does not interpret plugin unload as a request to erase recovery data. -Browser state is presentation state. It does not enter the Session log, model request, resource model, or DockKit layout operations. The existing [right Sidebar infrastructure](2026-09-04-right-sidebar-docking-infrastructure.md), [tab type contract](../architecture/2026-09-05-sidebar-tab-types-and-navigation.md), [resource model](../architecture/2026-09-05-client-resource-model.md), and [Document Preview operations](../architecture/2026-09-08-document-preview-operations.md) retain their existing responsibilities. +Browser state is presentation state. It does not enter the Session log, model request, resource model, or DockKit layout operations. The existing [right Sidebar infrastructure](2026-09-04-right-sidebar-docking-infrastructure.md), [tab type contract](../../../../packages/client/ui-sidebar-right/README.md), [resource model](../architecture/2026-09-05-client-resource-model.md), and [Document Preview operations](../architecture/2026-09-08-document-preview-operations.md) retain their existing responsibilities. ## Web navigation states diff --git a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.zh.md b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.zh.md index a037c2fb54..1fcbcccc95 100644 --- a/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.zh.md +++ b/.agents/notes/implemented/feature/2026-09-16-sidebar-browser.zh.md @@ -28,7 +28,7 @@ Web 使用 iframe 载体;Desktop 使用[保持页面实例的 webview 载体]( occurrence 取消会释放控制器。只有 Sidebar 的权威 `openTabs` 清单已不再包含该 Tab 时,才删除保存的检查点。清单先发布布局中的 Tab 移除,再由 `TabDomain` abort 对应 occurrence;插件卸载也会 abort occurrence,但仍保留已保存的 Tab 成员信息。因此清理不会把插件卸载当成删除恢复数据的请求。 -Browser 状态只属于呈现层,不进入 Session log、模型请求、resource model 或 DockKit layout operation。现有的[右侧 Sidebar 基础设施](2026-09-04-right-sidebar-docking-infrastructure.zh.md)、[tab 类型契约](../architecture/2026-09-05-sidebar-tab-types-and-navigation.zh.md)、[resource model](../architecture/2026-09-05-client-resource-model.zh.md)和[文档预览操作](../architecture/2026-09-08-document-preview-operations.zh.md)继续负责各自现有职责。 +Browser 状态只属于呈现层,不进入 Session log、模型请求、resource model 或 DockKit layout operation。现有的[右侧 Sidebar 基础设施](2026-09-04-right-sidebar-docking-infrastructure.zh.md)、[tab 类型契约](../../../../packages/client/ui-sidebar-right/README.zh.md)、[resource model](../architecture/2026-09-05-client-resource-model.zh.md)和[文档预览操作](../architecture/2026-09-08-document-preview-operations.zh.md)继续负责各自现有职责。 ## Web 导航状态 diff --git a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.i18n.yaml deleted file mode 100644 index 4a74dfb669..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-unified-model-input-controls.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-unified-model-input-controls.md -/agent-note-unified-model-input-controls: - en: a22f2dd1b73eb314 - zh: 1f43526fa6af593a -/agent-note-unified-model-input-controls/problem: - en: e28035e73033057d - zh: 262cf7551cdb783a -/agent-note-unified-model-input-controls/decision: - en: 6b4872a76d480c8a - zh: f833a14a48d2e9d7 -/agent-note-unified-model-input-controls/alternatives-considered: - en: 83f8ffa39259a9f4 - zh: e158cb5868c82d74 -/agent-note-unified-model-input-controls/consequences: - en: 768bb77e3a4d72be - zh: 81080b075b0cc26c diff --git a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.i18n.yaml b/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.i18n.yaml deleted file mode 100644 index 6c3b3ddfe0..0000000000 --- a/.agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-windows-desktop-titlebar.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-16-windows-desktop-titlebar.md -/agent-note-windows-desktop-caption-and-application-menus: - en: 4220f3c0d8058ca7 - zh: 4751c2cffb9b8e2a -/agent-note-windows-desktop-caption-and-application-menus/problem: - en: 9a3d1b6d02197c6b - zh: 5eba9f9bf6928430 -/agent-note-windows-desktop-caption-and-application-menus/decision: - en: be4b50d09caa5d46 - zh: 20b0572be5941b0c -/agent-note-windows-desktop-caption-and-application-menus/alternatives-considered: - en: db7703a7a37ac5da - zh: 704a2a289b8f7cb0 -/agent-note-windows-desktop-caption-and-application-menus/consequences: - en: a52bfa0c00b4e26d - zh: c479cc99fd1bff03 -/agent-note-windows-desktop-caption-and-application-menus/testing: - en: d72a6473a7d385cf - zh: ededb7391908db38 diff --git a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.i18n.yaml deleted file mode 100644 index 101417360a..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-compact-translucent-menu-surfaces.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md -/agent-note-compact-translucent-menu-surfaces: - en: 629cdf857a84d1f3 - zh: 676a34bac03903be -/agent-note-compact-translucent-menu-surfaces/problem: - en: f1ff5b408b409003 - zh: 1f548352bc075cc4 -/agent-note-compact-translucent-menu-surfaces/decision: - en: 30cbe72206e81f96 - zh: 21cb393871fe6420 -/agent-note-compact-translucent-menu-surfaces/alternatives-considered: - en: f06228674ee82029 - zh: 385603bebcd17d5f -/agent-note-compact-translucent-menu-surfaces/consequences: - en: e76746c0b95549df - zh: 9ff34644703d565c diff --git a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md b/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md deleted file mode 100644 index 08fcb2d851..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: Compact translucent menu surfaces - -Status: implemented - -English | [中文](2026-09-17-compact-translucent-menu-surfaces.zh.md) - -## Problem - -Shared dropdowns and feature-owned menu panels used the same elevated color token but retained unrelated padding, row height, corner radius, and scrollbar geometry. The opaque menu fill also made nested and portalled surfaces read as solid cards rather than one elevation family, while narrowing individual menus locally would keep those differences distributed across packages. - -## Decision - -`ui-theme` defines `--dsw-specific-menu` as a translucent light or dark fill and defines `--dsw-menu-backdrop-filter` as `blur(40px) saturate(150%)`. Every elevated package surface that paints the menu fill also applies that backdrop filter, uses `border: 0`, and takes an elevation shadow with its rebindable hairline stroke. A surface containing fixed-position overlays paints the fill and filter on an isolated background pseudo-element, because a filtered ancestor would otherwise change those overlays' containing block. Descendant sticky rows may repaint the inherited fill without another filter. Browsers without backdrop filtering still render the theme-owned translucent fill. - -The shared `Menu` and the composer input-trigger menu use the same compact baseline: a 16px outer radius, 3px frame padding, 34px ordinary rows, 13px primary text on a 20px line, 6px icon/text gaps, and 8px row radii. Dense and compact variants reduce from that baseline instead of retaining the former ordinary geometry. The composer menu keeps its feature-owned grouping and 400px height cap; its aliases and descriptions use 12px text on an 18px line. - -The global WebKit scrollbar width is 5px. The composer menu overrides it with a 6px draggable rail and a 2px visible thumb through the existing scrollbar geometry variables; Firefox continues to use its standard thin scrollbar path. Track insets and elevated l2 thumb colors remain surface-owned rebindings. - -## Alternatives considered - -**Keep an opaque shared menu fill.** Rejected because every consumer would need a separate translucent override to share one material, recreating the theme branch outside `ui-theme`. - -**Compact only the composer command menu.** Rejected because the shared `Menu` renders the same action, settings, and navigation vocabulary; keeping its former 40px rows would preserve two densities for the same control family. - -**Use the 6px padded rail for every scrollbar.** Rejected because ordinary scroll regions need the direct 5px thumb, while the command menu needs a wider hit area around a visually quieter thumb. The existing geometry variables express that difference without another scrollbar implementation. - -## Consequences - -Elevated menu-fill consumers must pair the fill and backdrop-filter tokens in the same rule, either on the surface or on its isolated background pseudo-element; the stylesheet gate rejects a material layer that omits the filter. Shared menu geometry changes affect every `Menu` render site, while the composer menu retains its own content and interaction rules. Browser and component snapshots cover assembled structure, and theme specs pin the tokens, compact metrics, scrollbar geometry, and complete menu-filter pairing. diff --git a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.zh.md b/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.zh.md deleted file mode 100644 index 8e13634995..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.zh.md +++ /dev/null @@ -1,29 +0,0 @@ -# Agent Note: 紧凑半透明菜单表面 - -Status: implemented - -[English](2026-09-17-compact-translucent-menu-surfaces.md) | 中文 - -## Problem - -共享下拉菜单与功能包自有菜单面板使用同一个高层级颜色 token,却保留了不同的内边距、行高、圆角和滚动条几何。不透明菜单填充也让嵌套与 portal 表面像彼此独立的实色卡片;如果只在各功能菜单中局部收紧尺寸,这些差异仍会分散在多个包中。 - -## Decision - -`ui-theme` 将 `--dsw-specific-menu` 定义为明暗主题各自的半透明填充,并将 `--dsw-menu-backdrop-filter` 定义为 `blur(40px) saturate(150%)`。每个绘制菜单填充的包级高层级表面同时应用该 backdrop filter、设置 `border: 0`,并使用带可重绑发丝描边的 elevation 投影。包含 fixed 定位浮层的表面把填充与滤镜绘制在隔离的背景伪元素上,因为带滤镜的祖先会改变这些浮层的包含块。后代 sticky 行可以再次绘制继承的填充,无需重复 filter。不支持 backdrop filtering 的浏览器仍渲染主题持有的半透明填充。 - -共享 `Menu` 与 composer 输入触发菜单使用同一套紧凑基准:16px 外圆角、3px 边框内距、34px 普通行、13px 主文字配 20px 行高、6px 图文间距,以及 8px 行圆角。dense 与 compact 变体在该基准上继续缩小,不再保留旧的普通几何。composer 菜单保留其功能包持有的分组与 400px 高度上限;别名和说明使用 12px 字号与 18px 行高。 - -WebKit 系全局滚动条宽度为 5px。composer 菜单通过既有滚动条几何变量覆盖为 6px 可拖动轨道与 2px 可见滑块;Firefox 继续走标准 thin scrollbar 路径。轨道内缩与高层级 l2 滑块颜色仍由表面自行重绑。 - -## Alternatives considered - -**保留不透明的共享菜单填充。** 否决,因为每个消费方都需要单独覆盖成半透明,导致同一种材质再次在 `ui-theme` 之外分叉。 - -**只收紧 composer 指令菜单。** 否决,因为共享 `Menu` 渲染的是同一套操作、设置与导航词汇;保留原 40px 行会让同一个控件家族继续存在两种密度。 - -**所有滚动条都使用 6px 带内边距轨道。** 否决,因为普通滚动区域需要直接的 5px 滑块,而指令菜单需要在更安静的可见滑块外保留更宽点击区域。现有几何变量可以表达该差异,无需第二套滚动条实现。 - -## Consequences - -高层级菜单填充消费方必须在同一规则中同时使用填充与 backdrop-filter token,规则可以属于表面本身,也可以属于其隔离的背景伪元素;样式表门禁会拒绝漏掉 filter 的材质层。共享菜单几何影响每个 `Menu` 渲染点,composer 菜单仍保留自身内容与交互规则。浏览器与组件快照覆盖装配结构,主题测试固定 token、紧凑尺寸、滚动条几何与完整的菜单 filter 配对。 diff --git a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml deleted file mode 100644 index e32c9f6b23..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-developer-tools-settings.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-developer-tools-settings.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-17-developer-tools-settings.md -/agent-note-shared-developer-tool-settings: - en: 353fa09368362d0f - zh: b36c06c8b49833b1 -/agent-note-shared-developer-tool-settings/problem: - en: e22ee243b1503319 - zh: b53c1a932d547f55 -/agent-note-shared-developer-tool-settings/decision: - en: b579ed62e2846939 - zh: 4d5c121599413807 -/agent-note-shared-developer-tool-settings/alternatives-considered: - en: df1b80e5b8d2a178 - zh: 0136d16fc44d57bf -/agent-note-shared-developer-tool-settings/consequences: - en: 3f50fdc4b4233266 - zh: 8647e00b474da639 diff --git a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.i18n.yaml deleted file mode 100644 index bc17196684..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-persistent-plan-cards.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md -/agent-note-persistent-plan-cards-and-sidebar-reading: - en: e84bc0e16f40cafb - zh: b11a06e7800e3f9a -/agent-note-persistent-plan-cards-and-sidebar-reading/problem: - en: 273400b515da3a1d - zh: 13b508617f08c6d0 -/agent-note-persistent-plan-cards-and-sidebar-reading/decision: - en: 243abb4fe9944c97 - zh: 6e66ddceb9c89aee -/agent-note-persistent-plan-cards-and-sidebar-reading/alternatives-considered: - en: f1f8dbe0f29b89cd - zh: 4fc73f1f4b4bcbae -/agent-note-persistent-plan-cards-and-sidebar-reading/consequences: - en: 4d1740b16711041d - zh: 412ac19a3705045e diff --git a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.i18n.yaml index f8fc347f32..368a5a645b 100644 --- a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.i18n.yaml @@ -69,5 +69,5 @@ en: 8faa05de777dea21 zh: 2f0b1ff65bdba75b /agent-note-on-demand-watching-and-automatic-refresh-for-sidebar-previews-and-file-trees/relationship-to-existing-agent-notes: - en: 8ddc059f64cb3b1c - zh: f6d6836458ef9522 + en: b397ea36155793e9 + zh: bfe74e82b0256cfa diff --git a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.md b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.md index f509a8cf26..99639db2e5 100644 --- a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.md +++ b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.md @@ -342,4 +342,4 @@ Automatic refresh and OS watching retain the Resource model, file-read authoriza - [Workspace file service](../../implemented/architecture/2026-09-05-workspace-files-service.md): read and listing responsibilities remain; target-scoped OS observation replaces the Session-scoped operation-only stream. - [Document preview operations](../../implemented/architecture/2026-09-08-document-preview-operations.md): content-loading ownership and all three modes remain; automatic refresh uses their existing reload paths. - [Workspace file-read authority](../../implemented/architecture/2026-09-09-workspace-file-read-authority.md): retain distinct access scopes for file reading and directory browsing; target watches follow their respective rules. -- [Sidebar text preview and file tree](../../implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md): per-Tab expansion, navigation, and scrolling remain; open nodes own automatic directory invalidation and watching. +- [Sidebar text preview and file tree](../../../../packages/client/ui-sidebar-files/README.md): per-Tab expansion, navigation, and scrolling remain; open nodes own automatic directory invalidation and watching. diff --git a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.zh.md b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.zh.md index ff7a950aa8..5d11041a5c 100644 --- a/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.zh.md +++ b/.agents/notes/implemented/feature/2026-09-17-sidebar-file-and-directory-auto-refresh.zh.md @@ -342,4 +342,4 @@ FS、Workspace API、预览和 Files 各自的测试覆盖监听生命周期、 - [Workspace 文件服务](../../implemented/architecture/2026-09-05-workspace-files-service.zh.md):读取与列表接口职责不变;目标级系统监听替代仅转发操作观察的 Session 流。 - [文档预览操作](../../implemented/architecture/2026-09-08-document-preview-operations.zh.md):正文加载所有权和三种加载方式不变;自动刷新使用其现有 reload 路径。 - [Workspace 文件读取授权](../../implemented/architecture/2026-09-09-workspace-file-read-authority.zh.md):保留文件读取与目录浏览的不同访问范围;目标 watch 分别遵循对应规则。 -- [Sidebar 文本预览与文件树](../../implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md):按 Tab 的展开、导航和滚动状态不变;已打开节点负责目录自动失效与监听。 +- [Sidebar 文本预览与文件树](../../../../packages/client/ui-sidebar-files/README.zh.md):按 Tab 的展开、导航和滚动状态不变;已打开节点负责目录自动失效与监听。 diff --git a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.i18n.yaml b/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.i18n.yaml deleted file mode 100644 index 711a40b14b..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-17-state-dot-visual-language.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.md -/agent-note-statedot-solid-marks-and-loading-spinner: - en: 74ca1dda90213e27 - zh: 14851ca8d3d4617d -/agent-note-statedot-solid-marks-and-loading-spinner/problem: - en: 4739e4079e950c72 - zh: 4633fc299da13565 -/agent-note-statedot-solid-marks-and-loading-spinner/decision: - en: 98dd1c4ac95913b9 - zh: c3c53bae1ffc430a -/agent-note-statedot-solid-marks-and-loading-spinner/alternatives-considered: - en: 962827b807ebf026 - zh: dfe83e98956dd2cd -/agent-note-statedot-solid-marks-and-loading-spinner/testing: - en: 9d5f7bfb69b7bded - zh: 281db4c0e7ddd8c2 -/agent-note-statedot-solid-marks-and-loading-spinner/consequences: - en: 17a7a8d9c3923666 - zh: 84d941223b079f2b diff --git a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.md b/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.md deleted file mode 100644 index 751abbaa33..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: StateDot solid marks and loading spinner - -Status: implemented - -English | [中文](2026-09-17-state-dot-visual-language.zh.md) - -## Problem - -`StateDot` mixed two drawing languages: settled states used a translucent halo around a solid core, while `ongoing` used an eight-cell pixel chase. - -## Decision - -`idle`, `done`, `warning`, and `error` render as one solid 6px circle inside the existing 10px layout slot, with no halo. `idle` uses the neutral `--dsw-alias-state-idle-primary` token, `done` remains success green, `warning` remains amber, and `error` remains red. - -`ongoing` is the sole non-dot member. Its default edge is 14px, while solid states retain their 10px layout slot. It renders a 25%-opacity complete ring behind a grey arc using the tertiary label token. The glyph rotates continuously over 1.5 seconds while the arc grows from 12 to 24 dash units around its center and returns to 12; the arc offset is zero at both ends, so the browser does not reset a second circular movement at the loop boundary. Reduced-motion environments retain an intermediate static arc. The explicit size override, `data-state`, and `aria-hidden` behavior do not change. - -Converted compact status-only presentations use this shared mapping instead of local dots or spinners: waiting or blocked is `warning`, active work is `ongoing`, successful completion is `done`, failure is `error`, and inactive or not-yet-started work is `idle`. Tool rows whose leading slot is a business icon retain the ordinary glyph in every lifecycle state; their collapsed summary turns red for failure and amber for `stopped`, preserving any tool-specific interruption text. The framework-free boot page remains separate because its arc reports aggregate loader progress before React and the shared primitive are available. - -## Alternatives considered - -**Keep the pixel chase.** Rejected because its block animation did not share the circular language of the settled markers and read as a decorative activity glyph rather than a conventional loading state. - -**Fill the complete 10px slot.** Rejected because the former visible core was 6px; retaining that diameter preserves row density while removing only the halo. - -## Testing - -The component specs pin the four solid-state elements, the two-circle loading artwork, both animation tracks, both size paths, the no-halo stylesheet, and every state token. Tool, Bash, and Skill row specs pin the retained business glyph plus visible red failure and amber interruption summaries. Workspace, Job, Workflow, Subagent, Deliverables, terminal, plugin, Schedule, Todo, Team, approval, document-preview, Trajectory, and connection suites pin their state mappings and accessible labels. - -## Consequences - -Consumers keep their accessibility labels while status-only presentations share one visual vocabulary. Every settled marker becomes visually quieter and more compact, completion remains green, and every active marker uses the same tertiary-grey rotating loader. Action glyphs such as reconnect and business glyphs such as file or tool types remain available when the icon itself communicates the operation rather than only its state. diff --git a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.zh.md b/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.zh.md deleted file mode 100644 index 9746ec9af7..0000000000 --- a/.agents/notes/implemented/feature/2026-09-17-state-dot-visual-language.zh.md +++ /dev/null @@ -1,31 +0,0 @@ -# Agent Note: StateDot 纯色标记与旋转 loading - -Status: implemented - -[English](2026-09-17-state-dot-visual-language.md) | 中文 - -## 问题 - -`StateDot` 混用了两套绘制语言:已结算状态在实心核外带半透明光晕,`ongoing` 则使用八格像素追逐动画。 - -## 决策 - -`idle`、`done`、`warning` 与 `error` 在既有 10px 布局槽内渲染一个 6px 纯色圆点,不再带光晕。`idle` 使用中性的 `--dsw-alias-state-idle-primary` token,`done` 保持成功绿色,`warning` 保持琥珀色,`error` 保持红色。 - -`ongoing` 是唯一不是圆点的成员。它的默认边长为 14px,纯色状态仍保留 10px 布局槽。它在透明度为 25% 的完整圆环上方,使用 tertiary label token 渲染灰色圆弧。图形以 1.5 秒周期持续旋转,圆弧围绕中心从 12 个 dash 单位增长到 24 个、再回到 12 个;圆弧偏移在首尾都为零,因此浏览器不会在循环边界重置第二段圆周运动。减少动态效果的环境保留中间长度的静态圆弧。显式 size 覆盖、`data-state` 与 `aria-hidden` 行为均不改变。 - -已转换的紧凑纯状态展示使用这套共享映射,不再自绘圆点或 spinner:等待或阻塞为 `warning`,活动工作为 `ongoing`,成功完成为 `done`,失败为 `error`,未活动或尚未开始为 `idle`。前置槽为业务图标的工具行在所有生命周期状态中都保留普通图标;收起摘要在失败时变红,在 `stopped` 时变为琥珀色并保留工具自有的中断文本。无框架的启动页保持独立,因为它会在 React 与共享原语可用前用圆弧表达 Loader 总体进度。 - -## 考虑过的替代方案 - -**保留像素追逐动画。** 否决,因为方块动画没有沿用已结算标记的圆形语言,看起来更像装饰性活动图形,而不是常规 loading 状态。 - -**填满完整 10px 槽位。** 否决,因为原来的可见实心核就是 6px;保留该直径只移除光晕,同时维持行密度。 - -## 测试 - -组件测试固定四种纯色状态元素、双圆环 loading 图稿、两条动画轨道、两条尺寸路径、无光晕样式和每个状态 token。Tool、Bash 与 Skill 行测试固定保留的业务图形,以及可见的红色失败摘要与琥珀色中断摘要。Workspace、Job、Workflow、Subagent、Deliverables、终端、插件、Schedule、Todo、Team、审批、文档预览、Trajectory 与连接测试固定各自的状态映射和无障碍 label。 - -## 后果 - -消费方保留无障碍 label,纯状态展示共享同一套视觉词汇。所有已结算标记在视觉上更安静、更紧凑,完成态保持绿色,所有活动态使用同一种 tertiary 灰色旋转 loading。重连等动作图标和文件或工具类型等业务图标在图形本身表达操作而不只表达状态时继续保留。 diff --git a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.i18n.yaml b/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.i18n.yaml deleted file mode 100644 index daa3d1e95a..0000000000 --- a/.agents/notes/implemented/feature/2026-09-18-account-quota-top-up.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-account-quota-top-up.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-18-account-quota-top-up.md -/agent-note-account-quota-top-up-action: - en: c9f0d30bc28f815c - zh: 472505b18ee2e669 -/agent-note-account-quota-top-up-action/problem: - en: 4c0caf28d02faec2 - zh: a6cc3e175a2f9f4a -/agent-note-account-quota-top-up-action/decision: - en: d8046306e6488932 - zh: a0d29f3949c96955 -/agent-note-account-quota-top-up-action/alternatives-considered: - en: ec6731c63d9c42ee - zh: 535b67062ce0269b -/agent-note-account-quota-top-up-action/consequences: - en: 72febb3835a50018 - zh: 0125b875f35b829e diff --git a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml b/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml deleted file mode 100644 index a74636286a..0000000000 --- a/.agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.i18n.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-18-session-pin-and-sidebar-archive.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-18-session-pin-and-sidebar-archive.md -/agent-note-session-pinning-and-the-sidebar-archived-filter: - en: f21362fdb86cdb6c - zh: b14889b8b7a717a3 -/agent-note-session-pinning-and-the-sidebar-archived-filter/problem: - en: 8a33826809d1dd8f - zh: b37aa33a8e2ed8d8 -/agent-note-session-pinning-and-the-sidebar-archived-filter/decision: - en: c483432158826f12 - zh: 2fc1e96f276420a7 -/agent-note-session-pinning-and-the-sidebar-archived-filter/decision/complete-session-order: - en: 321a47984818bbb9 - zh: a55d22a44e7b7155 -/agent-note-session-pinning-and-the-sidebar-archived-filter/decision/archive-restoration: - en: 0e16cb25af1f2bb1 - zh: 026fdf7bfa1c104c -/agent-note-session-pinning-and-the-sidebar-archived-filter/decision/archived-visibility-and-row-motion: - en: 83dd778a865f2cf5 - zh: 7636166749f63633 -/agent-note-session-pinning-and-the-sidebar-archived-filter/decision/persistence: - en: 4bcb0398b47334aa - zh: fa386724afa99a77 -/agent-note-session-pinning-and-the-sidebar-archived-filter/alternatives-considered: - en: 3bf6fc0c22b17377 - zh: 0fc64131ee8be859 -/agent-note-session-pinning-and-the-sidebar-archived-filter/consequences: - en: a487faf9af215726 - zh: a92cae8b15fba541 -/agent-note-session-pinning-and-the-sidebar-archived-filter/related: - en: 6c67f3a071f80c15 - zh: e0455cddc9c4bea9 diff --git a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.i18n.yaml b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.i18n.yaml index 5058cd26a1..3678ffb912 100644 --- a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.i18n.yaml @@ -36,5 +36,5 @@ en: 6c1deeacdbff418d zh: 4d7c546e19da5e53 /agent-note-windows-sandbox-delete-confinement-mandatory-integrity-plus-an-ambient-delete-deny/related: - en: e4b631df90dd1efa - zh: abc66bfbf8ea2c52 + en: 7bcd65866358de4a + zh: a0fbb85274736365 diff --git a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.md b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.md index f53765f34a..ffbdc1197a 100644 --- a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.md +++ b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.md @@ -53,4 +53,4 @@ Bought: deletes confined through every authority Windows accepts AND confined to ## Related - [Windows sandbox rung: raw ACL restricted tokens](2026-08-08-windows-acl-restricted-token-sandbox.md) — the rung this change extends (kept active; this note supersedes only its delete-route boundary). -- [Sandbox decision](2026-07-06-sandbox.md) — the platform chains and the `partial` enforcement vocabulary. +- [Sandbox reference](../../../../packages/sandbox/sandbox-local/README.md) — the platform chains and the `partial` enforcement vocabulary. diff --git a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.zh.md b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.zh.md index a963fd7f54..0cb60d7bf5 100644 --- a/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.zh.md +++ b/.agents/notes/implemented/feature/2026-09-19-windows-acl-mandatory-integrity-confinement.zh.md @@ -53,4 +53,4 @@ Status: implemented ## 相关 - [Windows sandbox 档:原始 ACL 受限令牌](2026-08-08-windows-acl-restricted-token-sandbox.zh.md)——本次改动所扩展的档(保持 active;本笔记只取代其删除路径边界)。 -- [沙箱决策](2026-07-06-sandbox.zh.md)——平台链与 `partial` 强制执行词汇。 +- [沙箱参考](../../../../packages/sandbox/sandbox-local/README.zh.md)——平台链与 `partial` 强制执行词汇。 diff --git a/.agents/notes/implemented/feature/2026-09-20-default-workspace.i18n.yaml b/.agents/notes/implemented/feature/2026-09-20-default-workspace.i18n.yaml deleted file mode 100644 index bfc8acd582..0000000000 --- a/.agents/notes/implemented/feature/2026-09-20-default-workspace.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-20-default-workspace.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-20-default-workspace.md -/agent-note-first-use-default-workspace: - en: cf68fedc85ef73ad - zh: a48dfa96ac11585a -/agent-note-first-use-default-workspace/problem: - en: 952ca70e83872f88 - zh: 471b4e39dba2039a -/agent-note-first-use-default-workspace/decision: - en: 5f70a77db26ed9e2 - zh: 8f6bfb84385315d4 -/agent-note-first-use-default-workspace/alternatives-considered: - en: 58523eb415e53e2a - zh: 714ddd81ef7d3262 -/agent-note-first-use-default-workspace/consequences: - en: ab39d4e9302a2641 - zh: 8921bb8451ef2d0e diff --git a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml b/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml deleted file mode 100644 index 7467604771..0000000000 --- a/.agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-20-developer-tools-default-on.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-20-developer-tools-default-on.md -/agent-note-developer-tools-default-on-after-preference-resolution: - en: 9e6062c94dd70c14 - zh: 3c04f278da2016a5 -/agent-note-developer-tools-default-on-after-preference-resolution/problem: - en: 81d08fb192b8e83b - zh: 90a841219f86bc12 -/agent-note-developer-tools-default-on-after-preference-resolution/decision: - en: 8a3fbb5281d1b313 - zh: 320b845842676375 -/agent-note-developer-tools-default-on-after-preference-resolution/alternatives-considered: - en: 7d9ecd04554103c4 - zh: 2706bb78fcc6072d -/agent-note-developer-tools-default-on-after-preference-resolution/consequences: - en: 54622ff5338afba9 - zh: 494d55213b2751a0 diff --git a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.i18n.yaml b/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.i18n.yaml deleted file mode 100644 index 0e8511d871..0000000000 --- a/.agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-20-focus-ring-brand-primary.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-20-focus-ring-brand-primary.md -/agent-note-one-keyboard-focus-colour-for-every-control: - en: 967e19daff3df9bc - zh: 81e1a21d4204eb8a -/agent-note-one-keyboard-focus-colour-for-every-control/problem: - en: 2f3f77b1de539959 - zh: a4fa146b1d9ddecf -/agent-note-one-keyboard-focus-colour-for-every-control/decision: - en: 613ec184df6bf5d5 - zh: e4b3f95f4d33a54c -/agent-note-one-keyboard-focus-colour-for-every-control/alternatives-considered: - en: 392968102d520d1d - zh: e7b7b68f145cf3d3 -/agent-note-one-keyboard-focus-colour-for-every-control/consequences: - en: 25b9933c3d43ad76 - zh: 75d374ce3b7ec2eb diff --git a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml b/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml deleted file mode 100644 index a107523e07..0000000000 --- a/.agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-unbounded-completion-wakes-by-default.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-22-unbounded-completion-wakes-by-default.md -/agent-note-background-job-completion-wakes-are-unbounded-by-default: - en: 5c8e5c0ff2d19ab9 - zh: a046e3297aba3904 -/agent-note-background-job-completion-wakes-are-unbounded-by-default/problem: - en: 32fdd5fa938fc279 - zh: f8f97f806157eea1 -/agent-note-background-job-completion-wakes-are-unbounded-by-default/decision: - en: 7e4ee951808e1860 - zh: 8dad4884d259cdc3 -/agent-note-background-job-completion-wakes-are-unbounded-by-default/alternatives-considered: - en: 62630b7ec477bf7b - zh: 0640a01ac9d22722 -/agent-note-background-job-completion-wakes-are-unbounded-by-default/consequences: - en: ba000076db02ccb7 - zh: 1aa1e7d45d6512fe diff --git a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml b/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml deleted file mode 100644 index 3a51c979a3..0000000000 --- a/.agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-23-language-neutral-default-workspace-naming.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-23-language-neutral-default-workspace-naming.md -/agent-note-language-neutral-default-workspace-naming: - en: f9065db27c1cd7f9 - zh: 3dd1a223a6fcc6c6 -/agent-note-language-neutral-default-workspace-naming/problem: - en: 35967bed9e895460 - zh: 9cf4da88466ff37c -/agent-note-language-neutral-default-workspace-naming/decision: - en: 72fd5b65bbee5434 - zh: 08a4967a1530e686 -/agent-note-language-neutral-default-workspace-naming/alternatives-considered: - en: 556df0ed81444162 - zh: 5eee5029659419c3 -/agent-note-language-neutral-default-workspace-naming/consequences: - en: 5f710ca9cf60d514 - zh: 9ea7bdaf8925fd44 diff --git a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.i18n.yaml b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.i18n.yaml index 21a2a83f53..9ab5b64bd8 100644 --- a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.i18n.yaml @@ -9,8 +9,8 @@ en: ed725dff16cd2339 zh: 555d4ba70af96790 /agent-note-auto-review-asks-the-user-after-a-denial/decision: - en: 4782a7609717bac3 - zh: f3ebb3581b807f3f + en: f5ec55bcfe5e9a9a + zh: 1b7462f8f06320e9 /agent-note-auto-review-asks-the-user-after-a-denial/alternatives-considered: en: 01f71dd10849f925 zh: 8b79a7e149a9a60c diff --git a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.md b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.md index c9d4cda11b..73f8e6f3b1 100644 --- a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.md +++ b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.md @@ -12,7 +12,7 @@ English | [中文](2026-09-24-auto-review-user-approval-fallback.zh.md) A reviewer denial returns the tools pipeline's existing `ask` decision when the Session's approval policy is `ask`. `ToolRuntime` sends it through the approval service with the reason `Auto review denied tool ""`, followed by `: ` when the reviewer gave one. `allowed-once` executes the call; `rejected`, `cancelled`, and `unavailable` deny it through the approval service's ordinary messages, and the body does not run. The listener asks only after later `tools/pre-execute` listeners allow the call, so a downstream denial or cancellation wins without prompting. -Selecting Auto writes `danger-full-access` with the `ask` approval policy. A recorded Auto selection also matches the `never` policy. A [delegated in-process child pins `never`](2026-08-10-subagent-approval-pinned-never.md), so a child Auto keeps the final `AutoReviewDeniedError` denial and its reason, and a Session recorded under the earlier `never` bundle still resolves to Auto instead of Full access. Selecting Auto again writes `ask`. +Selecting Auto writes `danger-full-access` with the `ask` approval policy. A recorded Auto selection also matches the `never` policy. A [delegated in-process child pins `never`](../../../../packages/subagent/subagent/README.md), so a child Auto keeps the final `AutoReviewDeniedError` denial and its reason, and a Session recorded under the earlier `never` bundle still resolves to Auto instead of Full access. Selecting Auto again writes `ask`. A reviewer failure denies the call with `Auto review of tool "" failed; its body was not executed: ` and no structured error info. A provider failure's message carries the finish kind, failure code, and provider message, so the model and the generic tool card show the actual cause. diff --git a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.zh.md b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.zh.md index 7d901b75fb..a215f09e3d 100644 --- a/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.zh.md +++ b/.agents/notes/implemented/feature/2026-09-24-auto-review-user-approval-fallback.zh.md @@ -12,7 +12,7 @@ Status: implemented 当 Session 的审批策略为 `ask` 时,reviewer 拒绝返回 tools 流水线既有的 `ask` 决定。`ToolRuntime` 通过审批服务发送该请求,理由为 `Auto review denied tool ""`;reviewer 给出理由时再追加 `: `。`allowed-once` 执行调用;`rejected`、`cancelled` 与 `unavailable` 通过审批服务的普通消息拒绝调用,body 不执行。只有后续 `tools/pre-execute` listener 放行调用后,listener 才请求审批,因此下游拒绝或取消优先且不弹出审批。 -选择 Auto 会写入 `danger-full-access` 与 `ask` 审批策略。已记录的 Auto 选择也匹配 `never` 策略。[进程内委派 child 固定 `never`](2026-08-10-subagent-approval-pinned-never.zh.md),因此 child 的 Auto 保留最终的 `AutoReviewDeniedError` 拒绝及其理由;按先前 `never` 组合记录的 Session 仍解析为 Auto,而不是 Full access。再次选择 Auto 会写入 `ask`。 +选择 Auto 会写入 `danger-full-access` 与 `ask` 审批策略。已记录的 Auto 选择也匹配 `never` 策略。[进程内委派 child 固定 `never`](../../../../packages/subagent/subagent/README.zh.md),因此 child 的 Auto 保留最终的 `AutoReviewDeniedError` 拒绝及其理由;按先前 `never` 组合记录的 Session 仍解析为 Auto,而不是 Full access。再次选择 Auto 会写入 `ask`。 Reviewer 失败以 `Auto review of tool "" failed; its body was not executed: ` 拒绝调用,不带结构化错误信息。Provider 失败消息包含 finish 类型、失败代码与 provider 消息,因此模型与通用工具卡片显示实际原因。 diff --git a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml b/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml deleted file mode 100644 index 7ed8eaaa55..0000000000 --- a/.agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-24-fixed-rate-floor-one-minute.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-24-fixed-rate-floor-one-minute.md -/agent-note-one-minute-fixed-rate-floor: - en: d74434992e0ca252 - zh: cae88d61368d5555 -/agent-note-one-minute-fixed-rate-floor/problem: - en: a16373df16ee9126 - zh: 10e86f4cfe0e9a11 -/agent-note-one-minute-fixed-rate-floor/decision: - en: fb49d104c6539934 - zh: 04655695ccbb58c6 -/agent-note-one-minute-fixed-rate-floor/alternatives-considered: - en: 080ae22387dd35b7 - zh: 8247747dc444d7a4 -/agent-note-one-minute-fixed-rate-floor/consequences: - en: c11eac4968a2a707 - zh: c4a02ddeef3a5dff diff --git a/.agents/notes/implemented/feature/2026-09-24-session-inspector.i18n.yaml b/.agents/notes/implemented/feature/2026-09-24-session-inspector.i18n.yaml new file mode 100644 index 0000000000..e7c8e935b9 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-24-session-inspector.i18n.yaml @@ -0,0 +1,25 @@ +# Bilingual-pair consistency record for 2026-09-24-session-inspector.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-24-session-inspector.md +/agent-note-session-inspector-data-ownership-and-navigation: + en: bf781f92c7d025d3 + zh: bff723de610cadfb +/agent-note-session-inspector-data-ownership-and-navigation/problem: + en: 701a51da8516dda5 + zh: 5c816c6d0123e332 +/agent-note-session-inspector-data-ownership-and-navigation/decision: + en: 3cce35b9d81d0639 + zh: cffb09878c9a50b4 +/agent-note-session-inspector-data-ownership-and-navigation/runtime-diagnostics: + en: 3a31de3a4ec96a1d + zh: d231917978c34ef6 +/agent-note-session-inspector-data-ownership-and-navigation/alternatives-considered: + en: 64658e4c356224cb + zh: ceabef316dca2457 +/agent-note-session-inspector-data-ownership-and-navigation/consequences: + en: a19bcffa41bcbf4d + zh: e6eb665efc5e838b +/agent-note-session-inspector-data-ownership-and-navigation/testing: + en: ff09aac1983cef78 + zh: c3b5669826b5777b diff --git a/.agents/notes/implemented/feature/2026-09-24-session-inspector.md b/.agents/notes/implemented/feature/2026-09-24-session-inspector.md new file mode 100644 index 0000000000..c9006347d0 --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-24-session-inspector.md @@ -0,0 +1,58 @@ +# Agent Note: Session Inspector data ownership and navigation + +Status: implemented + +English | [中文](2026-09-24-session-inspector.zh.md) + +## Problem + +Debugging a Session requires its raw log, Chat structures, and rendered conversation side by side. Runtime objects share Node, Turn, and Step identities and contain cycles, so JSON alone cannot support reference navigation. The Inspector needs an independent entry without making every Conversation View a Sidebar resource. + +## Decision + +`experimental/session-inspector` registers the `session-inspector-log` Sidebar page and its Session-scoped body. The existing Sidebar owns the Session reference. The Inspector adds no resource provider, Conversation header slot, composer, or product-package navigation API. The [Session Inspector README](../../../../packages/experimental/session-inspector/README.md) owns the table controls and presentation behavior. + +- Session Controller and Chat retain ownership of the data. Only the selected table subscribes; the log uses the existing loaded window and pagination, without another Session stream or persistence format. +- The virtual table groups Turn → Step → event → Assistant block/delta. Open blocks expand and closed blocks fold. Automatic folding preserves the visible row or retained ancestor through trailing space; manual folding anchors the clicked header and pauses following. Sticky ancestors are chosen below the table header and earlier sticky rows. +- Chat keeps its actual root order and group membership. Materialized nodes outside visible groups are inserted by event anchor, not appended after the final Turn. Content changes retain row identities. +- WeakMap references identify Node, Group, their Data roots, and Turn/Step locations and Data readers. Breadcrumbs retain readers rather than copies. Node Data can expand inline; named references and ancestor detection stop recursive expansion. +- Object fields are read lazily in pages of 50 entries. Conversation publications invalidate expanded-field caches because Location Data readers can mutate without changing identity. UI-only renders reuse cached fields. Getters never run automatically, and weak collections remain opaque. +- Navigation stays in the experimental package. It uses existing Slot/Chat data attributes and the current UI Session to find the main Chat, excluding Sidebar, hidden views, and nested conversations. Searchable folded ancestors receive `beforematch`; the Inspector does not write Chat's fold state. +- Visible targets only flash. Offscreen targets use native smooth scrolling before a body-level clipped overlay flashes. User interruption, replacement, mode changes, or disposal cancel pending work. Log rows may map approximately to a nearby loaded Node, Group, or Turn; navigation never switches the main View or loads history. +- Picking uses a real click-catching mask over Chat. `elementsFromPoint()` and the isolated matching functions resolve the underlying record; the mask prevents its click action and forwards wheel movement to existing scrollports. Cancellation removes the mask, overlays, and listeners. + +## Runtime diagnostics + +The default-off `inspector-profile` optional bundle enables Session inspection and the NodeJS debugging Worker with unredacted Host fetch capture. Enabling the bundle requires no debugging argument or user override. The implementation and explicit source/built overlays remain available; Web startup accepts no new argument and Settings gains no debugging button. + +The Host owns the debugging URL, Worker, and capture lifetime. When the composition supplies `--inspect`, the Host attempts to open Chrome; failure leaves the printed link usable. Closing the debugging window does not stop capture. Explicit activation permits local code execution and unredacted network capture, as disclosed in the [Inspector security section](../../../../packages/experimental/inspector/README.md#security). + +A Client loaded after the page obtains current ingest parameters through an authenticated document-relative Connection route. Initial injection still supports early connections. Connection resets refresh parameters; replacement releases the previous source, and disposal aborts and waits for outstanding work. The Client never receives or launches the debugging URL. + +## Alternatives considered + +**Generic Conversation View resources and header labels.** They require address validation, separate Session references, unavailable-view recovery, and composer options for a single consumer. An existing Sidebar page type is sufficient; a generic protocol needs independent consumers before reconsideration. + +**Sidebar registration in `ui-conversation`, including split header exports.** Sidebar already consumes Conversation headers. Reversing that dependency creates a TypeScript project-reference cycle that optional runtime injection and type-only imports cannot remove. The experimental consumer depends on both packages through their normal `/client` entries. + +**Reuse `subagentchat`.** It owns a complete subagent conversation and composer, not raw inspection. A dedicated page leaves ordinary and subagent Chat behavior unchanged. + +**Target-owned navigation through generic events.** This isolates DOM implementation but adds request types, routing, acknowledgements, and lifecycle code to product packages for an experimental consumer. Inspector-owned DOM adaptation accepts maintenance when existing attributes or layout change. + +**Serialize all Chat objects as JSON.** Shared identities, cycles, and Map keys would lose interactive structure. Weak references and a lazy object tree preserve navigation without retaining replaced snapshots. + +**Shrink the list immediately after a stream closes.** A long block's collapse changes the viewport. Trailing space preserves the anchor and is consumed by new rows; Follow Latest clears it. + +**Read initial page injection only.** Late plugin activation has no initial injection. Fetching the current bootstrap decouples connection from page-load timing. + +**Let the Client open the debugging URL.** The browser page does not own Host application startup. Host-side launch keeps URL handling and process lifetime together without another launch service. + +## Consequences + +The Inspector owns local filters, disclosures, selection, breadcrumbs, and highlights, not Chat business state. It can only inspect loaded history and mounted main-Chat content. Projection control-stream updates are not displayed. The Session Inspector plugin alone starts no debugging endpoint or request capture; enabling the optional bundle also enables NodeJS Inspector and fetch capture. + +The earlier [optional-bundle decision](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) continues to own installation and provider-runtime trade-offs. This feature adds the Inspector profile to that list rather than making all experimental providers optional bundles. + +## Testing + +Package tests exercise registration disposal, hierarchy and folding anchors, hidden-node ordering, filters, object navigation and cache invalidation, DOM scope, smooth scrolling, picking, and cancellation. Work-count regressions bound indexed first-page reads and prevent historical regrouping during append. Diagnostic tests cover Loader activation, injected and fetched bootstrap failures, reconnect recovery, registration rollback, failed window launch, and joined disposal. The Web scenario in `snapshots/web/session-inspector` replays a recorded Session through the optional bundle and checks log filtering, object details, and Chat Group order. diff --git a/.agents/notes/implemented/feature/2026-09-24-session-inspector.zh.md b/.agents/notes/implemented/feature/2026-09-24-session-inspector.zh.md new file mode 100644 index 0000000000..d619fe182f --- /dev/null +++ b/.agents/notes/implemented/feature/2026-09-24-session-inspector.zh.md @@ -0,0 +1,58 @@ +# Agent Note: Session Inspector 数据所有权与导航 + +Status: implemented + +[English](2026-09-24-session-inspector.md) | 中文 + +## 问题 + +调试 Session 需要并排查看原始日志、Chat 结构和会话内容。运行时对象共享 Node、Turn、Step 身份并包含循环引用,只显示 JSON 无法支持引用导航。检查器需要独立入口,但不需要把所有 Conversation View 变成 Sidebar 资源。 + +## 决策 + +`experimental/session-inspector` 注册 `session-inspector-log` Sidebar 页面及其 Session 作用域正文。已有 Sidebar 管理 Session 引用。检查器不增加资源提供方、Conversation header slot、composer 或产品包导航 API。[Session Inspector README](../../../../packages/experimental/session-inspector/README.zh.md) 描述表格操作与展示行为。 + +- Session Controller 和 Chat 保留数据所有权。只有当前选中的表格订阅;日志使用现有已加载窗口和分页,不增加 Session 流或持久化格式。 +- 虚拟表格按 Turn → Step → 事件 → Assistant block/delta 分组。未闭合 block 展开,闭合后折叠。自动折叠通过尾部留白保留可见行或仍存在的祖先位置;手动折叠锚定被点击组头并暂停跟随。吸顶祖先在表头和前面吸顶行之下选择。 +- Chat 保留真实根顺序和分组成员关系。未进入可见分组的已物化节点按事件锚点插回,不追加到最后一个 Turn 之后。内容更新保留行身份。 +- WeakMap 引用标记 Node、Group、各自 Data 根对象、Turn/Step 位置及 Data reader。面包屑保存 reader 而非副本。Node Data 可原地展开;具名引用和祖先检测阻止循环展开。 +- 对象字段按需读取,每页 50 项。Location Data reader 可以原地变化而保留身份,因此 Conversation 发布会使展开字段缓存失效。纯 UI 重渲染复用缓存。Getter 不自动执行,弱集合内容保持不透明。 +- 导航逻辑留在实验包内。检查器通过已有 Slot/Chat data 属性和当前 UI Session 查找主 Chat,排除 Sidebar、隐藏视图与嵌套会话。可搜索的折叠祖先接收 `beforematch`;检查器不写入 Chat 的折叠状态。 +- 已可见目标只闪烁。屏外目标先使用原生平滑滚动,再在 body 层通过裁剪浮层闪烁。用户打断、替换请求、切换模式或释放会取消未完成操作。日志行允许近似映射到附近已加载 Node、Group 或 Turn;定位不切换主 View,也不加载历史。 +- 反向点选通过主 Chat 上的真实点击蒙层完成。`elementsFromPoint()` 和独立匹配函数反查底下记录;蒙层拦截底下控件的点击,并把滚轮交给已有滚动区域。取消会移除蒙层、浮层与监听。 + +## 运行时诊断 + +默认关闭的可选组合包 `inspector-profile` 启用会话检查能力、NodeJS 调试 Worker 和未脱敏的 Host fetch 采集。启用组合包不需要调试参数或用户覆盖配置。实现及显式源码/构建产物 overlay 保持可用;Web startup 不接受新参数,设置区不增加调试按钮。 + +Host 管理调试 URL、Worker 和采集生命周期。组合配置提供 `--inspect` 时,Host 尝试打开 Chrome;打开失败后打印的链接仍可使用。关闭调试窗口不会停止采集。显式启用允许执行本地代码并采集未脱敏网络数据,风险说明见 [Inspector 安全部分](../../../../packages/experimental/inspector/README.zh.md#security)。 + +页面加载后启用的 Client 通过已鉴权、文档相对的 Connection 路由获取当前 ingest 参数。初始注入仍支持早期建联。连接重置会刷新参数;替换先释放旧 source,卸载会取消并等待未完成操作。Client 不接收也不打开调试 URL。 + +## 考虑过的替代方案 + +**通用 Conversation View 资源与 header label。** 这需要为一个消费者增加地址校验、独立 Session 引用、视图不可用恢复和 composer 选项。已有 Sidebar 页面类型足够;通用协议应等独立消费者出现后再评审。 + +**在 `ui-conversation` 注册 Sidebar,包括细分 header 导出。** Sidebar 已消费 Conversation header。反向依赖形成 TypeScript project-reference 环,运行时弱注入和仅类型导入不能消除。实验消费者通过正常 `/client` 入口同时依赖两包。 + +**复用 `subagentchat`。** 它承载完整 subagent 会话与 composer,而不是原始数据检查。独立页面不改变普通会话和 subagent Chat 行为。 + +**通过通用事件实现目标 View 定位。** 这能隔离 DOM 实现,但需要为实验消费者在产品包增加请求类型、路由、确认和生命周期代码。Inspector 自持 DOM 适配,接受已有属性或布局变化时同步维护的代价。 + +**把所有 Chat 对象序列化为 JSON。** 共享身份、循环引用和 Map key 会失去交互结构。弱引用与按需对象树保留导航,也不持有被替换的快照。 + +**流结束后立即缩短列表。** 长 block 折叠会改变视口。尾部留白保留锚点,并由新行逐渐填充;跟踪最新会清除留白。 + +**只读取页面初始注入。** 后启用插件没有初始注入。获取当前 bootstrap 可以让建联不依赖页面加载时机。 + +**让 Client 打开调试 URL。** 浏览器页面不管理 Host 应用启动。由 Host 启动可以统一 URL 处理与进程生命周期,不增加启动服务。 + +## 后果 + +检查器管理局部筛选、折叠、选中、面包屑和高亮,不拥有 Chat 业务状态。它只检查已加载历史和主 Chat 中已挂载内容,不展示 Projection 控制流更新。单独启用 Session Inspector 插件不会启动调试端口或请求采集;启用可选组合包也会启用 NodeJS Inspector 和 fetch 采集。 + +此前的[可选组合包决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md) 继续管理安装与提供方运行时的取舍。本功能向名单增加 Inspector profile,而不是把所有实验提供方都做成可选组合包。 + +## 测试 + +包级测试覆盖注册释放、层级与折叠锚点、隐藏节点顺序、筛选、对象导航与缓存失效、DOM 范围、平滑滚动、点选和取消。工作量回归限制索引集合的首屏读取量,并防止追加时重新分组历史行。诊断测试覆盖 Loader 启用、注入和主动获取 bootstrap 的失败、重连恢复、注册回滚、打开窗口失败与等待释放完成。`snapshots/web/session-inspector` 中的 Web 场景通过可选组合包重放录制 Session,检查日志筛选、对象详情和 Chat Group 顺序。 diff --git a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.i18n.yaml b/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.i18n.yaml deleted file mode 100644 index e1dbd577a9..0000000000 --- a/.agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-27-desktop-cli-runtime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-27-desktop-cli-runtime.md -/agent-note-desktop-installed-cli-runtime: - en: 0527f32e8a3c13eb - zh: 77dde6e91fc1a636 -/agent-note-desktop-installed-cli-runtime/problem: - en: fb07307392d14fd9 - zh: a334f905b26eef98 -/agent-note-desktop-installed-cli-runtime/decision: - en: 163c098af7ce6b7b - zh: 0c142db0f173f761 -/agent-note-desktop-installed-cli-runtime/consequences: - en: 1dfe287934c9f4bc - zh: 138f226ec7409a1f -/agent-note-desktop-installed-cli-runtime/command-registration: - en: 75ff35db64138e9a - zh: a8f799c2d568fa8b -/agent-note-desktop-installed-cli-runtime/alternatives-considered: - en: 91974c10416cf0fd - zh: fe26c0b4f5f37e98 diff --git a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.i18n.yaml b/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.i18n.yaml deleted file mode 100644 index 24c40aec52..0000000000 --- a/.agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-28-desktop-login-shell-environment.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-09-28-desktop-login-shell-environment.md -/agent-note-desktop-reads-the-login-shell-environment-once: - en: f3d08cb5b03f7032 - zh: 2f117f42aed85bc3 -/agent-note-desktop-reads-the-login-shell-environment-once/problem: - en: 3a4b92c977e42f45 - zh: 575d92a41e29ae7a -/agent-note-desktop-reads-the-login-shell-environment-once/decision: - en: d2af1761d96c498a - zh: 8419dea042446494 -/agent-note-desktop-reads-the-login-shell-environment-once/alternatives-considered: - en: ed9ed8bb06a19d7f - zh: 52ceb0d70f0d05a7 -/agent-note-desktop-reads-the-login-shell-environment-once/consequences: - en: f3cd204ab6691f1d - zh: 00c9ae619065b4c3 diff --git a/.agents/notes/implemented/process/2026-06-11-quality-gates.i18n.yaml b/.agents/notes/implemented/process/2026-06-11-quality-gates.i18n.yaml deleted file mode 100644 index 515c9508fd..0000000000 --- a/.agents/notes/implemented/process/2026-06-11-quality-gates.i18n.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-11-quality-gates.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-11-quality-gates.md -/agent-note-mechanical-quality-gates-over-prose-guidelines: - en: ee219fd65689e5ea - zh: a5eb719557f02ad6 -/agent-note-mechanical-quality-gates-over-prose-guidelines/problem: - en: 526b060333ea2457 - zh: 182f46699589aff5 -/agent-note-mechanical-quality-gates-over-prose-guidelines/decision: - en: c6d1551e56d22b99 - zh: 6b7efc184274b7ac -/agent-note-mechanical-quality-gates-over-prose-guidelines/consequences: - en: 7bf0c081f242501e - zh: 30f892fbd982f1b2 diff --git a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml b/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml deleted file mode 100644 index d4354efa31..0000000000 --- a/.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-16-pnpm-over-yarn.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md -/agent-note-pnpm-as-the-package-manager-instead-of-yarn-4: - en: 2c8be5b7d3dae8c7 - zh: e6d4d5dc41455f52 -/agent-note-pnpm-as-the-package-manager-instead-of-yarn-4/problem: - en: a397e5f0a83c3306 - zh: 5ced99f94dae9e30 -/agent-note-pnpm-as-the-package-manager-instead-of-yarn-4/decision: - en: cadbbd8b1bab85ef - zh: 31fcb11ddbcb79c3 -/agent-note-pnpm-as-the-package-manager-instead-of-yarn-4/alternatives-considered: - en: 20e2ebed65f92625 - zh: f1717d69d271fb43 -/agent-note-pnpm-as-the-package-manager-instead-of-yarn-4/consequences: - en: 024fefe88e8bbc95 - zh: 018dc287c07d6483 diff --git a/.agents/notes/implemented/process/2026-06-17-ts-build-config.i18n.yaml b/.agents/notes/implemented/process/2026-06-17-ts-build-config.i18n.yaml deleted file mode 100644 index 3d3e5e065d..0000000000 --- a/.agents/notes/implemented/process/2026-06-17-ts-build-config.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-17-ts-build-config.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-17-ts-build-config.md -/agent-note-tsc-first-build-and-one-compiler-ownership: - en: 98c341593a211f00 - zh: 5c051e3aaff4847e -/agent-note-tsc-first-build-and-one-compiler-ownership/problem: - en: 62307ddfb9e110cb - zh: 236bdad7df92a028 -/agent-note-tsc-first-build-and-one-compiler-ownership/decision: - en: e2b61f1e0549117d - zh: 596ca8cf8207f2b5 -/agent-note-tsc-first-build-and-one-compiler-ownership/alternatives-considered: - en: 9b143ba610f06e2f - zh: 7832be7816aec7e6 -/agent-note-tsc-first-build-and-one-compiler-ownership/consequences: - en: 05b0f4a3a96e9435 - zh: 2903da5edbaf3cb7 diff --git a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml deleted file mode 100644 index 59b17b44ff..0000000000 --- a/.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-02-bilingual-docs-and-pairing-gate.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate: - en: 04529b9cc16be36d - zh: 5fb4377453231503 -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/problem: - en: 0ca5c6556229e3ee - zh: 6dacd60a7dc4bff4 -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/decision: - en: 46b0ca2b0f45eb6c - zh: 3ddcd36e9ef8d134 -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/verification: - en: 8fbf681b863a831c - zh: e5ba11a549a92612 -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/alternatives-considered: - en: 995a746c59ea7b6c - zh: 04a248329dc0d04b -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/industry-precedent: - en: 6b5d62086bfd3868 - zh: 219dad72943f590a -/agent-note-bilingual-documentation-via-paired-sibling-files-and-a-pairing-gate/consequences: - en: 65f9f024d99d438f - zh: ff624c8be88c5830 diff --git a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.i18n.yaml b/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.i18n.yaml deleted file mode 100644 index e72a4d0af7..0000000000 --- a/.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-04-doc-tiers-and-budgets.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md -/agent-note-documentation-structure-tiers-and-budgets: - en: f2add84f6fdb18d0 - zh: 7f97fab8f9234bb9 -/agent-note-documentation-structure-tiers-and-budgets/problem: - en: 6ecec6cc312745dd - zh: ac1244b6113b89da -/agent-note-documentation-structure-tiers-and-budgets/decision: - en: 8a3a2e086af59a6c - zh: d1d45a4a108031a2 -/agent-note-documentation-structure-tiers-and-budgets/alternatives-considered: - en: 8275e8f6ffe18def - zh: 06a5af3e5581f45b -/agent-note-documentation-structure-tiers-and-budgets/consequences: - en: 76c254b56618cef6 - zh: 1f1264d6879df20c diff --git a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.i18n.yaml b/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.i18n.yaml deleted file mode 100644 index c860b4f1d3..0000000000 --- a/.agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-05-uniform-agent-note-format.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md -/agent-note-one-gated-in-file-format-for-agent-notes: - en: 1d02708a467f4a07 - zh: 3c924198ec72c881 -/agent-note-one-gated-in-file-format-for-agent-notes/problem: - en: 71bd2d16e40c3ed7 - zh: 485862218afcdc00 -/agent-note-one-gated-in-file-format-for-agent-notes/decision: - en: 246ec96b0c1da90d - zh: 12a06608ffdf28f8 -/agent-note-one-gated-in-file-format-for-agent-notes/alternatives-considered: - en: 330044b841da3ab9 - zh: e909207cbcbc5edb -/agent-note-one-gated-in-file-format-for-agent-notes/consequences: - en: 345e6318b76b1a06 - zh: 63cef25e59d91654 diff --git a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml deleted file mode 100644 index 60dff04b5b..0000000000 --- a/.agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-06-parallel-pre-push-gates.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md -/agent-note-parallel-pre-push-gates: - en: d5ee83ee15159e15 - zh: 07a6599af21d98a9 -/agent-note-parallel-pre-push-gates/problem: - en: 896ffdae7d51cc19 - zh: eed0d7e80456bd8e -/agent-note-parallel-pre-push-gates/decision: - en: c95d213689164ad6 - zh: da85165d13b26d58 -/agent-note-parallel-pre-push-gates/verification: - en: c7124c68afb12a8e - zh: 0a0269476ae353f3 -/agent-note-parallel-pre-push-gates/alternatives-considered: - en: d54f4ed806116b2e - zh: 38725684130bb843 -/agent-note-parallel-pre-push-gates/consequences: - en: 5d22505a7c041e41 - zh: ba1010f18d3e8302 diff --git a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.i18n.yaml b/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.i18n.yaml deleted file mode 100644 index 0496ff4408..0000000000 --- a/.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-12-package-model-experience-contract.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md -/agent-note-package-model-experience-contract: - en: c14f97f66251831b - zh: a7ef93d593a72215 -/agent-note-package-model-experience-contract/problem: - en: 69034b9a9ba40d49 - zh: 016b64cb54c4c873 -/agent-note-package-model-experience-contract/decision: - en: f67c13ad41004520 - zh: 28cf10f7d217f626 -/agent-note-package-model-experience-contract/alternatives-considered: - en: 206bc943c04313b1 - zh: 6d295d9da6675854 -/agent-note-package-model-experience-contract/consequences: - en: dc6057b8836bb3fc - zh: 6e41a27a99ae0339 diff --git a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml b/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml deleted file mode 100644 index 42524fde30..0000000000 --- a/.agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-remove-generated-agent-note-index.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-19-remove-generated-agent-note-index.md -/agent-note-keep-agent-notes-discoverable-without-a-generated-index: - en: aedcead60a29472a - zh: 0b17ad37acb4a92e -/agent-note-keep-agent-notes-discoverable-without-a-generated-index/problem: - en: 8a66ca1096aab8f4 - zh: f9774f5f3e7d0a31 -/agent-note-keep-agent-notes-discoverable-without-a-generated-index/decision: - en: 2b02e79f20d9be1b - zh: 5dec073afcee9c2e -/agent-note-keep-agent-notes-discoverable-without-a-generated-index/alternatives-considered: - en: 0317d3d10b97f004 - zh: 9315c31a17f7057a -/agent-note-keep-agent-notes-discoverable-without-a-generated-index/consequences: - en: 631728aa0d482555 - zh: 910a749ae88d4c83 diff --git a/.agents/notes/implemented/process/2026-07-19-web-styling-system.i18n.yaml b/.agents/notes/implemented/process/2026-07-19-web-styling-system.i18n.yaml deleted file mode 100644 index 7889068a5c..0000000000 --- a/.agents/notes/implemented/process/2026-07-19-web-styling-system.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-web-styling-system.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-19-web-styling-system.md -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints: - en: 60e793c38185c08c - zh: 8f4d4644e3502c31 -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/problem: - en: aa546850f8c51cd1 - zh: bba14f66aaf1e06a -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/decision: - en: 72a1d3545d1ebd6d - zh: f2fdc9006468aa72 -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/engineering-constraints: - en: 39f1984d96e609ed - zh: 14eda6caf43c5514 -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/the-execution-shape-for-agents: - en: 10342491b9dcc1a7 - zh: cd23d8c2f8ef3d26 -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/division-of-labor-with-web-styling-md: - en: 91e06333e11b1417 - zh: c743f392dbd9391f -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/consequences: - en: 128333a6495cbfa0 - zh: f165ef2f3d76f216 -/agent-note-web-styling-system-the-token-framework-and-engineering-constraints/alternatives-considered: - en: 6579ca659b112618 - zh: f1a650f4af1b8cc2 diff --git a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml b/.agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml deleted file mode 100644 index d91e7a4590..0000000000 --- a/.agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-20-gui-testing-system.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-20-gui-testing-system.md -/agent-note-gui-testing-system-the-three-tier-structure: - en: 66959d1c7c3cad28 - zh: 1e29f27d36c45909 -/agent-note-gui-testing-system-the-three-tier-structure/problem: - en: 1c54e58ea993ef6a - zh: 73108687fa58a6f2 -/agent-note-gui-testing-system-the-three-tier-structure/decision: - en: 4c6e52549113c2c7 - zh: 586175edee8b128c -/agent-note-gui-testing-system-the-three-tier-structure/lane-map: - en: 81d53390f31e540e - zh: 272a2b2b93269c45 -/agent-note-gui-testing-system-the-three-tier-structure/anti-regression-discipline: - en: 80fe1589a6236740 - zh: 30e1911d37865c49 -/agent-note-gui-testing-system-the-three-tier-structure/consequences: - en: 22f2475a438c639c - zh: aafab5ff60b7c644 -/agent-note-gui-testing-system-the-three-tier-structure/alternatives-considered: - en: 19e29023b70018b0 - zh: d47ff8e62a0befb6 diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml index c8afbe8e53..c1501b5647 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.i18n.yaml @@ -9,8 +9,8 @@ en: 4fb1c27a631b32d3 zh: 9dbe9a4b9aadfca9 /agent-note-serial-cross-platform-ci-reference/decision: - en: 6c682d0b7b2a03ca - zh: b75950853f45cccf + en: c88f606c0749bd67 + zh: b2038b0d92fd8890 /agent-note-serial-cross-platform-ci-reference/alternatives-considered: en: 3610152c5c4e93af zh: f4c09d327716f750 diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md index abe34e58a4..35fa389802 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.md @@ -16,7 +16,7 @@ Real-kernel sandbox proofs require specific hosted operating systems and archite ## Decision -[CI](../../../../.github/workflows/ci.yml) (pull-request-only) and [CI master](../../../../.github/workflows/ci-master.yml) (master-push + workflow_dispatch) give pull-request and master-push events complementary responsibilities. Pull requests run Linux, native Windows, [Node compatibility with self-hosted routing under the Linux variable and trust conditions](2026-09-06-node-compatibility-selfhosted.md), and Python checks; [platform scheduling](2026-09-06-master-only-platform-ci.md) assigns Wine and three Python runtime carriers to master pushes. On a push to `master`, the active references are `serial / linux (self-hosted standby)` on the in-house `vm-backup` pool and `serial / windows (self-hosted standby)` on the in-house `dsh-win-ci` pool — the hot-standby drills that continuously re-prove the failover targets described in the [failover runbook](2026-07-26-ci-failover-runbook.md). There is no standard-hosted `serial / linux` definition; the standard-hosted `serial / macos` remains disabled under `TODO(hosted-serial-ci)` until its portable capacity can be restored. The current `serial / windows` definition is the in-house `dsh-win-ci` standby. The separate job definitions intentionally keep their short checkout, runtime setup, and immutable install sequences visible instead of hiding operating systems behind a matrix or reusable workflow. `workflow_dispatch` is reserved for runner benchmarks. +[CI](../../../../.github/workflows/ci.yml) (pull-request-only) and [CI master](../../../../.github/workflows/ci-master.yml) (master-push + workflow_dispatch) give pull-request and master-push events complementary responsibilities. Pull requests run Linux, native Windows, [Node compatibility with self-hosted routing under the Linux variable and trust conditions](../../../../.github/workflows/ci.yml), and Python checks; [platform scheduling](../../../../.github/workflows/ci-master.yml) assigns Wine and three Python runtime carriers to master pushes. On a push to `master`, the active references are `serial / linux (self-hosted standby)` on the in-house `vm-backup` pool and `serial / windows (self-hosted standby)` on the in-house `dsh-win-ci` pool — the hot-standby drills that continuously re-prove the failover targets described in the [failover runbook](2026-07-26-ci-failover-runbook.md). There is no standard-hosted `serial / linux` definition; the standard-hosted `serial / macos` remains disabled under `TODO(hosted-serial-ci)` until its portable capacity can be restored. The current `serial / windows` definition is the in-house `dsh-win-ci` standby. The separate job definitions intentionally keep their short checkout, runtime setup, and immutable install sequences visible instead of hiding operating systems behind a matrix or reusable workflow. `workflow_dispatch` is reserved for runner benchmarks. Each reference job runs `pnpm run check:ci` without any shard selector. `DSH_GATE_CONCURRENCY=1` makes the top-level aggregate execute one ready gate at a time; coverage, snapshot replay, built-bin smoke, and publication validation also receive worker counts of one. The reference jobs may run beside one another, but each host's repository gates are serial and complete. Linux installs bubblewrap before replaying snapshots, and Windows enables Developer Mode before installing the symlinked workspace. diff --git a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md index 832af4e655..98ab14a3e1 100644 --- a/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md +++ b/.agents/notes/implemented/process/2026-07-21-serial-cross-platform-ci-reference.zh.md @@ -16,7 +16,7 @@ Status: implemented ## 决策 -[CI](../../../../.github/workflows/ci.yml)(仅 pull request)与 [CI master](../../../../.github/workflows/ci-master.yml)(master 推送 + `workflow_dispatch`)为拉取请求事件与 master 推送事件赋予互补的职责。拉取请求运行 Linux、原生 Windows、[仅在 Linux 变量与信任条件满足时使用自托管路由的 Node 兼容性](2026-09-06-node-compatibility-selfhosted.zh.md)与 Python 检查;[平台调度](2026-09-06-master-only-platform-ci.zh.md)将 Wine 与三个 Python 运行时载体分配给 master 推送。向 `master` 推送时,当前启用的参考作业是公司自有 `vm-backup` 池上的 `serial / linux (self-hosted standby)` 和 `dsh-win-ci` 池上的 `serial / windows (self-hosted standby)`——这些热备演练持续验证[故障切换手册](2026-07-26-ci-failover-runbook.zh.md)所描述的切换目标。不存在标准托管的 `serial / linux` 定义;标准托管的 `serial / macos` 仍处于禁用状态,并由 `TODO(hosted-serial-ci)` 标记,直到其可移植容量恢复。当前 `serial / windows` 定义是公司自有 `dsh-win-ci` 池的 standby。各自独立的作业定义有意显式保留简短的代码检出、运行时设置和依赖锁定的安装步骤,而不是用矩阵或可复用工作流隐藏操作系统差异。`workflow_dispatch` 仅用于运行器基准测试。 +[CI](../../../../.github/workflows/ci.yml)(仅 pull request)与 [CI master](../../../../.github/workflows/ci-master.yml)(master 推送 + `workflow_dispatch`)为拉取请求事件与 master 推送事件赋予互补的职责。拉取请求运行 Linux、原生 Windows、[仅在 Linux 变量与信任条件满足时使用自托管路由的 Node 兼容性](../../../../.github/workflows/ci.yml)与 Python 检查;[平台调度](../../../../.github/workflows/ci-master.yml)将 Wine 与三个 Python 运行时载体分配给 master 推送。向 `master` 推送时,当前启用的参考作业是公司自有 `vm-backup` 池上的 `serial / linux (self-hosted standby)` 和 `dsh-win-ci` 池上的 `serial / windows (self-hosted standby)`——这些热备演练持续验证[故障切换手册](2026-07-26-ci-failover-runbook.zh.md)所描述的切换目标。不存在标准托管的 `serial / linux` 定义;标准托管的 `serial / macos` 仍处于禁用状态,并由 `TODO(hosted-serial-ci)` 标记,直到其可移植容量恢复。当前 `serial / windows` 定义是公司自有 `dsh-win-ci` 池的 standby。各自独立的作业定义有意显式保留简短的代码检出、运行时设置和依赖锁定的安装步骤,而不是用矩阵或可复用工作流隐藏操作系统差异。`workflow_dispatch` 仅用于运行器基准测试。 每个参考作业均在不设置任何分片选择器的情况下运行 `pnpm run check:ci`。`DSH_GATE_CONCURRENCY=1` 使顶层聚合每次只执行一个已经就绪的门禁;覆盖率、快照回放、built-bin 冒烟测试和发布验证的 worker 数量也设为 1。各参考作业可以彼此并行,但每台主机上的仓库门禁都串行运行且完整执行。Linux 在回放快照前安装 bubblewrap,Windows 则在安装采用符号链接的工作区前启用开发人员模式。 diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml index 14ae915151..de88f44003 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.i18n.yaml @@ -6,14 +6,14 @@ en: 677b522817e84e2f zh: f8518a7033b0fd54 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/problem: - en: 317fafe666a76953 - zh: c886a5a4bccceda0 + en: 3ecedc74cceced3b + zh: f4708f1887d42c61 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/decision: - en: 2520d6c0d2bfe1a6 - zh: 616c70b8b1026109 + en: fbc8d8ef03350ac9 + zh: 7fc81bf75eaab0b7 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/decision/release-rehearsals-share-the-linux-switch: - en: 7fdad4f7d060d659 - zh: 6773db9b7d62cd0a + en: 01e74068e0a96375 + zh: aa811861ce30cb47 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/decision/what-the-in-house-pool-is: en: 00d8934ed30e838c zh: bdb5584fbaf27c7d @@ -24,8 +24,8 @@ en: 2e3829fd72d6b1b0 zh: 563f0abade7974f9 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/capacity-during-failover: - en: 4095da1b492d7484 - zh: 0c0bb9c97e4af977 + en: 22611ed4316adc7c + zh: 29acd02b4a07313d /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/capacity-during-failover/switch-back: en: 63cf911019db8d2f zh: 5eb3903b0ceb30db @@ -36,5 +36,5 @@ en: e68b70d7caca52f1 zh: e6f0be792105a9e6 /agent-note-ci-failover-runbook-hosted-pools-in-house-pool/consequences: - en: f66e0aa401136799 - zh: 657188692959955e + en: d6de79eb73b7a6a2 + zh: 329d8ed8e3e02f7b diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md index d46f9d1408..f745cb4e70 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md @@ -6,17 +6,17 @@ English | [中文](2026-07-26-ci-failover-runbook.zh.md) ## Problem -The three required Linux worker jobs in [CI](../../../../.github/workflows/ci.yml) (`node 24 / static`, `node 24 / coverage`, `node 24 / snapshots and artifacts`) run on the hosted enterprise 32-core pools; the required verdict job that aggregates them (`all checks passed`) runs on standard `ubuntu-latest`; the [native Windows jobs](2026-08-08-native-windows-pull-request-ci.md) run on the hosted `dsh-windows-2025-16core` larger runner. When the enterprise pools degrade — jobs queue indefinitely or the enterprise labels vanish — every open pull request becomes unmergeable, and the ordinary recovery of merging a fix is itself deadlocked behind the very required checks that cannot run. **Scope: two independent switches, one per platform.** `DSH_CI_FAILOVER_LINUX` recovers an enterprise Linux-pool outage (the three required Linux workers plus the `all checks passed` verdict); `DSH_CI_FAILOVER_WINDOWS` recovers a hosted Windows-pool outage (the native Windows jobs). A Linux-pool outage need not retarget Windows jobs and vice versa. The [Node compatibility jobs](2026-09-06-node-compatibility-selfhosted.md) also follow the Linux switch with isolated setup; the verdict's `node-24-bench`, `python-sdk`, and `python-runtime` dependencies stay on standard hosted runners; in a broader GitHub-hosted capacity failure that also takes out the standard pools, those dependencies still block `all checks passed`. An outage therefore needs a switch any responder with repository write access can throw without merging anything. +The three required Linux worker jobs in [CI](../../../../.github/workflows/ci.yml) (`node 24 / static`, `node 24 / coverage`, `node 24 / snapshots and artifacts`) run on the hosted enterprise 32-core pools; the required verdict job that aggregates them (`all checks passed`) runs on standard `ubuntu-latest`; the [native Windows jobs](2026-08-08-native-windows-pull-request-ci.md) run on the hosted `dsh-windows-2025-16core` larger runner. When the enterprise pools degrade — jobs queue indefinitely or the enterprise labels vanish — every open pull request becomes unmergeable, and the ordinary recovery of merging a fix is itself deadlocked behind the very required checks that cannot run. **Scope: two independent switches, one per platform.** `DSH_CI_FAILOVER_LINUX` recovers an enterprise Linux-pool outage (the three required Linux workers plus the `all checks passed` verdict); `DSH_CI_FAILOVER_WINDOWS` recovers a hosted Windows-pool outage (the native Windows jobs). A Linux-pool outage need not retarget Windows jobs and vice versa. The [Node compatibility jobs](../../../../.github/workflows/ci.yml) also follow the Linux switch with isolated setup; the verdict's `node-24-bench`, `python-sdk`, and `python-runtime` dependencies stay on standard hosted runners; in a broader GitHub-hosted capacity failure that also takes out the standard pools, those dependencies still block `all checks passed`. An outage therefore needs a switch any responder with repository write access can throw without merging anything. ## Decision The three primary Linux jobs (`node-24`, `node-24-coverage`, `node-24-consumers`), the three `node-compat` matrix entries, and `all-checks-passed` resolve through `DSH_CI_FAILOVER_LINUX`; the native Windows jobs resolve through `DSH_CI_FAILOVER_WINDOWS`. A platform switch does not redirect the other platform. Set to `selfhosted` by a repository writer, the applicable trusted jobs select `vm-backup` or `dsh-win-ci`; the `blacksmith` value routes the participating jobs per the [blacksmith failover leg note](2026-09-09-blacksmith-failover-leg.md); unset or any other value retains the workflow-defined hosted fallbacks. Node compatibility jobs require a same-repository, non-fork head and a non-Dependabot author, use isolated runtime setup, and retain the `ubuntu-latest` fallback under unset and non-special values; the blacksmith branch carries none of those predicates. Under the `selfhosted` value, Linux failover bounds snapshot concurrency and skips hosted package-cache restores. The verdict follows its workers so it does not remain queued on an unavailable hosted pool. Each switch is writer-manageable repository state, not a merge, so it works while checks are red. The `serial / linux (self-hosted standby)` and `serial / windows (self-hosted standby)` lanes re-prove the complete unsharded aggregates on master pushes. -The [superseded-CI cancellation policy](2026-09-09-cancel-superseded-ci.md) governs master pushes and manual runs in the same workflow/ref group, including standby drills. Rapid master updates can starve a drill before it reaches a verdict. Use the latest completed standby verdict and check its age and commit before treating it as readiness evidence; a cancelled or merely scheduled run is not proof of readiness. +The [superseded-CI cancellation policy](../../../../.github/workflows/ci.yml) governs master pushes and manual runs in the same workflow/ref group, including standby drills. Rapid master updates can starve a drill before it reaches a verdict. Use the latest completed standby verdict and check its age and commit before treating it as readiness evidence; a cancelled or merely scheduled run is not proof of readiness. ### Release rehearsals share the Linux switch -`DSH_CI_FAILOVER_LINUX=selfhosted` also routes the credential-free dependency-layout job and both dsh/vendor pack jobs onto `vm-backup` for eligible same-repository PRs and master pushes. Their [release rehearsal decision](2026-09-06-release-rehearsal-selfhosted.md) owns the stricter event eligibility and hosted manual dispatch. This coupling is intentional: keeping the variable set to save release minutes also keeps the eligible main-CI Linux jobs self-hosted. Clearing it returns both workloads to their hosted targets for subsequent runs; publication stays hosted regardless. +`DSH_CI_FAILOVER_LINUX=selfhosted` also routes the credential-free dependency-layout job and both dsh/vendor pack jobs onto `vm-backup` for eligible same-repository PRs and master pushes. Their [release rehearsal reference](../../../../.github/workflows/release.yml) owns the stricter event eligibility and hosted manual dispatch. This coupling is intentional: keeping the variable set to save release minutes also keeps the eligible main-CI Linux jobs self-hosted. Clearing it returns both workloads to their hosted targets for subsequent runs; publication stays hosted regardless. ### What the in-house pool is @@ -40,7 +40,7 @@ The two switches are independent: flip only the one whose platform is degraded. ## Capacity during failover -Capacity includes the master standby, main-CI jobs, and three release-rehearsal jobs for each eligible PR or master push while the Linux switch is set. Each trusted PR also adds three Node compatibility jobs at gate concurrency one, including the build-backed Node 22 leg and cold temporary runtime downloads. The release rehearsal workflows cancel superseded runs within each workflow/ref group under the [cancellation policy](2026-09-09-cancel-superseded-ci.md); different refs can still add concurrent build, pack, and install load. Check current CPU, memory, disk, and queue pressure before extending self-hosted operation; extra registrations on this VM add scheduling slots, not machine resources. Do not infer spare capacity from the standby alone. When host resources permit extra registrations, use an org registration token (org Settings → Actions → Runners → New runner). Clone an existing runner directory **excluding its identity files** — `rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' / /` (the globs also catch `.runner_migrated`/`.credentials_migrated`, which GitHub writes on migrated runners and which equally trigger the already-configured refusal) — then run `config.sh` (copying `.runner`/`.credentials` verbatim makes it refuse with "already configured"), and **start the listener**: `sudo ./svc.sh install ubuntu && sudo ./svc.sh start`. Registration alone leaves the runner offline; a started service adds a scheduling slot, not CPU or memory. +Capacity includes the master standby, main-CI jobs, and three release-rehearsal jobs for each eligible PR or master push while the Linux switch is set. Each trusted PR also adds three Node compatibility jobs at gate concurrency one, including the build-backed Node 22 leg and cold temporary runtime downloads. The release rehearsal workflows cancel superseded runs within each workflow/ref group under the [cancellation policy](../../../../.github/workflows/ci.yml); different refs can still add concurrent build, pack, and install load. Check current CPU, memory, disk, and queue pressure before extending self-hosted operation; extra registrations on this VM add scheduling slots, not machine resources. Do not infer spare capacity from the standby alone. When host resources permit extra registrations, use an org registration token (org Settings → Actions → Runners → New runner). Clone an existing runner directory **excluding its identity files** — `rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' / /` (the globs also catch `.runner_migrated`/`.credentials_migrated`, which GitHub writes on migrated runners and which equally trigger the already-configured refusal) — then run `config.sh` (copying `.runner`/`.credentials` verbatim makes it refuse with "already configured"), and **start the listener**: `sudo ./svc.sh install ubuntu && sudo ./svc.sh start`. Registration alone leaves the runner offline; a started service adds a scheduling slot, not CPU or memory. ### Switch back @@ -59,4 +59,4 @@ The variables are writer-manageable repository state; a pull request event itsel ## Consequences -Recovering from a hosted-pool outage is flipping the affected platform's variable (any writer) plus a re-run, with no merge on the critical path. The cost is a second runner topology per platform to keep working: master pushes schedule the standby lanes, but only completed verdicts establish readiness under the [cancellation policy](2026-09-09-cancel-superseded-ci.md), and the snapshot-concurrency and cache-restore branches in `ci.yml` carry a `selfhosted` leg (Linux only) that must stay in step with the hosted leg. Splitting the switch by platform adds one more variable to manage but bounds the blast radius of each switch to the jobs of a single platform. +Recovering from a hosted-pool outage is flipping the affected platform's variable (any writer) plus a re-run, with no merge on the critical path. The cost is a second runner topology per platform to keep working: master pushes schedule the standby lanes, but only completed verdicts establish readiness under the [cancellation policy](../../../../.github/workflows/ci.yml), and the snapshot-concurrency and cache-restore branches in `ci.yml` carry a `selfhosted` leg (Linux only) that must stay in step with the hosted leg. Splitting the switch by platform adds one more variable to manage but bounds the blast radius of each switch to the jobs of a single platform. diff --git a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md index a229d46f16..3e51f00298 100644 --- a/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md +++ b/.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md @@ -6,17 +6,17 @@ Status: implemented ## 问题 -[CI](../../../../.github/workflows/ci.yml) 中三个必需的 Linux 工作作业(`node 24 / static`、`node 24 / coverage`、`node 24 / snapshots and artifacts`)运行在托管的企业级 32 核池上;聚合它们的必需判定作业(`all checks passed`)运行在标准 `ubuntu-latest` 上;[原生 Windows 作业](2026-08-08-native-windows-pull-request-ci.zh.md)运行在托管的 `dsh-windows-2025-16core` 大型运行器上。当企业池发生故障——作业无限排队或企业标签消失——所有开启的拉取请求都无法合并,而"合并一个修复"这一常规恢复手段本身正被那些无法运行的必需检查死锁。**适用范围:两个独立开关,每个平台一个。**`DSH_CI_FAILOVER_LINUX` 恢复企业级 Linux 池故障(三个必需的 Linux 工作作业加 `all checks passed` 判定作业);`DSH_CI_FAILOVER_WINDOWS` 恢复托管 Windows 池故障(原生 Windows 作业)。Linux 池故障无需重定向 Windows 作业,反之亦然。[Node 兼容性作业](2026-09-06-node-compatibility-selfhosted.zh.md)也通过隔离设置跟随 Linux 开关;判定作业的 `node-24-bench`、`python-sdk` 和 `python-runtime` 依赖仍留在标准托管运行器上;若更大范围的 GitHub 托管容量故障连标准池一并击倒,这些依赖仍会阻塞 `all checks passed`。因此故障需要一个任何具备仓库写权限的响应者都能在不合并任何代码的情况下触发的开关。 +[CI](../../../../.github/workflows/ci.yml) 中三个必需的 Linux 工作作业(`node 24 / static`、`node 24 / coverage`、`node 24 / snapshots and artifacts`)运行在托管的企业级 32 核池上;聚合它们的必需判定作业(`all checks passed`)运行在标准 `ubuntu-latest` 上;[原生 Windows 作业](2026-08-08-native-windows-pull-request-ci.zh.md)运行在托管的 `dsh-windows-2025-16core` 大型运行器上。当企业池发生故障——作业无限排队或企业标签消失——所有开启的拉取请求都无法合并,而"合并一个修复"这一常规恢复手段本身正被那些无法运行的必需检查死锁。**适用范围:两个独立开关,每个平台一个。**`DSH_CI_FAILOVER_LINUX` 恢复企业级 Linux 池故障(三个必需的 Linux 工作作业加 `all checks passed` 判定作业);`DSH_CI_FAILOVER_WINDOWS` 恢复托管 Windows 池故障(原生 Windows 作业)。Linux 池故障无需重定向 Windows 作业,反之亦然。[Node 兼容性作业](../../../../.github/workflows/ci.yml)也通过隔离设置跟随 Linux 开关;判定作业的 `node-24-bench`、`python-sdk` 和 `python-runtime` 依赖仍留在标准托管运行器上;若更大范围的 GitHub 托管容量故障连标准池一并击倒,这些依赖仍会阻塞 `all checks passed`。因此故障需要一个任何具备仓库写权限的响应者都能在不合并任何代码的情况下触发的开关。 ## 决策 三个主要 Linux 作业(`node-24`、`node-24-coverage`、`node-24-consumers`)、三个 `node-compat` 矩阵条目和 `all-checks-passed` 通过 `DSH_CI_FAILOVER_LINUX` 解析;原生 Windows 作业通过 `DSH_CI_FAILOVER_WINDOWS` 解析。一个平台的开关不会重定向另一个平台。仓库写者将变量设为 `selfhosted` 时,适用的可信作业选择 `vm-backup` 或 `dsh-win-ci`;`blacksmith` 取值按 [blacksmith 故障切换支路笔记](2026-09-09-blacksmith-failover-leg.zh.md) 路由参与切换的作业;未设置或任何其它值保留工作流定义的托管回退。Node 兼容性作业要求同仓库且非 fork 的头部以及非 Dependabot 作者,使用隔离运行时设置,并在未设置与非特殊值下保留 `ubuntu-latest` 回退;blacksmith 分支不带上述任何条件。在 `selfhosted` 取值下,Linux 故障切换会限制快照并发,并跳过托管软件包缓存恢复。判定作业跟随工作作业,避免继续在不可用的托管池排队。每个开关都是写者可管理的仓库状态而非一次合并,因此在检查失败时仍然有效。`serial / linux (self-hosted standby)` 与 `serial / windows (self-hosted standby)` 通道在 master 推送上重新验证完整的未分片聚合流程。 -[被取代 CI 的取消策略](2026-09-09-cancel-superseded-ci.zh.md) 管理同一工作流/引用组内的 master 推送和手动运行,包括热备演练。master 快速更新可能让演练因反复被取消而始终无法得出结论。判断就绪状态时,使用最近一次已完成的热备结论,并核对其时间和提交;已取消或仅被调度的运行不构成就绪证据。 +[被取代 CI 的取消策略](../../../../.github/workflows/ci.yml) 管理同一工作流/引用组内的 master 推送和手动运行,包括热备演练。master 快速更新可能让演练因反复被取消而始终无法得出结论。判断就绪状态时,使用最近一次已完成的热备结论,并核对其时间和提交;已取消或仅被调度的运行不构成就绪证据。 ### 发布演练共用 Linux 开关 -`DSH_CI_FAILOVER_LINUX=selfhosted` 还会将符合条件的同仓库 PR 和 master 推送中的无凭据依赖布局作业与 dsh/vendor 两个打包作业路由到 `vm-backup`。[发布演练决策](2026-09-06-release-rehearsal-selfhosted.zh.md) 负责更严格的事件准入规则及保留托管的手动触发。这种耦合是有意的:持续设置变量来节省发布分钟,也会让符合条件的主 CI Linux 作业持续使用自托管。清除变量会让两类负载的后续运行返回各自的托管目标;发布操作始终保留托管。 +`DSH_CI_FAILOVER_LINUX=selfhosted` 还会将符合条件的同仓库 PR 和 master 推送中的无凭据依赖布局作业与 dsh/vendor 两个打包作业路由到 `vm-backup`。[发布演练说明](../../../../.github/workflows/release.yml) 负责更严格的事件准入规则及保留托管的手动触发。这种耦合是有意的:持续设置变量来节省发布分钟,也会让符合条件的主 CI Linux 作业持续使用自托管。清除变量会让两类负载的后续运行返回各自的托管目标;发布操作始终保留托管。 ### 自有池是什么 @@ -40,7 +40,7 @@ Status: implemented ## 切换期间的容量 -Linux 开关启用期间,容量需覆盖 master 热备、主 CI 作业,以及每个符合条件的 PR 或 master 推送的三个发布演练作业。每个可信 PR 还会增加三个门禁并发度为一的 Node 兼容性作业,包括需要构建的 Node 22 条目和冷临时运行时下载。发布演练工作流依据[取消策略](2026-09-09-cancel-superseded-ci.zh.md)取消各工作流/引用组内被取代的运行;不同引用仍可能增加并发构建、打包和安装负载。延长自托管运行前,检查当前 CPU、内存、磁盘和队列压力;同一虚拟机上新增注册只增加调度槽位,不增加机器资源。不能只依据热备负载推断空闲容量。主机资源允许增加注册实例时,使用组织级注册 token(组织 Settings → Actions → Runners → New runner)。复制现有 runner 目录时**必须排除身份文件**——`rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' / /`(通配同时排除 `.runner_migrated`/`.credentials_migrated`——GitHub 会在迁移过的运行器上写入这些文件,它们同样会触发 already-configured 拒绝)——再跑 `config.sh`(原样拷贝 `.runner`/`.credentials` 会使其以 "already configured" 拒绝),然后**启动监听器**:`sudo ./svc.sh install ubuntu && sudo ./svc.sh start`。仅注册不会上线;启动服务增加的是调度槽位,而非 CPU 或内存。 +Linux 开关启用期间,容量需覆盖 master 热备、主 CI 作业,以及每个符合条件的 PR 或 master 推送的三个发布演练作业。每个可信 PR 还会增加三个门禁并发度为一的 Node 兼容性作业,包括需要构建的 Node 22 条目和冷临时运行时下载。发布演练工作流依据[取消策略](../../../../.github/workflows/ci.yml)取消各工作流/引用组内被取代的运行;不同引用仍可能增加并发构建、打包和安装负载。延长自托管运行前,检查当前 CPU、内存、磁盘和队列压力;同一虚拟机上新增注册只增加调度槽位,不增加机器资源。不能只依据热备负载推断空闲容量。主机资源允许增加注册实例时,使用组织级注册 token(组织 Settings → Actions → Runners → New runner)。复制现有 runner 目录时**必须排除身份文件**——`rsync -a --exclude '.runner*' --exclude '.credentials*' --exclude '_diag' --exclude '_work' / /`(通配同时排除 `.runner_migrated`/`.credentials_migrated`——GitHub 会在迁移过的运行器上写入这些文件,它们同样会触发 already-configured 拒绝)——再跑 `config.sh`(原样拷贝 `.runner`/`.credentials` 会使其以 "already configured" 拒绝),然后**启动监听器**:`sudo ./svc.sh install ubuntu && sudo ./svc.sh start`。仅注册不会上线;启动服务增加的是调度槽位,而非 CPU 或内存。 ### 切回 @@ -59,4 +59,4 @@ Linux 开关启用期间,容量需覆盖 master 热备、主 CI 作业,以 ## 后果 -从托管池故障中恢复只需切换受影响平台的变量(任何写者可设)加一次重跑,关键路径上没有合并。代价是每个平台都要维护第二套运行器拓扑:master 推送会调度热备通道,但依据[取消策略](2026-09-09-cancel-superseded-ci.zh.md),只有已完成的结论才能证明就绪状态;而 `ci.yml` 中的快照并发与缓存恢复分支带有一条 `selfhosted` 支路(仅 Linux),必须与托管支路保持同步。按平台拆分开关多了一个需要管理的变量,但把每个开关的影响范围限定在单个平台的作业上。 +从托管池故障中恢复只需切换受影响平台的变量(任何写者可设)加一次重跑,关键路径上没有合并。代价是每个平台都要维护第二套运行器拓扑:master 推送会调度热备通道,但依据[取消策略](../../../../.github/workflows/ci.yml),只有已完成的结论才能证明就绪状态;而 `ci.yml` 中的快照并发与缓存恢复分支带有一条 `selfhosted` 支路(仅 Linux),必须与托管支路保持同步。按平台拆分开关多了一个需要管理的变量,但把每个开关的影响范围限定在单个平台的作业上。 diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml index 91cb4b1f67..67d1d90e9b 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.i18n.yaml @@ -9,8 +9,8 @@ en: e6929494e60041a2 zh: 15bec8e8baabcab2 /agent-note-generated-third-party-notices/decision: - en: 4d9ab23559067da3 - zh: 8de95de194393187 + en: 11f3362b0f45debb + zh: 5953b0848b940b67 /agent-note-generated-third-party-notices/testing: en: 194f87f780991068 zh: 58096d2339576d20 diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md index 71f1fd0a5b..0ebd540939 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.md @@ -20,7 +20,7 @@ One trigger gap is accepted rather than worked around: lefthook inspects only fi The file discloses **direct** dependencies by default; complete npm and Python SDK closures remain in their lock files. Two separately distributed payload sets receive explicit entries: the official Claude executable packages declared by `@anthropic-ai/claude-agent-sdk`, and every distribution in the shared runtime lock's `pythonPackages`. Bundled entries include normalized names, exact versions, and recorded license metadata; missing metadata, duplicate normalized names and conflicting normalized versions fail generation. The shared wheel set is independent of the `python/` manifests, so scanning only those manifests would omit the packaged Office libraries. Bundled distributions also pass the runtime license check, including the permissive `MIT-CMU` and `PSF-2.0` identifiers declared by Pillow and typing-extensions. -**Tiering follows distribution, not manifest section.** Installed runtime libraries are identified by `dependencies` or `optionalDependencies` outside `DEV_ONLY_AREAS` — the root manifest, `packages/test-support/`, `packages/test-support/client-runtime/`, `website/`, `native/`. Browser inputs resolved by the shipping tsdown and Vite configurations also count as runtime, even in `devDependencies`; [browser third-party build inputs](2026-09-08-browser-third-party-build-inputs.md) owns that classification. Test-support dependencies do not ship merely because their manifest says `dependencies`, and the generator explicitly discloses `tsx` because source launches execute through its ESM hook. +**Tiering follows distribution, not manifest section.** Installed runtime libraries are identified by `dependencies` or `optionalDependencies` outside `DEV_ONLY_AREAS` — the root manifest, `packages/test-support/`, `packages/test-support/client-runtime/`, `website/`, `native/`. Browser inputs resolved by the shipping tsdown and Vite configurations also count as runtime, even in `devDependencies`; [browser third-party build inputs](2026-08-26-published-dependency-faces.md) owns that classification. Test-support dependencies do not ship merely because their manifest says `dependencies`, and the generator explicitly discloses `tsx` because source launches execute through its ESM hook. The runtime tier deliberately covers **every mountable plugin**, not just what the CLI, Web UI, and Python runtime load by default. Source execution can mount any plugin package from a user's `cordis.yml`; `@modelcontextprotocol/sdk` and the OpenTelemetry packages therefore reach real users even though no default assembly imports them. Under-disclosure is the costly direction for a legal notice. diff --git a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md index e3adb43e4b..323ad8ea15 100644 --- a/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md +++ b/.agents/notes/implemented/process/2026-07-30-generated-third-party-notices.zh.md @@ -20,7 +20,7 @@ Status: implemented 文件默认只披露**直接**依赖;完整的 npm 与 Python SDK 闭包保留在各自锁文件中。两组独立分发的载荷会明确列出:`@anthropic-ai/claude-agent-sdk` 声明的官方 Claude 可执行包,以及共享运行时锁文件 `pythonPackages` 中的每个分发包。内置条目包含归一化名称、精确版本和已记录的许可证元数据;元数据缺失、归一化后名称重复或版本冲突都会使生成失败。共享 wheel 集合独立于 `python/` 下的 manifest,仅扫描后者会遗漏已打包的 Office 库。内置分发包同样通过运行时许可证检查,其中包括 Pillow 与 typing-extensions 声明的宽松许可证标识 `MIT-CMU` 和 `PSF-2.0`。 -**分层依据是分发内容,而非 manifest 字段名。** 安装的运行时库由 `DEV_ONLY_AREAS` 之外的 `dependencies` 或 `optionalDependencies` 识别;排除区域为根 manifest、`packages/test-support/`、`packages/test-support/client-runtime/`、`website/`、`native/`。发布所用的 tsdown 与 Vite 配置解析到的浏览器输入也属于运行时,即使它们位于 `devDependencies`;[浏览器第三方构建输入](2026-09-08-browser-third-party-build-inputs.zh.md)拥有这项分类。测试支撑依赖不会仅因字段写成 `dependencies` 就被交付,而生成器显式披露 `tsx`,因为源码启动通过其 ESM 钩子执行。 +**分层依据是分发内容,而非 manifest 字段名。** 安装的运行时库由 `DEV_ONLY_AREAS` 之外的 `dependencies` 或 `optionalDependencies` 识别;排除区域为根 manifest、`packages/test-support/`、`packages/test-support/client-runtime/`、`website/`、`native/`。发布所用的 tsdown 与 Vite 配置解析到的浏览器输入也属于运行时,即使它们位于 `devDependencies`;[浏览器第三方构建输入](2026-08-26-published-dependency-faces.zh.md)拥有这项分类。测试支撑依赖不会仅因字段写成 `dependencies` 就被交付,而生成器显式披露 `tsx`,因为源码启动通过其 ESM 钩子执行。 运行时层刻意覆盖**所有可挂载的插件**,而不止 CLI、Web UI 与 Python 运行时默认加载的那些。从源码运行时,用户可以通过 `cordis.yml` 挂载任何插件包;因此,`@modelcontextprotocol/sdk` 与 OpenTelemetry 系列即使没有任何默认装配引入,也会触达真实用户。对法务披露而言,披露不足才是代价更高的那个方向。 diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml deleted file mode 100644 index b427a1cc50..0000000000 --- a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-03-package-anchored-subsystem-pages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md -/agent-note-package-anchored-subsystem-pages-and-thin-group-readmes: - en: 48a39166064d9f0d - zh: c23ba48570aab506 -/agent-note-package-anchored-subsystem-pages-and-thin-group-readmes/problem: - en: 244099e9da9c15ce - zh: 72b5dd59abe0133a -/agent-note-package-anchored-subsystem-pages-and-thin-group-readmes/decision: - en: b8c1668317997b2c - zh: 62d0ef605bfa3d3c -/agent-note-package-anchored-subsystem-pages-and-thin-group-readmes/alternatives-considered: - en: 07b4fc9048074b31 - zh: d14c862196f545fa -/agent-note-package-anchored-subsystem-pages-and-thin-group-readmes/consequences: - en: 9a6d02787410d14d - zh: e63d7023f49d5750 diff --git a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.i18n.yaml deleted file mode 100644 index 402eb36c98..0000000000 --- a/.agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-08-browser-gif-evidence-chain.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-browser-gif-evidence-chain.md -/agent-note-browser-gifs-preserve-one-evidence-chain: - en: acd1abc5215025db - zh: 13d04f81ebb47d18 -/agent-note-browser-gifs-preserve-one-evidence-chain/problem: - en: 427edb924ca3106e - zh: 2115278719f9fd25 -/agent-note-browser-gifs-preserve-one-evidence-chain/decision: - en: 1a098893e7e55ff8 - zh: c2912855cfc4a2bb -/agent-note-browser-gifs-preserve-one-evidence-chain/alternatives-considered: - en: 7c8b16109ffaf01a - zh: d978945c49912639 -/agent-note-browser-gifs-preserve-one-evidence-chain/consequences: - en: 18e112842a2a0ac8 - zh: bb562ffcbd660b83 diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml index 8a421fe006..360ed3d315 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml @@ -9,8 +9,8 @@ en: 3e229ecf646af866 zh: d358507b1e1d381f /agent-note-wine-and-native-windows-ci/decision: - en: f557cb00e1b457c1 - zh: eeaec8412ddb315a + en: 5a74ef2d98f6b4cf + zh: 4a539303a3cd4ed8 /agent-note-wine-and-native-windows-ci/alternatives-considered: en: f6c5e77fe3f05739 zh: 8de640d12d3a38c4 diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md index 64c4366ee8..478c5a8ba3 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md @@ -12,13 +12,13 @@ A coverage audit found that stale branch state had restored temporary exclusions ## Decision -The master-only `windows` job in [ci-master.yml](../../../../.github/workflows/ci-master.yml) runs `windows node 24 / wine` on `ubuntu-latest`. It retains the checksum-verified Windows Node, Wine apt and pnpm caches, a hoisted install confined to a workspace snapshot, and the [shared Wine gate script](../../../../scripts/wine-windows-gates.sh) that runs the workspace build and production site. Node distribution transfers use bounded retries; when nodejs.org stalls on the large archive, a range-capable transport mirror resumes the same bytes, but nodejs.org remains the version and SHA-256 authority and the archive is never promoted before that checksum passes. Wine is outside the PR aggregate under the [master-only platform policy](2026-09-06-master-only-platform-ci.md). The [archived Wine experiment](../../archived/process/2026-07-27-wine-windows-gates-experiment.md) preserves its measured trade-offs, while this note owns the current dual topology. +The master-only `windows` job in [ci-master.yml](../../../../.github/workflows/ci-master.yml) runs `windows node 24 / wine` on `ubuntu-latest`. It retains the checksum-verified Windows Node, Wine apt and pnpm caches, a hoisted install confined to a workspace snapshot, and the [shared Wine gate script](../../../../scripts/wine-windows-gates.sh) that runs the workspace build and production site. Node distribution transfers use bounded retries; when nodejs.org stalls on the large archive, a range-capable transport mirror resumes the same bytes, but nodejs.org remains the version and SHA-256 authority and the archive is never promoted before that checksum passes. Wine is outside the PR aggregate under the [master-only platform policy](../../../../.github/workflows/ci-master.yml). The [archived Wine experiment](../../archived/process/2026-07-27-wine-windows-gates-experiment.md) preserves its measured trade-offs, while this note owns the current dual topology. Every pull request starts three native jobs: `windows-build`, `windows-coverage`, and `windows-native-tests`. Each job enables Developer Mode for workspace symlinks, provisions the repository-pinned pnpm through `pnpm/action-setup`, performs an immutable install without a transferred store archive, and runs its inventory under native PowerShell. The Windows failover variable (`DSH_CI_FAILOVER_WINDOWS`) selects the organization-owned `dsh-windows-2025-16core` runner by default, the in-house pool under `selfhosted`, or Blacksmith runners under `blacksmith` (see the [blacksmith failover leg note](2026-09-09-blacksmith-failover-leg.md)). Blacksmith build and coverage jobs request 16 vCPU; file-serial native tests request 2 vCPU. Per-job deadlines range from 60 to 120 minutes. `windows-build` and `windows-native-tests` are dependencies of `all checks passed`; their workspace-build and targeted native-process results are blocking. After its blocking build and production-site checks succeed, `windows-build` runs `check:ci:windows-observational-ready` in the same workspace. This step uses `continue-on-error`, disables fail-fast, and has a 15-minute timeout; failures produce a workflow warning because Linux owns the blocking static, documentation, package, and built-artifact verdicts. The required verdict waits for the step to finish but does not require its diagnostics to pass. `windows-coverage` remains outside aggregate `needs`, so its 100%-per-file result stays red and visible without delaying the verdict. -`windows-coverage` runs [in-job partitioned coverage](2026-08-18-in-job-partitioned-coverage.md) without a preceding build, matching the Linux coverage lane: four single-worker instrumented shards beside a two-worker exempt-heavy gate; workspace imports resolve to `src` through the tsconfig paths map, and the lib-consuming suites self-skip on unbuilt checkouts. Both coverage gates raise Vitest's per-test, expect.poll, and hook budgets through `DSH_COVERAGE_TEST_TIMEOUT_MS=90000`. The observational step reuses the successful build, retains the distinct MPA documentation build, starts independent static gates together, and uses CI worker defaults for `publint`, with an eight-worker override on shared self-hosted runners. Its built-bin smoke starts only after every other observational gate settles, including failed gates. The ready aggregate requires a successful prior workspace build; it removes only that build and its internal dependency edges from the shared internal inventory. This keeps bounded real-application startup measurements from competing with tool-catalog, NodeNext, package, and documentation processes. The script-only translation-pairing merge suite runs in the exempt-heavy gate because it imports only `scripts/` sources and child processes; V8 instrumentation contributes no threshold coverage there but magnifies Git-process latency. Lefthook concurrency fixtures retain their outcomes with 30-second case budgets and a 10-second process-ready probe, while the installer allows five seconds for a preempted lock owner to publish its record after exclusive creation. Directory-picker composition gives its debounced config write an explicit 15-second poll budget; workspace-context composition fixtures use a test-owned signal without an unrelated one-second deadline. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform. +`windows-coverage` runs [in-job partitioned coverage](../../../../scripts/coverage-partitions.ts) without a preceding build, matching the Linux coverage lane: four single-worker instrumented shards beside a two-worker exempt-heavy gate; workspace imports resolve to `src` through the tsconfig paths map, and the lib-consuming suites self-skip on unbuilt checkouts. Both coverage gates raise Vitest's per-test, expect.poll, and hook budgets through `DSH_COVERAGE_TEST_TIMEOUT_MS=90000`. The observational step reuses the successful build, retains the distinct MPA documentation build, starts independent static gates together, and uses CI worker defaults for `publint`, with an eight-worker override on shared self-hosted runners. Its built-bin smoke starts only after every other observational gate settles, including failed gates. The ready aggregate requires a successful prior workspace build; it removes only that build and its internal dependency edges from the shared internal inventory. This keeps bounded real-application startup measurements from competing with tool-catalog, NodeNext, package, and documentation processes. The script-only translation-pairing merge suite runs in the exempt-heavy gate because it imports only `scripts/` sources and child processes; V8 instrumentation contributes no threshold coverage there but magnifies Git-process latency. Lefthook concurrency fixtures retain their outcomes with 30-second case budgets and a 10-second process-ready probe, while the installer allows five seconds for a preempted lock owner to publish its record after exclusive creation. Directory-picker composition gives its debounced config write an explicit 15-second poll budget; workspace-context composition fixtures use a test-owned signal without an unrelated one-second deadline. The LSP sources and the ACL-sandbox sources remain in the Windows denominator: stub-based failure-path suites carry every in-process ACL-sandbox file to 100%, and only the runner entry stays excluded — it executes exclusively as a spawned child outside the instrumented run, its behavior pinned end-to-end by the runner suite. Narrow annotated V8 ignores cover only unreachable branches (peer-platform arms and lifecycle-unreachable guards), with their behavior tests retained on the owning platform. The 16-core allocation is the measured capacity point for this inventory. Six-worker coverage trials produced complete passes in 6 minutes 27 seconds and 7 minutes 50 seconds, while exact-head trials with four, three, and two concurrent workers inside one instrumented Vitest process exposed unreliable fixtures and worker exits. Separate single-worker child processes retain process isolation. Historical sixteen-shard samples reduced instrumented coverage to 112.66–122.01 seconds. The pull-request coverage job schedules four instrumented children plus two exempt workers without a preceding build, while the self-hosted complete reference runs its unsharded coverage gates serially with one worker. A six-partition pull-request profile creates enough process and type-aware lint contention to violate bounded test deadlines. Sixteen instrumented shards plus two exempt workers would exceed a 16-core allocation before system overhead. A 32-core comparison reduced aggregate gate time by only 1.47 seconds and still triggered the CJS-lexer fatal inside a fork worker, so additional cores did not provide a reliable wall-clock improvement. @@ -30,7 +30,7 @@ Native watchers use `canonicalizeWatchPath()` to realpath the deepest existing a Windows durable JSONL paths keep drive roots in native spelling and apply the extended-length namespace only to descendants and staging paths. The ACP teardown ladder uses real Node children, proves graceful and forced tiers with host-appropriate outcomes, and avoids claiming POSIX signal delivery on Windows. Executable fixtures provide `.cmd` shims and `PATHEXT` where the product accepts a bare command. Repository-cache helpers live inside the selected Git subpath so their declared `file:` dependencies expose command shims identically on Windows. The bundled installer exports pnpm's own workspace-ignore configuration, retains `PNPM_HOME` for pnpm data while removing that directory from lifecycle `PATH`, and prioritizes `.CMD` in `PATHEXT`; nested Git-package installation therefore cannot rejoin the enclosing workspace or let an inherited Windows pnpm executable outrank the transaction-owned wrapper. -Post-boot profile watcher setup proceeds only while the root fiber and Loader are both live. A concurrent setup error is contained only when the same invocation's recorded signal already owns shutdown; unrelated HMR failures remain loud. The [process-shutdown controller](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.md) lets a successful one-shot completion drain Node's remaining handles after root disposal, while teardown failure, deadline, and signal escalation retain forced exit. The vendored Include serializes debounced writes, retries only transient access or busy failures with bounded backoff, and observes every timer rejection. A terminal persistence failure remains on the queue and is rethrown to the teardown owner, while successful teardown drains the latest write. +Post-boot profile watcher setup proceeds only while the root fiber and Loader are both live. A concurrent setup error is contained only when the same invocation's recorded signal already owns shutdown; unrelated HMR failures remain loud. The [process-shutdown controller](../../../../apps/cli/src/process-shutdown.ts) lets a successful one-shot completion drain Node's remaining handles after root disposal, while teardown failure, deadline, and signal escalation retain forced exit. The vendored Include serializes debounced writes, retries only transient access or busy failures with bounded backoff, and observes every timer rejection. A terminal persistence failure remains on the queue and is rethrown to the teardown owner, while successful teardown drains the latest write. Shiki disables lazy TextMate-regex compilation and warms each boot grammar before user content enters the unchanged per-line tokenization budget, so scheduler contention cannot publish a partial highlighted stream. The Codex real-product fixture is pinned to stable 0.153.4 schemas and selects an actually advertised command tool and argument shape, preserving the provider-owned protocol while proving unattended rejection and whole-tree exit on each host. diff --git a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md index 48725cdc58..f3c8dc5fff 100644 --- a/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md +++ b/.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md @@ -12,13 +12,13 @@ Wine 在 Linux 内核与区分大小写的 ext4 之上采用 hoisted 依赖布 ## 决策 -[ci-master.yml](../../../../.github/workflows/ci-master.yml) 中仅 master 触发的 `windows` 作业在 `ubuntu-latest` 上运行 `windows node 24 / wine`。它保留经过校验和验证的 Windows Node、Wine apt 与 pnpm 缓存、仅限工作区快照的 hoisted 安装,以及运行工作区构建与生产网站的[共享 Wine 门禁脚本](../../../../scripts/wine-windows-gates.sh)。Node 分发文件传输采用有界重试;nodejs.org 的大文件传输停滞时,由支持范围请求的传输镜像续传相同字节,但版本和 SHA-256 权威仍属于 nodejs.org,归档通过该校验前绝不会投入使用。根据[仅 master 平台策略](2026-09-06-master-only-platform-ci.zh.md),Wine 不参与 PR 聚合。[已归档的 Wine 实验](../../archived/process/2026-07-27-wine-windows-gates-experiment.md)保留其实测取舍,而本文负责当前双通道拓扑。 +[ci-master.yml](../../../../.github/workflows/ci-master.yml) 中仅 master 触发的 `windows` 作业在 `ubuntu-latest` 上运行 `windows node 24 / wine`。它保留经过校验和验证的 Windows Node、Wine apt 与 pnpm 缓存、仅限工作区快照的 hoisted 安装,以及运行工作区构建与生产网站的[共享 Wine 门禁脚本](../../../../scripts/wine-windows-gates.sh)。Node 分发文件传输采用有界重试;nodejs.org 的大文件传输停滞时,由支持范围请求的传输镜像续传相同字节,但版本和 SHA-256 权威仍属于 nodejs.org,归档通过该校验前绝不会投入使用。根据[仅 master 平台策略](../../../../.github/workflows/ci-master.yml),Wine 不参与 PR 聚合。[已归档的 Wine 实验](../../archived/process/2026-07-27-wine-windows-gates-experiment.md)保留其实测取舍,而本文负责当前双通道拓扑。 每个拉取请求都会启动三个原生作业:`windows-build`、`windows-coverage` 和 `windows-native-tests`。每个作业都会为 workspace 符号链接启用开发者模式,通过 `pnpm/action-setup` 提供仓库固定版本的 pnpm,在不传输 store 归档的情况下执行不可变安装,并在原生 PowerShell 下运行自己的清单。Windows 故障切换变量(`DSH_CI_FAILOVER_WINDOWS`)默认选择组织自有的 `dsh-windows-2025-16core` 运行器,在 `selfhosted` 下选择公司内部运行器池,在 `blacksmith` 下选择 Blacksmith 运行器(见 [blacksmith 故障切换支路笔记](2026-09-09-blacksmith-failover-leg.zh.md))。Blacksmith 构建和覆盖率作业请求 16 vCPU;逐文件串行的原生测试请求 2 vCPU。各作业的截止时间为 60 至 120 分钟。 `windows-build` 与 `windows-native-tests` 是 `all checks passed` 的依赖项;其工作区构建和定向原生进程结果具有阻断性。阻断性构建和生产网站检查成功后,`windows-build` 在同一工作区运行 `check:ci:windows-observational-ready`。该步骤使用 `continue-on-error`、关闭 fail-fast,并设置 15 分钟超时;失败时会产生工作流警告,因为静态检查、文档、包与构建产物的阻断性判定由 Linux 负责。必需判定会等待该步骤结束,但不要求其诊断通过。`windows-coverage` 仍在聚合流程的 `needs` 之外,因此逐文件 100% 覆盖率结果会保持红灯并可见,却不会延迟判定。 -`windows-coverage` 与 Linux 覆盖率通道一致,不先构建工作区就运行[job 内分区覆盖率](2026-08-18-in-job-partitioned-coverage.zh.md):4 个单 worker 插桩分片与一个双 worker 的豁免重型门禁并行运行;工作区导入通过 tsconfig paths 映射解析到 `src`,消费构建产物的套件在未构建的检出上会自跳。两项覆盖率门禁都通过 `DSH_COVERAGE_TEST_TIMEOUT_MS=90000` 提高 Vitest 的单测试、expect.poll 与 hook 预算。观测步骤复用成功的构建,保留独立的 MPA 文档构建,一起启动相互独立的静态门禁,并让 `publint` 使用 CI worker 默认值,在共享自托管运行器上覆盖为 8 个 worker。其 built-bin 冒烟测试只在其他所有观测性门禁结算后启动,包括失败的门禁。ready 聚合要求此前已成功构建工作区;它仅从内部共用清单中移除该构建及其内部依赖边。这可避免有界的真实应用启动测量与 tool-catalog、NodeNext、包及文档进程争抢资源。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。 +`windows-coverage` 与 Linux 覆盖率通道一致,不先构建工作区就运行[job 内分区覆盖率](../../../../scripts/coverage-partitions.ts):4 个单 worker 插桩分片与一个双 worker 的豁免重型门禁并行运行;工作区导入通过 tsconfig paths 映射解析到 `src`,消费构建产物的套件在未构建的检出上会自跳。两项覆盖率门禁都通过 `DSH_COVERAGE_TEST_TIMEOUT_MS=90000` 提高 Vitest 的单测试、expect.poll 与 hook 预算。观测步骤复用成功的构建,保留独立的 MPA 文档构建,一起启动相互独立的静态门禁,并让 `publint` 使用 CI worker 默认值,在共享自托管运行器上覆盖为 8 个 worker。其 built-bin 冒烟测试只在其他所有观测性门禁结算后启动,包括失败的门禁。ready 聚合要求此前已成功构建工作区;它仅从内部共用清单中移除该构建及其内部依赖边。这可避免有界的真实应用启动测量与 tool-catalog、NodeNext、包及文档进程争抢资源。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。 16 核配置是这项清单经实测选定的容量规格。使用 6 个 coverage worker 的试验分别以 6 分 27 秒和 7 分 50 秒跑出完整通过结果,而在单个插桩 Vitest 进程内使用 4 个、3 个和 2 个并发 worker 的分支头精确试验暴露出不稳定的 fixture 与 worker 退出。相互独立的单 worker 子进程保留进程隔离。历史上的 16 分片样本把插桩覆盖率缩短到 112.66–122.01 秒。拉取请求覆盖率作业会在没有前置构建的情况下调度 4 个插桩子进程和 2 个豁免 worker,而自托管完整参考流程会用 1 个 worker 串行运行未分片的覆盖率门禁。拉取请求若采用 6 分片配置,就会产生足以违反有界测试截止时间的进程与类型感知 lint 争用。16 个插桩分片加 2 个豁免 worker 会在计入系统开销前就超过 16 核分配。32 核对比仅将聚合门禁时间缩短 1.47 秒,且仍在 fork worker 内触发 CJS lexer 致命故障,因此增加核心数没有带来可靠的墙钟时间改善。 @@ -30,7 +30,7 @@ Wine 在 Linux 内核与区分大小写的 ext4 之上采用 hoisted 依赖布 Windows 的持久 JSONL 路径会保留驱动器根目录的原生写法,并仅对后代路径与暂存路径应用扩展长度命名空间。ACP(Agent Client Protocol)拆卸阶梯使用真实 Node 子进程,以符合宿主语义的结果证明优雅终止与强制终止两个层级,并避免声称 Windows 会交付 POSIX 信号。产品接受裸命令时,可执行 fixture 会提供 `.cmd` 包装脚本与 `PATHEXT`。repository-cache 辅助包位于所选 Git 子路径内,因此它们声明的 `file:` 依赖会在 Windows 上以相同方式暴露命令包装脚本。随附的安装器会导出 pnpm 自有的 workspace-ignore 配置,保留 `PNPM_HOME` 作为 pnpm 数据配置,同时从生命周期 `PATH` 中移除该目录,并在 `PATHEXT` 中优先选择 `.CMD`;因此,嵌套 Git 包安装既不会重新加入外层 workspace,也不会让继承的 Windows pnpm 可执行文件抢在事务持有的 wrapper 之前。 -启动后,只有根 fiber 与 Loader 均处于活跃状态时,系统才会继续设置 profile watcher。只有当同一次调用所记录的信号已取得关闭流程所有权时,系统才会隔离并发设置错误;无关 HMR 故障仍会响亮失败。[进程关闭控制器](../bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md)会在根级 dispose 成功后让单次任务的正常完成流程排空 Node 剩余句柄,同时让拆卸失败、截止时间到期和信号升级继续强制退出。vendored Include 会串行化防抖写入,只对瞬时访问或忙碌故障执行有界退避重试,并确保每个由计时器触发的拒绝都得到观察。持久化最终失败后,该故障会保留在队列中,并重新抛给拆卸责任方;成功拆卸则会排空最新写入。 +启动后,只有根 fiber 与 Loader 均处于活跃状态时,系统才会继续设置 profile watcher。只有当同一次调用所记录的信号已取得关闭流程所有权时,系统才会隔离并发设置错误;无关 HMR 故障仍会响亮失败。[进程关闭控制器](../../../../apps/cli/src/process-shutdown.ts)会在根级 dispose 成功后让单次任务的正常完成流程排空 Node 剩余句柄,同时让拆卸失败、截止时间到期和信号升级继续强制退出。vendored Include 会串行化防抖写入,只对瞬时访问或忙碌故障执行有界退避重试,并确保每个由计时器触发的拒绝都得到观察。持久化最终失败后,该故障会保留在队列中,并重新抛给拆卸责任方;成功拆卸则会排空最新写入。 Shiki 会禁用 TextMate 正则的延迟编译,并在用户内容进入保持不变的逐行 tokenization(词元化)预算前预热每种启动语法,从而避免调度器争用发布不完整的高亮流。Codex 真实产品 fixture 固定使用稳定版 0.153.4 schema,并选择实际提供的命令工具与对应参数形态;这样既保留由提供方负责的协议,也能在每种宿主上证明无人值守拒绝和整棵进程树退出。 diff --git a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml deleted file mode 100644 index e8b85c04de..0000000000 --- a/.agents/notes/implemented/process/2026-08-09-committed-artifact-citations.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-09-committed-artifact-citations.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-committed-artifact-citations.md -/agent-note-cite-committed-artifacts-never-design-session-ordinals: - en: c97b89961652ab9f - zh: a2ecdc61c29652a6 -/agent-note-cite-committed-artifacts-never-design-session-ordinals/problem: - en: 79dc8b3d2c600d91 - zh: e7b16c9e276e565f -/agent-note-cite-committed-artifacts-never-design-session-ordinals/decision: - en: 83f88200cfa66c98 - zh: 3db6822a92701d64 -/agent-note-cite-committed-artifacts-never-design-session-ordinals/alternatives-considered: - en: cdf3ce2ccd530449 - zh: f24bb73190b4b22b -/agent-note-cite-committed-artifacts-never-design-session-ordinals/verification: - en: fb9ce345704f7da4 - zh: 3148c6bbf67ad87f -/agent-note-cite-committed-artifacts-never-design-session-ordinals/consequences: - en: 53fe33cd40aa7133 - zh: 4eaf89d5dea43c62 diff --git a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml b/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml deleted file mode 100644 index eb5e443f1b..0000000000 --- a/.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-09-concrete-prose-names-actors-and-recorded-facts.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md -/agent-note-concrete-prose-names-actors-and-recorded-facts: - en: fbfbd92c8f73d34c - zh: fc48118b80f5dbb9 -/agent-note-concrete-prose-names-actors-and-recorded-facts/problem: - en: f0228c15ff233209 - zh: b6183fdc8dd705a0 -/agent-note-concrete-prose-names-actors-and-recorded-facts/decision: - en: 833658027dc8e2d7 - zh: a360c4aa7b4dde7f -/agent-note-concrete-prose-names-actors-and-recorded-facts/alternatives-considered: - en: 08a988b00edf03b2 - zh: 8614c5edbc46ca36 -/agent-note-concrete-prose-names-actors-and-recorded-facts/consequences: - en: 18e6d0b597186220 - zh: 744ed1b795929bba diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml index 6d96599459..5f1198581c 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.i18n.yaml @@ -9,8 +9,8 @@ en: 3e00a9cb6c0e53ec zh: 1bd27ceea8a424ce /agent-note-event-directed-pr-review-status-commands/decision: - en: 3b661de4e6fbf401 - zh: 2fb7f7adb4f846e4 + en: f5d89f6df53c858e + zh: 795a430226b17390 /agent-note-event-directed-pr-review-status-commands/verification: en: 46301d11d2f1c8df zh: daa44a617912c315 diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md index 37b8086a08..ddb4e0867f 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.md @@ -12,11 +12,11 @@ A monotonic projection also cannot return an automation-owned Issue from `In rev ## Decision -The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions do not allocate a lifecycle runner, while dismissed reviews are not subscribed. [Selective policy evaluation](2026-09-07-selective-issue-policy-evaluation.md) owns lifecycle scheduling. +The Issue lifecycle workflow treats review webhooks as commands. `pull_request.review_requested`, including a repeated request, targets `In review`. `pull_request_review.submitted` targets `In progress` only when `review.state` is `changes_requested`; the submitted event remains necessary because a reviewer can request changes without an earlier review-request event. Approved and commented submissions do not allocate a lifecycle runner, while dismissed reviews are not subscribed. [Selective policy evaluation](../../../../.github/issue-management/README.md) owns lifecycle scheduling. Ordinary subscribed pull-request events remain forward-only implementation signals: they can move `Inbox`, `Backlog`, or `Ready` to `In progress`, but they cannot move `In review` backward. Review-request commands can move any earlier active status to `In review`. Changes-requested commands can move earlier active statuses forward to `In progress` and can move `In review` back only when the latest status event for the target Project was written by the configured lifecycle actor. A human or unknown latest actor preserves the current status. -The status projection resolves only exact same-repository `Fixes`, `Closes`, or `Resolves` references. It does not alter terminal statuses, add an Issue with no Project status, depend on PR metadata validity, query `reviewDecision`, reconstruct review rounds, look up pull requests from Issues, or run a scheduled reconciler. [Project-local Issue planning fields](2026-09-02-project-local-issue-planning-fields.md) own the separate date initialization for every same-repository Issue reference. +The status projection resolves only exact same-repository `Fixes`, `Closes`, or `Resolves` references. It does not alter terminal statuses, add an Issue with no Project status, depend on PR metadata validity, query `reviewDecision`, reconstruct review rounds, look up pull requests from Issues, or run a scheduled reconciler. [Project-local Issue planning fields](../../../../.github/issue-management/README.md) own the separate date initialization for every same-repository Issue reference. [Issue lifecycle](../../../../.github/workflows/issue-lifecycle.yml) remains unsubscribed from `pull_request.ready_for_review`; neither event command depends on that action. [Issue policy](../../../../.github/workflows/issue-policy.yml) retains `ready_for_review` because it owns required-check enforcement when a human pull request enters review. diff --git a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md index cc0dad8554..ff42c089d3 100644 --- a/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-event-directed-pr-review-status.zh.md @@ -12,11 +12,11 @@ Issue 所在 Project 中的状态记录了解决工作的下一步由谁负责 ## 决策 -Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review`。`pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state` 为 `changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,工作流不分配生命周期 runner;dismissed 评审则不在订阅范围内。[选择性策略求值](2026-09-07-selective-issue-policy-evaluation.zh.md)拥有生命周期调度规则。 +Issue 生命周期工作流把评审 webhook 视为命令。`pull_request.review_requested`(包括重复请求)将目标状态指定为 `In review`。`pull_request_review.submitted` 将目标状态指定为 `In progress`,但仅在 `review.state` 为 `changes_requested` 时生效;submitted 事件仍不可省略,因为评审人即使没有先触发 review-request 事件,也可以直接提出修改要求。对于 approved 和 commented 提交,工作流不分配生命周期 runner;dismissed 评审则不在订阅范围内。[选择性策略求值](../../../../.github/issue-management/README.zh.md)拥有生命周期调度规则。 工作流订阅的普通 PR 事件仍是只向前推进的实现信号:它们可以将 `Inbox`、`Backlog` 或 `Ready` 推进至 `In progress`,但不能让 `In review` 倒退。请求评审命令可将任意较早的活跃状态推进至 `In review`。请求修改命令可将较早的活跃状态推进至 `In progress`;它也可以让 `In review` 状态回退,但仅在目标 Project 的最新状态事件由配置的生命周期执行主体写入时进行。若最新状态事件的执行主体是人工用户或未知主体,则保留当前状态。 -状态投影仅解析同一仓库内严格匹配的 `Fixes`、`Closes` 或 `Resolves` 引用。它不会更改终态、将没有 Project 状态的 Issue 添加到 Project、依赖 PR 元数据是否有效、查询 `reviewDecision`、重建评审轮次、从 Issue 反向查找 PR,或运行定时协调器。独立的日期初始化由[Project 局部 Issue 规划字段](2026-09-02-project-local-issue-planning-fields.zh.md)负责,并处理每个同仓库 Issue 引用。 +状态投影仅解析同一仓库内严格匹配的 `Fixes`、`Closes` 或 `Resolves` 引用。它不会更改终态、将没有 Project 状态的 Issue 添加到 Project、依赖 PR 元数据是否有效、查询 `reviewDecision`、重建评审轮次、从 Issue 反向查找 PR,或运行定时协调器。独立的日期初始化由[Project 局部 Issue 规划字段](../../../../.github/issue-management/README.zh.md)负责,并处理每个同仓库 Issue 引用。 [Issue 生命周期](../../../../.github/workflows/issue-lifecycle.yml)仍不订阅 `pull_request.ready_for_review`;两条事件命令均不依赖该动作。[Issue 策略](../../../../.github/workflows/issue-policy.yml)保留 `ready_for_review`,因为人工提交的 PR 进入评审时,该工作流负责执行必需检查门禁。 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index 40e0e18b71..531b156371 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml @@ -12,20 +12,20 @@ en: a15c7c13f167e5ac zh: 63bcc2ef60c2f344 /agent-note-private-npm-publication-as-three-independent-sequences/decision/three-independent-sequences: - en: 6cd23b2407935500 - zh: 6d50f5f4fb867342 + en: c1d7c618fa93ab10 + zh: 887ad2a37ed18035 /agent-note-private-npm-publication-as-three-independent-sequences/decision/versions-land-in-the-repository-from-a-local-command-ci-only-checks-and-uploads: en: 71d4cda384013bfb zh: 909d853c18496925 -/agent-note-private-npm-publication-as-three-independent-sequences/decision/vendor-publish-what-changed-and-let-tags-be-the-ledger: - en: 7f578bb18f7f8693 - zh: 49f25210f7c1a61f +/agent-note-private-npm-publication-as-three-independent-sequences/decision/vendor-advance-the-complete-family-with-per-package-tags: + en: 321f48c7e4afac24 + zh: 974a7fa1a75c810d /agent-note-private-npm-publication-as-three-independent-sequences/decision/publication-runs-only-on-github-and-the-registry-decides-what-goes-out: - en: 4f399390f58d7a34 - zh: 9df67248acfc61c8 + en: 8c8cd90e6eabe07d + zh: 7035b71a6eeeb379 /agent-note-private-npm-publication-as-three-independent-sequences/decision/workspace-internal-references-use-the-workspace-protocol: - en: 6cff087e7e2d0ac7 - zh: 5d45a3abdd960d9c + en: 82a7925d449fc283 + zh: 653358a1ffbad80a /agent-note-private-npm-publication-as-three-independent-sequences/decision/published-dependency-faces-use-an-explicit-policy: en: 30a64e0e919a27c0 zh: 19729d7ebee35439 @@ -33,20 +33,17 @@ en: d83f6cf38529c5b5 zh: 4df5659b2154d3b7 /agent-note-private-npm-publication-as-three-independent-sequences/decision/release-family-objects: - en: e8bb80d936e2cbbd - zh: bc4ae297e98a5446 + en: 5bc6a83d124b682d + zh: 17383ec5c3c7e0e5 /agent-note-private-npm-publication-as-three-independent-sequences/decision/workflow-shape-pack-on-pr-push-publish-from-a-manual-dispatch-workflow: en: ea0acc6409da4d5c zh: ac0bd2fe41f4fe67 /agent-note-private-npm-publication-as-three-independent-sequences/decision/repository-changes-this-carried: en: c505237cad2c4128 zh: 55364ba462536680 -/agent-note-private-npm-publication-as-three-independent-sequences/decision/relationship-to-the-earlier-proposal: - en: 298382a27013dcfb - zh: ab89cf392232f9bb /agent-note-private-npm-publication-as-three-independent-sequences/alternatives-considered: - en: 11877d09e5a84f6e - zh: ea372c779254be93 + en: ed22fa8a47b5c4b6 + zh: 7b367971a51a67ff /agent-note-private-npm-publication-as-three-independent-sequences/consequences: - en: f961ebb927e30b5c - zh: 019f1628fb1f2c56 + en: 46f82110175ea3f7 + zh: 1cdd1c99bfb88440 diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md index 6a4f9d1ec2..8552232363 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md @@ -22,7 +22,7 @@ Two hard blockers sat in the way. All 217 workspace manifests set `private: true | Sequence | Members | Version baseline | Tag | Workflow | |---|---|---|---|---| -| dsh | Publish set: public members of `packages/*/*` + `apps/*`, with [private experimental exceptions](2026-09-12-experimental-publication-denylist.md); private packages join only the shared version bump | one version for the publish set, private dsh packages, and workspace root, `0.0.x` | `dsh-v` | `release.yml` (pack) / `release-publish.yml` (publish) | +| dsh | Publish set: public members of `packages/*/*` + `apps/*`, with [private experimental exceptions](../../../../packages/experimental/README.md); private packages join only the shared version bump | one version for the publish set, private dsh packages, and workspace root, `0.0.x` | `dsh-v` | `release.yml` (pack) / `release-publish.yml` (publish) | | vendored framework | the nine `vendor/*` packages | each package on its own version line | `vendor--v` (one per package) | `release-vendor.yml` (pack) / `release-vendor-publish.yml` (publish) | | native | `native/system/packages/*` | its own `0.0.x` | `node-addon-system-v` | `node-addon-system-release.yml` | @@ -36,9 +36,9 @@ Each sequence has one bump-and-commit command: it derives the target version, wr For equal release numbers, SemVer compares alphanumeric prerelease identifiers lexically: `alpha` is lower than `canary`, `canary` is lower than `rc`, and every prerelease is lower than the stable version. npm dist-tags are mutable aliases and do not participate in version precedence. -### vendor: publish what changed, and let tags be the ledger +### vendor: advance the complete family with per-package tags -The vendored packages are decoupled from upstream by their scope but keep their own version lines. The published version is the higher of the manifest version and the last published version, with the patch incremented — which also drops an upstream prerelease segment. The first published versions: +The vendored packages are decoupled from upstream by their scope but keep their own version lines. The next version uses the higher of the manifest version and the latest tagged version, incrementing the patch after a stable version. The first published versions: | Package | Upstream version | First published version | |---|---|---| @@ -52,13 +52,13 @@ The vendored packages are decoupled from upstream by their scope but keep their | `@deepseek-ai/cordis-plugin-group` | 1.0.0 | 1.0.1 | | `@deepseek-ai/cordis-plugin-logger-console` | 1.0.0 | 1.0.1 | -Taking the last published version as the baseline is what survives a re-sync: upstream restoring `4.0.0-rc.8` after this repository published `4.0.1` would otherwise compute `4.0.1` again and collide. `--prerelease rc.1` publishes a rehearsal instead, which takes `--tag next` and leaves the release numbers free: a prerelease has lower precedence than the release it precedes, so `4.0.1` still follows `4.0.1-rc.1`. That ordering is computed here rather than read from `git tag --sort=v:refname`, which places a prerelease above its release. +Taking the latest tagged version as the baseline prevents reuse after a re-sync: upstream restoring `4.0.0-rc.8` after this repository tagged `4.0.1` would otherwise compute `4.0.1` again. `release:vendor --prerelease rc.1` or `--prerelease alpha.1` selects a prerelease, whose default npm dist-tag is `next`. Later prereleases and the stable release reuse its release numbers: `4.0.1` follows `4.0.1-rc.1`. The script compares SemVer precedence rather than using `git tag --sort=v:refname`, which places a prerelease above its release. -Only changed packages publish, and the change judgement adds no state file: **each package has its own tag, and that tag records the commit it last published from**. For each package, bump reads the newest `vendor--v*` tag and diffs the package directory against it. A path counts when the manifest's `files` selects it, when npm publishes it regardless (`package.json`, `README*`, `LICENSE*`), or — for a package whose `files` selects `lib/` — when it is a build input (`src/**`, `tsconfig*.json`, a build config). That last rule exists because a built payload is not tracked by git: without it, a real source change reads as "nothing changed" and the next publication fails on a version whose bytes moved. +Every vendor release advances all nine packages, each with its own version and `vendor--v` tag. Repacking from another repository state can change a member's dependency ranges or built payload even when its directory has no source changes. Advancing the complete family avoids reusing an existing version for those different bytes. -A tag is a commit pointer, not proof of publication. Bump asks the registry whether the version its newest tag names exists and fails for a human to resolve when it does not, because a tag pushed for a publication that then failed would otherwise read as "already published" and skip the package indefinitely. Querying a private package needs credentials, so an unauthenticated machine reports the gap instead of failing. +A tag reserves a package version and identifies its commit; it does not establish successful publication. Bump uses the latest tag even if its publication failed. The publisher checks registry versions and integrity, and the release manager verifies completion for the complete family. -`vendor/cordis` publishes `src` as well. Its export map declares `"./src/*"`, so a tarball without those files points consumers at absent paths, and `files` selecting only build output left the change judgement with no tracked path to match. +`vendor/cordis` publishes `src` as well. Its export map declares `"./src/*"`, so a tarball without those files points consumers at absent paths. ### Publication runs only on GitHub, and the registry decides what goes out @@ -76,11 +76,15 @@ All three sequences decide this way, including the native one: it publishes thro Two registry behaviours shape how a publish is attempted. Writes are spaced by at least two seconds and retried with a backoff, because publishing several packages back to back outruns the registry's own processing and earns `E409 Failed to save packument`. And every retry re-reads the registry first: a reported failure can answer a write that landed anyway, so a version that now exists with this tarball's integrity counts as published rather than as a version to place again. +For dsh and vendor, `release:publish --dist-tag ` overrides the family's default publication channel. The vendor publish workflow accepts the optional `dist-tag` input; omitting it preserves the family default. An override must be a valid npm dist-tag. Before publishing any tarball, the publisher checks every packed member: the selected tag must already name the intended version, or both that tag and the version must be absent. A tag bound elsewhere, or an already published version without the selected tag, rejects the entire attempt before any upload. Existing integrity checks and retries still apply. Skipped versions are never retagged. + +The preflight does not reserve npm tags atomically. The release manager coordinates external publishers that could change a binding after the check. A dedicated dist-tag also does not exclude stable versions from existing dependency ranges: isolation requires prerelease versions and an audit of the actual packed dependency ranges. + ### Workspace-internal references use the `workspace:` protocol -Every reference to a workspace member uses the `workspace:` protocol. The [release-range policy](2026-09-22-workspace-release-ranges.md) requires exact `workspace:*` DSH references and `workspace:~` vendor/native references in every dependency section and consumer, including the native entry's optional platform packages. Local workspace linking is unchanged. +Every reference to a workspace member uses the `workspace:` protocol. Every dependency section and consumer uses exact `workspace:*` DSH references and `workspace:~` vendor/native references, including the native entry's optional platform packages. Vendor and native patch releases must preserve consumer-facing APIs and binary interfaces. Local workspace linking is unchanged. -`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The invariant-companion rule requires `workspace:*` for `@deepseek-ai/dsh-invariants`; the dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions. +`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions. ### Published dependency faces use an explicit policy @@ -109,7 +113,7 @@ The entity in this domain is a **release family**: a set of packages sharing one | `publishOrder` | topological order over the sections npm installs plus peer declarations, ties broken by package name; a cycle among installed dependencies is reported rather than resolved arbitrarily, and a peer edge no order can honour is dropped and named | | `pack` | packs a whole family into one directory and records the upload order | | `verify` | the family's version baseline, the publish order it prints in full, and — when publishing — that the run comes from that family's tag and its members are publishable | -| `verify-packed-install` | installs the tarballs of one or more pack directories into a throwaway consumer and drives the installed executable | +| `verify-packed-install` | installs the tarballs of one or more pack directories into a throwaway consumer with an [owned npm cache](../bug-fix/2026-09-06-packed-install-private-npm-cache.md) and drives the installed executable | | `publish` | the three registry states above | | `process` / `tarball` | the one home for spawning commands and for reading a packed tarball, including the entry guard that keeps every script importable | @@ -138,10 +142,6 @@ The installed-consumer probe captures npm's HTTP diagnostics and includes them w | `vendor/README.md` | records `src` joining `cordis`'s `files` as a local modification | | the three native packages | `publishConfig.access: public`, and their workflow passes no `--access` | -### Relationship to the earlier proposal - -This Agent Note replaces the version scheme and the release-set boundary in [artifact-first npm baseline publication](../../rejected/process/2026-08-04-artifact-first-npm-baseline-publication.md): its `--` prerelease versions and `dev-` dist-tag are not adopted, and vendor is not excluded from the release set. What both agree on stands: pack and publish are separate, publish consumes only verified tarballs, and the payload and installed-artifact probes are release gates. - ## Alternatives considered **A `--` version.** Planned for continuous dev publication. It conflicts with keeping the published version in the repository: the version embeds a commit SHA, and writing the version back produces a new commit, so the SHA can only name the parent commit that was published and the link needs a convention to explain it. With numbered versions, a prerelease such as `0.0.1-rc.1` already covers "verify first, then release". @@ -150,9 +150,9 @@ This Agent Note replaces the version scheme and the release-set boundary in [art **Event-level tags (`vendor-r1`, `vendor-r2`).** Prepared for one release event carrying several package versions. Once the registry decides what publishes, the workflow no longer infers the set from the tag, so per-package tags suffice — and each one names its own package's real version. -**Putting the nine vendored packages on one `4.0.x` line.** It removes change detection, but cosmokit would jump from `1.8.1` to `4.0.1` and lose its upstream lineage; the upstream ranges inside the nine (`^1.8.1` and friends) would stop matching immediately, forcing a rewrite of the vendored manifests. +**Putting the nine vendored packages on one `4.0.x` line.** Cosmokit would jump from `1.8.1` to `4.0.1` and lose its upstream lineage; the upstream ranges inside the nine (`^1.8.1` and friends) would stop matching immediately, forcing a rewrite of the vendored manifests. -**Incrementing every vendored package on every vendor release, with no change detection.** The least machinery, at the cost of new version numbers for packages whose content is byte-identical to the previous release. Tags reduce change detection to reading one tag and running one diff, which is not worth trading for inflated version numbers. +**Publishing only changed vendor directories.** A directory diff does not establish that repacking from another repository state produces identical bytes. Advancing all nine packages avoids integrity collisions for unchanged directories at the cost of additional versions. **Deciding "already published" from the version alone, without comparing content.** The reference flow queries no registry: publish uploads each tarball and npm rejects a duplicate version. Skipping on the version alone misses code that changed without a bump, which is the only failure that quietly leaves stale bytes on the registry. The cost is a registry query and a dependency on reproducible builds. @@ -170,14 +170,14 @@ This Agent Note replaces the version scheme and the release-set boundary in [art ## Consequences -The release scripts are importable modules behind a guarded entry point, and their judgements carry unit tests: tag naming, publish order and cycle reporting, version-baseline arithmetic, the payload change judgement, and each family's payload policy. Two defects the first draft carried — a publish command that ran the pack command on import, and a change judgement blind to `vendor/cordis` source edits — are exactly what a test at that seam catches. +The release scripts are importable modules behind a guarded entry point, and their judgements carry unit tests: tag naming, publish order and cycle reporting, version-baseline arithmetic, and each family's payload policy. The entry guard prevents an import from executing a release command. A pull request runs the full pack for both sequences without credentials and installs the packed dsh tarballs into a throwaway consumer, where plain Node drives `dsh --version`. That probe is deliberately one command: it proves `files` selected a complete payload and that the published ranges resolve, and says nothing about interactive behavior. What this costs: -- **Tags can drift from the registry.** A tag pushed for a publication that then failed is caught by bump's registry check, but only where credentials exist; an unauthenticated machine reports the gap and continues. -- **The change judgement depends on visible tags.** A shallow clone, or a checkout without tags, degrades the vendored judgement to "publish everything for the first time". `fetch-depth: 0` is a precondition, not an optimization. +- **Tags can drift from the registry.** Tags reserve versions even when publication fails; the release manager verifies registry completion separately. +- **The version baseline depends on visible tags.** A shallow clone or a checkout without tags can reuse a reserved vendor version. `fetch-depth: 0` is a precondition, not an optimization. - **The protocol rewrite touched 1504 dependency declarations.** It does not change local resolution — pnpm already resolves from the workspace — but it changes the ranges that go out. - **Private packages need credentials to install.** Every consumer — CI, sandbox e2e, outside users — needs scope credentials, including for the Landlock packages, which have never been published and so cut off no existing anonymous path. - **`repository` names a different organization than the one running the workflows.** Token-based publication is unaffected; npm's OIDC attestation requires the two to agree, so adopting it means either repointing `repository` or publishing from the organization it names. diff --git a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md index d33d48e445..9d130478e6 100644 --- a/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md +++ b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.zh.md @@ -22,7 +22,7 @@ Status: implemented | 序列 | 成员 | 版本基线 | tag | workflow | |---|---|---|---|---| -| dsh | 发布集:`packages/*/*` + `apps/*` 中的公开成员,保留[私有实验包例外](2026-09-12-experimental-publication-denylist.zh.md);私有包仅加入共享版本 bump | 发布集、私有 dsh 包与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml`(pack)/ `release-publish.yml`(发布) | +| dsh | 发布集:`packages/*/*` + `apps/*` 中的公开成员,保留[私有实验包例外](../../../../packages/experimental/README.zh.md);私有包仅加入共享版本 bump | 发布集、私有 dsh 包与 workspace 根共用一个 `0.0.x` | `dsh-v<版本>` | `release.yml`(pack)/ `release-publish.yml`(发布) | | vendored framework | `vendor/*` 九个包 | 每包各自一条版本线 | `vendor-<包名>-v<版本>`(每包一个) | `release-vendor.yml`(pack)/ `release-vendor-publish.yml`(发布) | | native | `native/system/packages/*` | 自己的 `0.0.x` | `node-addon-system-v<版本>` | `node-addon-system-release.yml` | @@ -36,9 +36,9 @@ Status: implemented 基础版本号相同时,SemVer 按字典序比较字母数字型预发布标识:`alpha` 小于 `canary`,`canary` 小于 `rc`,所有预发布版本都小于稳定版本。npm dist-tag 是可变别名,不参与版本优先级比较。 -### vendor:谁改了谁发版,tag 就是账本 +### vendor:整族递增,每包保留自己的 tag -vendor 九包加了 scope 之后与上游脱钩,但保留各自的版本线。发布版本取「manifest 版本」与「上次发布版本」中较高的那个,再递增 patch——这一步同时去掉上游的预发布段。首发版本: +vendor 九包加了 scope 之后与上游脱钩,但保留各自的版本线。下一版本取 manifest 版本与最新 tag 版本中较高的那个,稳定版本之后递增 patch。首发版本: | 包 | 上游版本 | 首发版本 | |---|---|---| @@ -52,13 +52,13 @@ vendor 九包加了 scope 之后与上游脱钩,但保留各自的版本线。 | `@deepseek-ai/cordis-plugin-group` | 1.0.0 | 1.0.1 | | `@deepseek-ai/cordis-plugin-logger-console` | 1.0.0 | 1.0.1 | -以「上次发布版本」为基线才扛得住重同步:本仓发过 `4.0.1` 之后上游把版本恢复成 `4.0.0-rc.8`,只看 manifest 会再算出 `4.0.1` 并撞上已发版本。加 `--prerelease rc.1` 则发一次排练版:它进 `--tag next`,而且不占用那组数字——预发布的优先级低于它所先行的正式版,所以 `4.0.1` 仍然接在 `4.0.1-rc.1` 之后。这个次序由脚本自己算,不读 `git tag --sort=v:refname`——git 会把预发布排在正式版之前。 +以最新 tag 的版本为基线可避免重同步后复用版本:本仓标记 `4.0.1` 之后,上游若将版本恢复成 `4.0.0-rc.8`,只看 manifest 会再次算出 `4.0.1`。`release:vendor --prerelease rc.1` 或 `--prerelease alpha.1` 选择预发布版本,默认 npm dist-tag 为 `next`。后续预发布和稳定版本复用其基础版本号:`4.0.1` 接在 `4.0.1-rc.1` 之后。脚本按 SemVer 优先级比较,不使用会将预发布排在稳定版本之前的 `git tag --sort=v:refname`。 -只发改动过的包,而变更判据不引入新的状态文件:**每包一个 tag,tag 就是「上次发布到哪个 commit」的记录**。bump 对每个包取最新的 `vendor-<包名>-v*` tag,拿包目录与它做 diff。一条路径算命中的条件是:manifest 的 `files` 选中它,或 npm 无论如何都会发布它(`package.json`、`README*`、`LICENSE*`),或者——当该包的 `files` 选中 `lib/` 时——它是构建输入(`src/**`、`tsconfig*.json`、构建配置)。最后那条规则的存在理由是构建产物不在 git 里:没有它,真实的源码改动会读成「没变化」,而下一次发布会在一个字节已变的版本上失败。 +每次 vendor 发布都递增全部九个包,每包保留自己的版本和 `vendor--v` tag。即使包目录没有源码变更,从不同仓库状态重新打包也可能改变其依赖范围或构建产物。整族递增可避免为这些不同的字节复用已有版本。 -tag 只是 commit 指针,不是发布成功的证明。bump 会向 registry 核对「最新 tag 指向的版本是否真的存在」,不存在就明确失败交人处理——否则一个为失败发布而推的 tag 会被读成「已发布」,从此永远跳过该包。查询私有包需要凭据,因此未鉴权的机器只报告这道核对被跳过,不失败。 +tag 预留包版本并标识其 commit,不代表发布成功。即使对应发布失败,bump 仍使用最新 tag。publisher 核对 registry 中的版本和 integrity,发布负责人核实整族发布完成。 -`vendor/cordis` 现在也发布 `src`。它的 exports 声明了 `"./src/*"`,tarball 里没有这些文件就等于把消费方指向不存在的路径;而 `files` 只选构建产物,也让变更判据没有任何受 git 跟踪的路径可匹配。 +`vendor/cordis` 也发布 `src`。它的 exports 声明了 `"./src/*"`,tarball 中缺少这些文件就会将消费方指向不存在的路径。 ### 发布只在 GitHub 执行,由 registry 状态决定发什么 @@ -76,11 +76,15 @@ tag 只是 commit 指针,不是发布成功的证明。bump 会向 registry registry 的两个行为决定了「怎么尝试一次发布」。写入之间至少间隔两秒并带退避重试,因为连续背靠背发多个包会超出 registry 自身的处理速度,换来 `E409 Failed to save packument`。而每次重试都先重查 registry:报出来的失败可能对应一次其实已经落地的写入,所以「该版本现在存在且 integrity 与本 tarball 相同」算作已发布,而不是又一个待放置的版本。 +对 dsh 和 vendor,`release:publish --dist-tag ` 覆盖该族默认发布通道。vendor 发布 workflow 接受可选的 `dist-tag` 输入;省略时保留该族默认值。覆盖值必须是合法的 npm dist-tag。发布任何 tarball 前,publisher 检查每个打包成员:所选 tag 必须已指向预期版本,或者该 tag 和版本都不存在。tag 指向其他版本,或版本已经发布但缺少所选 tag,都会在上传前拒绝整次发布。原有 integrity 检查与重试继续生效。跳过的版本不会重新绑定 tag。 + +预检不会原子性地预留 npm tag。发布负责人协调可能在检查后更改绑定的外部发布者。专用 dist-tag 也不会让稳定版本被已有依赖范围排除:隔离需要预发布版本,并核查实际打包产物中的依赖范围。 + ### workspace 内部引用走 `workspace:` 协议 -所有指向 workspace 成员的引用都用 `workspace:` 协议。[发布范围策略](2026-09-22-workspace-release-ranges.zh.md)要求每个消费者的所有依赖区段对 DSH 使用精确的 `workspace:*` 引用,对 vendor/native 使用 `workspace:~` 引用,包括原生入口的可选平台包。本地 workspace 链接方式不变。 +所有指向 workspace 成员的引用都用 `workspace:` 协议。每个消费者的所有依赖区段对 DSH 使用精确的 `workspace:*` 引用,对 vendor/native 使用 `workspace:~` 引用,包括原生入口的可选平台包。vendor 与 native 的补丁版本必须保持面向消费者的 API 和二进制接口兼容。本地 workspace 链接方式不变。 -`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。Invariant companion 规则要求 `@deepseek-ai/dsh-invariants` 使用 `workspace:*`;依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。 +`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。 ### 发布依赖门面使用显式策略 @@ -109,7 +113,7 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间 | `publishOrder` | 按 npm 会安装的依赖段加 peer 声明做拓扑序,同层按包名排;安装依赖成环是报错而不是随意定序,任何排不进去的 peer 边被丢弃并点名 | | `pack` | 把整族打进一个目录并记录上传顺序 | | `verify` | 族的版本基线、完整打印出来的发布顺序;发布时还要求本次运行来自该族的 tag、且成员可发布 | -| `verify-packed-install` | 把一个或多个 pack 目录的 tarball 装进一次性 consumer,并驱动已安装的可执行入口 | +| `verify-packed-install` | 把一个或多个 pack 目录的 tarball 装进带[独立 npm 缓存](../bug-fix/2026-09-06-packed-install-private-npm-cache.zh.md)的一次性 consumer,并驱动已安装的可执行入口 | | `publish` | 上面那三态 | | `process` / `tarball` | 启动命令、读取打包 tarball 的唯一正家,其中的入口守卫让每个脚本都可被 import | @@ -138,10 +142,6 @@ dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 ven | `vendor/README.md` | 记录「`src` 加入 `cordis` 的 `files`」这条本地修改 | | native 三包 | `publishConfig.access: public`,且其 workflow 不传 `--access` | -### 与先前提案的关系 - -本 Note 取代 [以产物为先的 NPM 基线发布](../../rejected/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md) 中的版本方案与发布集边界:那篇的 `-<时间戳>-<短 SHA>` 预发布版本与 `dev-` dist-tag 不再采用,vendor 也不排除在发布集之外。两篇一致的部分保留:pack 与 publish 分离、publish 只消费已验证的 tarball、payload 与安装后探针作为发布门。 - ## 曾考虑的替代方案 **`-<时间戳>-<短 SHA>` 版本号。** 曾计划用于持续 dev 发布。它与「把发布版本留在仓库里」冲突:版本内嵌 commit SHA,而把版本写回会产生新的 commit,于是 SHA 只能指向被发布的父 commit,这条链要靠约定解释。改用数字版本后,`0.0.1-rc.1` 这类预发布号已经覆盖「先验证再正式发」。 @@ -150,9 +150,9 @@ dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 ven **事件级 tag(`vendor-r1`、`vendor-r2`)。** 为「一次发布事件携带多个包版本」准备。既然由 registry 决定发什么,workflow 就不再从 tag 推断集合,per-package tag 够用,而且每个 tag 携带的是它自己那个包的真实版本。 -**把九个 vendored 包统一到一条 `4.0.x` 版本线。** 省掉变更检测,但 cosmokit 会从 `1.8.1` 跳到 `4.0.1`、丢失上游血缘;九包内部的上游范围(`^1.8.1` 之类)会立刻失配,必须改写 vendored manifest。 +**把九个 vendored 包统一到一条 `4.0.x` 版本线。** Cosmokit 会从 `1.8.1` 跳到 `4.0.1`、丢失上游血缘;九包内部的上游范围(`^1.8.1` 之类)会立刻失配,必须改写 vendored manifest。 -**每次 vendor 发布把九包全部 patch+1,不做变更检测。** 机制最少,代价是内容与上一版逐字节相同的包也拿到新版本号。tag 把变更检测的成本压到「读一个 tag、跑一次 diff」,不值得为省这点让版本号虚涨。 +**只发布目录有变更的 vendor 包。** 目录 diff 不能证明从不同仓库状态重新打包会产出相同字节。递增全部九个包可避免未改目录的 integrity 冲突,代价是增加版本号。 **只按版本号判断「是否已发布」,不比对内容。** 参照流程根本不查 registry:publish 逐个上传,重复版本由 npm 拒绝。只按版本号跳过会漏掉「改了代码没 bump」,而这是唯一会安静地把旧字节留在 registry 上的错误。代价是引入一次 registry 查询和对构建可复现性的依赖。 @@ -170,14 +170,14 @@ dsh 的验证会一并安装 vendored 族的 pack 产物。harness 的包把 ven ## 后果 -发布脚本是带入口守卫的可 import 模块,其判断都有单测覆盖:tag 命名、发布顺序与环报告、版本基线运算、payload 变更判据,以及各族的 payload 策略。第一版带过的两个缺陷——publish 命令在 import 时执行了 pack 命令、变更判据对 `vendor/cordis` 的源码改动失明——正是这类测试在对应接缝上能抓住的。 +发布脚本是带入口守卫的可 import 模块,其判断都有单测覆盖:tag 命名、发布顺序与环报告、版本基线运算,以及各族的 payload 策略。入口守卫阻止 import 执行发布命令。 一个 pull request 会为两条序列跑完整的 pack(无凭据),并把打包好的 dsh tarball 装进一次性 consumer,用普通 Node 驱动 `dsh --version`。这个探针刻意只有一条命令:它证明 `files` 选出了完整 payload、发布出去的范围可解析,不涉及任何交互行为。 代价: -- **tag 可能与 registry 漂移。** 为失败发布而推的 tag 由 bump 的 registry 核对拦下,但只在有凭据的地方;未鉴权的机器只报告这道核对被跳过。 -- **变更判据依赖 tag 可见。** shallow clone 或未拉 tag 会把 vendored 族的判据退化成「全部首发」。`fetch-depth: 0` 是前提,不是优化。 +- **tag 可能与 registry 漂移。** 即使发布失败,tag 仍预留版本;发布负责人另行核实 registry 中的完成状态。 +- **版本基线依赖 tag 可见。** shallow clone 或未拉取 tag 的 checkout 可能复用已预留的 vendor 版本。`fetch-depth: 0` 是前提,不是优化。 - **协议改写触及 1504 处依赖声明。** 它不改变本机解析(pnpm 本来就从 workspace 解析),但改变了发布出去的范围写法。 - **私有包需要凭据才能安装。** 任何消费方——CI、沙箱 e2e、外部使用者——都要持有 scope 凭据,Landlock 三包也在其中;它们从未发布过,所以没有切断既有的匿名安装路径。 - **`repository` 指向的组织与运行 workflow 的组织不同。** 用 token 发布不受影响;npm 的 OIDC attestation 要求二者一致,届时要么把 `repository` 改指过去,要么从它指向的组织发布。 diff --git a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml b/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml deleted file mode 100644 index 47454c2346..0000000000 --- a/.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-18-in-job-partitioned-coverage.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md -/agent-note-in-job-partitioned-coverage: - en: eb25ff049a1ecc31 - zh: d7430b14ccea5707 -/agent-note-in-job-partitioned-coverage/problem: - en: ab6dcac4ca2bbfb3 - zh: c754cc8077671b25 -/agent-note-in-job-partitioned-coverage/decision: - en: ae23ea86587c23e8 - zh: b9868c5c76238d9a -/agent-note-in-job-partitioned-coverage/failure-and-output-semantics: - en: c73ddbc65107e34e - zh: dbc7649637f8d1c0 -/agent-note-in-job-partitioned-coverage/verification: - en: 81dcaebd4b96229c - zh: 76b3332b183f6c44 -/agent-note-in-job-partitioned-coverage/alternatives-considered: - en: 82f314bd434b0059 - zh: 7b64e90b74f428e5 -/agent-note-in-job-partitioned-coverage/consequences: - en: 146e997017ee6e47 - zh: cd5e5d2d9b0b47b9 diff --git a/.agents/notes/implemented/process/2026-08-19-remove-knip.i18n.yaml b/.agents/notes/implemented/process/2026-08-19-remove-knip.i18n.yaml deleted file mode 100644 index 1d76a326bd..0000000000 --- a/.agents/notes/implemented/process/2026-08-19-remove-knip.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-19-remove-knip.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-19-remove-knip.md -/agent-note-remove-knip-from-repository-gates: - en: adba00071ebc35c8 - zh: 44f778106e0b9965 -/agent-note-remove-knip-from-repository-gates/problem: - en: 5b2ece5e6f3775e0 - zh: 4fbc60619afdb7e2 -/agent-note-remove-knip-from-repository-gates/decision: - en: c5d7ae84ed863e29 - zh: 409dca40c2a63940 -/agent-note-remove-knip-from-repository-gates/alternatives-considered: - en: 95363dbc293712f3 - zh: 9cbcb9937081f5f0 -/agent-note-remove-knip-from-repository-gates/consequences: - en: 7466ca9e99d0bb27 - zh: 2ac417bc23865e14 diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml index 064bf1cbf2..f627f1c4d6 100644 --- a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.i18n.yaml @@ -6,8 +6,8 @@ en: 98843ceb0ee72c06 zh: e4963bfe0e1b1314 /agent-note-ban-an-ambiguous-origin-label/problem: - en: e3c5b3a689f7a3cc - zh: 676620dfb5303bed + en: 1b3ff74fd7669a39 + zh: f38776a677da81f8 /agent-note-ban-an-ambiguous-origin-label/decision: en: bb8507460f159ad1 zh: 357b5e6065cf389c diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md index b4c161a8a9..f0b547fbc6 100644 --- a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md @@ -8,7 +8,7 @@ English | [中文](2026-08-26-ban-ambiguous-origin-label.zh.md) The case-insensitive ten-letter ASCII token formed by `prove` followed by `nance` had accumulated unrelated meanings across the repository. It named source-event references, provider and model metadata, context producers, installed artifact identity, configuration origins, browser-recording evidence, and release attestations. A reader could not determine the recorded fact from the label alone. -The existing [concrete prose decision](2026-08-09-concrete-prose-names-actors-and-recorded-facts.md) required sentence-level classification and normally preserved identifiers. That policy improved individual sentences but allowed the same ambiguous label to remain in APIs, durable compatibility fixtures, filenames, generated catalogs, and new prose. +The existing [concrete prose reference](../../../skills/dsh-prose-standard/SKILL.md) required sentence-level classification and normally preserved identifiers. That policy improved individual sentences but allowed the same ambiguous label to remain in APIs, durable compatibility fixtures, filenames, generated catalogs, and new prose. ## Decision diff --git a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md index 2d592f4442..1f1c754af6 100644 --- a/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md +++ b/.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.zh.md @@ -8,7 +8,7 @@ Status: implemented 由 `prove` 与 `nance` 拼成的十个 ASCII 字母术语不区分大小写时,在仓库中已经承载了互不相关的含义。它曾表示来源事件引用、提供方与模型元数据、上下文生产方、已安装产物身份、配置来源、浏览器录制证据和发布证明。读者无法仅凭该名称判断实际记录的事实。 -现有的[具体表述决策](2026-08-09-concrete-prose-names-actors-and-recorded-facts.zh.md)要求逐句分类,且通常保留标识符。该规则改善了各个句子,却仍允许同一个有歧义名称留在 API、持久化兼容 fixture、文件名、生成目录和新行文中。 +现有的[具体表述说明](../../../skills/dsh-prose-standard/SKILL.md)要求逐句分类,且通常保留标识符。该规则改善了各个句子,却仍允许同一个有歧义名称留在 API、持久化兼容 fixture、文件名、生成目录和新行文中。 ## 决策 diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml index 19ca92d47b..e3b23f9d2c 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.i18n.yaml @@ -15,8 +15,8 @@ en: 5ea13b09ae774437 zh: ef9dbccada39107a /agent-note-published-dependency-faces-and-bounded-peer-relays/decision/dependency-sections: - en: b1c3e9a87b8bb1ea - zh: 5cfd8b4db39805b7 + en: d34ebac6c1c7642b + zh: 2cf3a60df14b072a /agent-note-published-dependency-faces-and-bounded-peer-relays/decision/maintainer-workflow: en: 5983475fa551ca90 zh: a5519f4817f65e8c diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md index f188ccc0fc..ac0550750f 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md @@ -28,7 +28,7 @@ A workspace package reached by a runtime value import from the Host entry closur An export whose constructor identity or module state must be shared appears in `peerRequiredHostExports`; importing one such export keeps the whole package edge in matching `peerDependencies` and `devDependencies`. Each export-table key is an exact module specifier and each value is a reviewed export set. The verifier follows runtime local imports from the Host entry, records named and default imports and re-exports, and rejects exports covered by neither the package list nor an export table; namespace, dynamic, and side-effect imports remain unbounded unless the complete exact entry is package-classified. -Workspace imports used by the Client bundle, type-only imports, module augmentations, `dsh.client.inject`, and existing metadata-only peers belong only in `devDependencies`. Host runtime imports, including additional Node entries, follow the Host classification. [Browser third-party build inputs](2026-09-08-browser-third-party-build-inputs.md) governs ordinary third-party declarations; it partially supersedes their preservation in this decision. [Release ranges](2026-09-22-workspace-release-ranges.md) require `workspace:*` for DSH and `workspace:~` for vendor/native references. +Workspace imports used by the Client bundle, type-only imports, module augmentations, `dsh.client.inject`, and existing metadata-only peers belong only in `devDependencies`. Host runtime imports, including additional Node entries, follow the Host classification. Browser-only third-party build inputs belong in `devDependencies`; a Host runtime value import takes precedence as `dependencies`. Bundled implementations remain runtime license disclosures regardless of their npm section. Static browser libraries remain Web-shell build inputs, without an independent installed-library support promise. [Release-family rules](2026-08-10-npm-release-sequences.md) require `workspace:*` for DSH and `workspace:~` for vendor/native references. Some development relationships exist only in `dsh.client.inject` or TypeScript project references. The policy's `configurationOnlyDevDependencies` table names only those reviewed edges and keeps them in `devDependencies`. diff --git a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md index d59bb724ce..5ba6333ae4 100644 --- a/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md +++ b/.agents/notes/implemented/process/2026-08-26-published-dependency-faces.zh.md @@ -28,7 +28,7 @@ Host 入口闭包中的运行期 value import 所到达的 workspace 包,只 constructor 身份或模块状态必须共享的导出列入 `peerRequiredHostExports`;一旦使用这类导出,整条包依赖边就保留在范围一致的 `peerDependencies` 与 `devDependencies` 中。每个导出表的 key 都是精确 module specifier,每个 value 都是经审查的导出集合。验证器从 Host 入口沿运行期本地 import 扫描,记录具名与默认 import 和 re-export,并拒绝既没有包级分类、也没有导出级分类的导出;除非完整的精确入口已按包分类,否则 namespace、dynamic 和 side-effect import 仍无法限定范围。 -Client bundle 使用的 workspace import、纯类型 import、模块扩充、`dsh.client.inject` 和仅有元数据的现存 peer 只属于 `devDependencies`。Host 运行时 import(包括额外 Node 入口)遵循 Host 分类。[浏览器第三方构建输入](2026-09-08-browser-third-party-build-inputs.zh.md)规定普通第三方声明,部分取代本决策对它们原区段的保留。[发布范围](2026-09-22-workspace-release-ranges.zh.md)要求 DSH 引用使用 `workspace:*`,vendor/native 引用使用 `workspace:~`。 +Client bundle 使用的 workspace import、纯类型 import、模块扩充、`dsh.client.inject` 和仅有元数据的现存 peer 只属于 `devDependencies`。Host 运行时 import(包括额外 Node 入口)遵循 Host 分类。仅供浏览器使用的第三方构建输入属于 `devDependencies`;Host 运行时 value import 优先归为 `dependencies`。已打包的实现无论位于哪个 npm 区段,都属于运行时许可证披露内容。静态浏览器库仍是 Web shell 的构建输入,不承诺作为独立安装的库提供支持。[发布系列规则](2026-08-10-npm-release-sequences.zh.md)要求 DSH 引用使用 `workspace:*`,vendor/native 引用使用 `workspace:~`。 部分开发期关系只存在于 `dsh.client.inject` 或 TypeScript project reference 中。策略的 `configurationOnlyDevDependencies` 表只列出这些已评审的依赖边,并将它们保留在 `devDependencies` 中。 diff --git a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml b/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml deleted file mode 100644 index 9413c6b4fd..0000000000 --- a/.agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-28-ci-node-compile-cache-data-disk.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp: - en: 52bb1466b8f7f8fe - zh: fa7c282d3d207e53 -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/problem: - en: 3efe58882ca81bf7 - zh: 14f802e97e26f03d -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/decision: - en: cb66e6c4ad73b50b - zh: 43540cec116e342a -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/verification: - en: e23de17356f97224 - zh: b28bcafb4d1a72ad -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/alternatives-considered: - en: 6119e2aff3b6be5a - zh: e01a6c669a9ec6aa -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/alternatives-considered/why-not-disable-the-compile-cache-entirely: - en: cee8de5b45031309 - zh: 6ce10a304e63bdbb -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/alternatives-considered/why-not-add-node-compile-cache-to-the-dsh-residue-sweep: - en: 7e06d9c4df7a9f56 - zh: f3151fe2671fd91e -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/alternatives-considered/why-not-job-level-env-or-gate-step-env-only: - en: 4ba6c9a14bafbc80 - zh: 410149f4a3e930e6 -/agent-note-redirect-the-node-compile-cache-to-the-data-volume-runner-temp/consequences: - en: 083e2665e1550111 - zh: 70560c09ca2b3750 diff --git a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.i18n.yaml b/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.i18n.yaml deleted file mode 100644 index c092807a5a..0000000000 --- a/.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-02-project-local-issue-planning-fields.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md -/agent-note-project-local-issue-planning-fields: - en: 9791d03a5df4c38c - zh: 19cbbf884294402b -/agent-note-project-local-issue-planning-fields/problem: - en: d01d052a9f366f35 - zh: b18600b91aad9a7c -/agent-note-project-local-issue-planning-fields/decision: - en: 21db463121897497 - zh: fd0551038412fd17 -/agent-note-project-local-issue-planning-fields/verification: - en: 7d1ef3eb9aac465d - zh: 68280a4511bce360 -/agent-note-project-local-issue-planning-fields/alternatives-considered: - en: b51a8fbc92bb7b5f - zh: 9316e86ac82c0a1b -/agent-note-project-local-issue-planning-fields/consequences: - en: d3614b91a0a6523a - zh: 773a31194e293014 diff --git a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml b/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml deleted file mode 100644 index 68c4e68e70..0000000000 --- a/.agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-03-semantic-issue-templates-and-policy.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-03-semantic-issue-templates-and-policy.md -/agent-note-semantic-issue-templates-and-presentation-neutral-policy: - en: e1e183193c798bf4 - zh: db76ac954cd3038f -/agent-note-semantic-issue-templates-and-presentation-neutral-policy/problem: - en: 5da7b92d65316469 - zh: 30bc615947b3a8aa -/agent-note-semantic-issue-templates-and-presentation-neutral-policy/decision: - en: a351c272ef520bb9 - zh: 98db942d18da7565 -/agent-note-semantic-issue-templates-and-presentation-neutral-policy/verification: - en: 90912d4c58b9e35d - zh: 811eaab5bdd54ab4 -/agent-note-semantic-issue-templates-and-presentation-neutral-policy/alternatives-considered: - en: 520ba80ce03e05b4 - zh: 50df33c59c4c837d -/agent-note-semantic-issue-templates-and-presentation-neutral-policy/consequences: - en: c762b95945320c61 - zh: 935f6e7290c06811 diff --git a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.i18n.yaml deleted file mode 100644 index a9cf40b2dc..0000000000 --- a/.agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-evidence-driven-performance-skill.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-evidence-driven-performance-skill.md -/agent-note-evidence-driven-performance-optimization-workflow: - en: 0a63ab9dc4847f40 - zh: f8ba9a608396c47c -/agent-note-evidence-driven-performance-optimization-workflow/problem: - en: 2e0cb63ef966a541 - zh: 1853e223e530708e -/agent-note-evidence-driven-performance-optimization-workflow/decision: - en: bd0e363221108421 - zh: fc83dc1020d53f4e -/agent-note-evidence-driven-performance-optimization-workflow/historical-evidence: - en: 2d913642c87e5a01 - zh: 45fce898640c87e8 -/agent-note-evidence-driven-performance-optimization-workflow/alternatives-considered: - en: 70d016cd8e541933 - zh: 2ebeb9cbeba8129e -/agent-note-evidence-driven-performance-optimization-workflow/consequences: - en: 961c634c37340937 - zh: 9a528567223f7bfa diff --git a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml deleted file mode 100644 index 0397c20321..0000000000 --- a/.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-master-only-platform-ci.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md -/agent-note-master-only-platform-ci: - en: fed101c5775cbbcd - zh: b407efdb4c46cfe2 -/agent-note-master-only-platform-ci/problem: - en: 85cf39297a859208 - zh: c959bf7f94ebc50b -/agent-note-master-only-platform-ci/decision: - en: 98b8ecd3b4cfe5e5 - zh: 2c622950ea7afd42 -/agent-note-master-only-platform-ci/alternatives-considered: - en: 71ce7f8bb8a31323 - zh: 45f97b72eb9c6971 -/agent-note-master-only-platform-ci/consequences: - en: 523bbd6a59c120bf - zh: a9d834b5c32a911f diff --git a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.i18n.yaml deleted file mode 100644 index 7cc692226f..0000000000 --- a/.agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-node-compatibility-selfhosted.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-node-compatibility-selfhosted.md -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux: - en: 21c12c8bd0c44122 - zh: b07f0ffc18092f0d -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux/problem: - en: 6bcedb8e3f0dce21 - zh: 83e88ed593259292 -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux/decision: - en: 14f1348f993b9102 - zh: a94e1b802e6a8c39 -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux/alternatives-considered: - en: 93217ca46c2cefb8 - zh: 77a167c69f59111c -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux/consequences: - en: 116d255b8d1b846c - zh: 931754a68e4f75c6 -/agent-note-isolated-node-compatibility-jobs-on-self-hosted-linux/verification: - en: d0b1f1431fae737b - zh: 6cf4f4d654a9525d diff --git a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml deleted file mode 100644 index 6bd758533f..0000000000 --- a/.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-preview-hosted-runner-sizing.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.md -/agent-note-measured-github-hosted-pr-preview-sizing: - en: 987e2592c644030d - zh: 55f306766247e4a4 -/agent-note-measured-github-hosted-pr-preview-sizing/problem: - en: bc382c3ef9c28709 - zh: 27e50735d80696a2 -/agent-note-measured-github-hosted-pr-preview-sizing/decision: - en: 9988e6268f8fc5b8 - zh: e92f3922b49b193d -/agent-note-measured-github-hosted-pr-preview-sizing/decision/measurements: - en: 768cd2ecf9d33c87 - zh: 693ea069a5275736 -/agent-note-measured-github-hosted-pr-preview-sizing/alternatives-considered: - en: 04f845ddb2a86e31 - zh: 993c6f82f5119f66 -/agent-note-measured-github-hosted-pr-preview-sizing/consequences: - en: ed9e8446f4579f5d - zh: c899cdd070e6befa diff --git a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml b/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml deleted file mode 100644 index 9e35b7e987..0000000000 --- a/.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-release-rehearsal-selfhosted.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.md -/agent-note-trusted-release-rehearsals-on-persistent-linux-runners: - en: 1bccf6bdafd4cc76 - zh: 28ce63be303697f1 -/agent-note-trusted-release-rehearsals-on-persistent-linux-runners/problem: - en: c458bf4244320194 - zh: fbd5f64cf8875725 -/agent-note-trusted-release-rehearsals-on-persistent-linux-runners/decision: - en: d51d35350d91f19b - zh: 65297576aff64e8a -/agent-note-trusted-release-rehearsals-on-persistent-linux-runners/alternatives-considered: - en: cd04297045b93d7b - zh: 9eacc1d9c6c10726 -/agent-note-trusted-release-rehearsals-on-persistent-linux-runners/consequences: - en: 90c50dda81822e7c - zh: 9c3ff4df4d2d9533 diff --git a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml b/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml deleted file mode 100644 index b8002f1c9e..0000000000 --- a/.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-selective-issue-policy-evaluation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md -/agent-note-selective-issue-policy-evaluation: - en: b0f986e68a7b7d74 - zh: cffd2efae38626ef -/agent-note-selective-issue-policy-evaluation/problem: - en: 5af4901a5e9576fb - zh: eb089750b27129b1 -/agent-note-selective-issue-policy-evaluation/decision: - en: 99e6c76f04a4d49b - zh: 2631ff72b4db3bdf -/agent-note-selective-issue-policy-evaluation/alternatives-considered: - en: 89631c2a92ab079a - zh: 37b94f76ec6dc36a -/agent-note-selective-issue-policy-evaluation/consequences: - en: 6013a044827444e5 - zh: 69f767a1d534761a -/agent-note-selective-issue-policy-evaluation/verification: - en: b7fb4ebd009d7fd3 - zh: 6d682a4b7f41a4e0 diff --git a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.i18n.yaml b/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.i18n.yaml deleted file mode 100644 index 70e32ca967..0000000000 --- a/.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-browser-third-party-build-inputs.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.md -/agent-note-browser-third-party-libraries-as-build-inputs: - en: 907b99b05fcbce0e - zh: 57980cde5b585338 -/agent-note-browser-third-party-libraries-as-build-inputs/problem: - en: 55458dc0cd27341e - zh: ab23b350c17862be -/agent-note-browser-third-party-libraries-as-build-inputs/decision: - en: 2e9ff103342733eb - zh: d8be06fe18aab03a -/agent-note-browser-third-party-libraries-as-build-inputs/alternatives-considered: - en: 0247f31a86099d53 - zh: 445a6d35c03fc468 -/agent-note-browser-third-party-libraries-as-build-inputs/consequences: - en: ee331a2282a6884e - zh: de81b33db3fd6486 diff --git a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.i18n.yaml b/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.i18n.yaml deleted file mode 100644 index e57f7c6ecc..0000000000 --- a/.agents/notes/implemented/process/2026-09-08-package-index-link-discovery.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-package-index-link-discovery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-08-package-index-link-discovery.md -/agent-note-include-package-indexes-in-markdown-link-discovery: - en: edf2fd5b97c0075a - zh: da6ca05db38ee998 -/agent-note-include-package-indexes-in-markdown-link-discovery/problem: - en: 74a87cccbf602fc4 - zh: 88d1116da4cc35fb -/agent-note-include-package-indexes-in-markdown-link-discovery/decision: - en: 350354f891182cbf - zh: 4607ef9c38ff7a68 -/agent-note-include-package-indexes-in-markdown-link-discovery/alternatives-considered: - en: e12999e567cd2cbd - zh: a502f11b11e86815 -/agent-note-include-package-indexes-in-markdown-link-discovery/consequences: - en: 0bd932bd7feb363b - zh: acce05659887e5e5 diff --git a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.i18n.yaml b/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.i18n.yaml deleted file mode 100644 index f8222bf89a..0000000000 --- a/.agents/notes/implemented/process/2026-09-08-playwright-video-gif.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-playwright-video-gif.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-08-playwright-video-gif.md -/agent-note-playwright-video-captures-continuous-browser-demos: - en: fcca6f069dc51b80 - zh: e9d171b6c84f92a7 -/agent-note-playwright-video-captures-continuous-browser-demos/problem: - en: 967fed211899587e - zh: 0062f7ca29211105 -/agent-note-playwright-video-captures-continuous-browser-demos/decision: - en: 5f9e88a564fd6b8a - zh: b78164f92167dada -/agent-note-playwright-video-captures-continuous-browser-demos/alternatives-considered: - en: 221e89bafcb152d4 - zh: b33816c680dc450f -/agent-note-playwright-video-captures-continuous-browser-demos/consequences: - en: e5c9eb6df05801ba - zh: 786cd7775bfceb9e diff --git a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml b/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml deleted file mode 100644 index 3cf9104f6e..0000000000 --- a/.agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-blocked-weighted-approvals-remain-pending.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-09-blocked-weighted-approvals-remain-pending.md -/agent-note-blocked-weighted-approvals-remain-pending: - en: 6a5da25eb874aece - zh: 654aa967a9222501 -/agent-note-blocked-weighted-approvals-remain-pending/problem: - en: 0afdda30d02b666b - zh: 6624aa9c8c58ea0a -/agent-note-blocked-weighted-approvals-remain-pending/decision: - en: cf7ac63f422b4cfb - zh: 250385e42d546d30 -/agent-note-blocked-weighted-approvals-remain-pending/verification: - en: af674f57088d303a - zh: b9c42f04beec9be5 -/agent-note-blocked-weighted-approvals-remain-pending/alternatives-considered: - en: 13d5f67070bd3396 - zh: c75db3746d31272e -/agent-note-blocked-weighted-approvals-remain-pending/consequences: - en: 284f19608b0d449f - zh: 1d9bd95239ba6ffa diff --git a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.i18n.yaml b/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.i18n.yaml deleted file mode 100644 index 5dac131679..0000000000 --- a/.agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-cancel-superseded-ci.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-09-cancel-superseded-ci.md -/agent-note-cancel-superseded-ci-validation: - en: 78c382bc45c48256 - zh: a6a93a2ef554c61c -/agent-note-cancel-superseded-ci-validation/problem: - en: b436e3c8a7ffe727 - zh: 80b9f77b5cb051de -/agent-note-cancel-superseded-ci-validation/decision: - en: a61d1bc387ed8436 - zh: 3162cdd8096a8a01 -/agent-note-cancel-superseded-ci-validation/alternatives-considered: - en: c7d012d6e43f35ec - zh: 6f3eaf1342319aeb -/agent-note-cancel-superseded-ci-validation/consequences: - en: c226f645a79d0060 - zh: 023071f83b9a126d -/agent-note-cancel-superseded-ci-validation/verification: - en: 015f312772637350 - zh: b703cdab8bd4f0ae diff --git a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.i18n.yaml b/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.i18n.yaml deleted file mode 100644 index 521e943a63..0000000000 --- a/.agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-approval-review-workflow-identity.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-10-approval-review-workflow-identity.md -/agent-note-identify-approval-review-workflows-by-file-path: - en: 8a332b9d080f3d62 - zh: 29f1de05d7867a77 -/agent-note-identify-approval-review-workflows-by-file-path/problem: - en: 6b927a7545ac2c48 - zh: 0902f03aa478d5ee -/agent-note-identify-approval-review-workflows-by-file-path/decision: - en: 82802438f1b8d16c - zh: ffe8c1fcbcc39abe -/agent-note-identify-approval-review-workflows-by-file-path/alternatives-considered: - en: f8fdd869a03ac5ff - zh: c355eb36e9cba350 -/agent-note-identify-approval-review-workflows-by-file-path/consequences: - en: 9f31e023a04a7849 - zh: 66831c450af4c88f diff --git a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.i18n.yaml b/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.i18n.yaml deleted file mode 100644 index 9272a63484..0000000000 --- a/.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-11-production-blame-approval-weight.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md -/agent-note-weight-approvals-by-changed-production-line-ownership: - en: 40516dee2915ffc3 - zh: 3d58358bc7110df2 -/agent-note-weight-approvals-by-changed-production-line-ownership/problem: - en: 2dbd821b8d65b054 - zh: 9bef2993db51a00d -/agent-note-weight-approvals-by-changed-production-line-ownership/decision: - en: 7dc225f91538ace1 - zh: a22d05ffd8d32b6d -/agent-note-weight-approvals-by-changed-production-line-ownership/alternatives-considered: - en: 5e8af03e3ff6018a - zh: 6b518c8eee8c5172 -/agent-note-weight-approvals-by-changed-production-line-ownership/consequences: - en: 08495483b77771be - zh: a798d4af0eda55fe -/agent-note-weight-approvals-by-changed-production-line-ownership/verification: - en: cb604625373372b9 - zh: f9056d20ab612537 diff --git a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.i18n.yaml b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.i18n.yaml index cf4ae928c6..e3a2921567 100644 --- a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.i18n.yaml @@ -9,8 +9,8 @@ en: 6938ed9a5ca419bb zh: 6d2cd6bb912b9118 /agent-note-keep-experimental-packages-outside-the-default-product/decision: - en: d471671fd6c06d03 - zh: 205586983a29eba4 + en: b5e4681e86cf54c1 + zh: d04dfcda4d402754 /agent-note-keep-experimental-packages-outside-the-default-product/alternatives-considered: en: b8615d8d31f313d7 zh: 556e3b96a90f6897 diff --git a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md index 5c2485e4f7..2266f39a44 100644 --- a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md +++ b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.md @@ -22,7 +22,7 @@ Build-time input checks cover both browser bundling stages. The [client preset]( [`verify-packed-install`](../../../../scripts/release/verify-packed-install.ts) follows the installed dependency graph from `@deepseek-ai/dsh`, using resolved manifest names to detect aliases and external transitive dependencies. Development dependencies and unrelated tarballs installed beside the product are excluded. Missing required dependencies fail; omitted optional dependencies remain allowed unless they name an experimental package. -This check enforces the existing [experimental dependency isolation rules](../architecture/2026-08-18-experimental-agent-teams-packages.md). The [publication policy](2026-09-12-experimental-publication-denylist.md) independently determines which experimental packages explicit consumers may install. +This check enforces the existing [experimental dependency isolation rules](../../../../packages/experimental/AGENTS.md). The [publication policy](../../../../packages/experimental/README.md) independently determines which experimental packages explicit consumers may install. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.zh.md b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.zh.md index 3a412a491a..60922b9c39 100644 --- a/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.zh.md +++ b/.agents/notes/implemented/process/2026-09-12-default-product-experimental-isolation.zh.md @@ -22,7 +22,7 @@ Status: implemented [`verify-packed-install`](../../../../scripts/release/verify-packed-install.ts) 从 `@deepseek-ai/dsh` 遍历已安装依赖图,通过解析后的 manifest 名称识别别名和外部传递依赖。开发依赖以及安装在产品旁边的不相关 tarball 不参与遍历。缺失必需依赖会失败;允许省略可选依赖,但其名称不得指向实验包。 -此检查执行现有的[实验包依赖隔离规则](../architecture/2026-08-18-experimental-agent-teams-packages.zh.md)。[发布策略](2026-09-12-experimental-publication-denylist.zh.md) 独立决定显式消费者可以安装哪些实验包。 +此检查执行现有的[实验包依赖隔离规则](../../../../packages/experimental/AGENTS.md)。[发布策略](../../../../packages/experimental/README.zh.md) 独立决定显式消费者可以安装哪些实验包。 ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.i18n.yaml b/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.i18n.yaml deleted file mode 100644 index 0d6fed02a4..0000000000 --- a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-experimental-publication-denylist.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md -/agent-note-publish-experimental-packages-with-explicit-private-exceptions: - en: 20aed7271477745f - zh: 81b50a3ba89b7935 -/agent-note-publish-experimental-packages-with-explicit-private-exceptions/problem: - en: c267b50c3004619f - zh: 91830d04d2972d46 -/agent-note-publish-experimental-packages-with-explicit-private-exceptions/decision: - en: 3cf968834876a655 - zh: 7f7bad8b931a5ed2 -/agent-note-publish-experimental-packages-with-explicit-private-exceptions/alternatives-considered: - en: 61369830fb32e695 - zh: 445eb44e1ae3457a -/agent-note-publish-experimental-packages-with-explicit-private-exceptions/consequences: - en: 23f4d725196f1c2d - zh: ebd9455778d8a89f diff --git a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.i18n.yaml b/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.i18n.yaml deleted file mode 100644 index 34379e6e6c..0000000000 --- a/.agents/notes/implemented/process/2026-09-12-maintained-repository-references.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-maintained-repository-references.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-12-maintained-repository-references.md -/agent-note-maintained-repository-references: - en: 10750a69061b5dd0 - zh: ab4be61612ead1da -/agent-note-maintained-repository-references/problem: - en: 36ca11f2ef3d8b4e - zh: 6e6ca6ae00fab2f7 -/agent-note-maintained-repository-references/decision: - en: ec87568d74d5dc5f - zh: bc34159aeafa9fcd -/agent-note-maintained-repository-references/alternatives-considered: - en: de1c15bc0d404c41 - zh: a32a80be126d3d71 -/agent-note-maintained-repository-references/consequences: - en: 17e87405d1ab8efa - zh: d4dbc337733d9a2d diff --git a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.i18n.yaml b/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.i18n.yaml deleted file mode 100644 index 3568158aca..0000000000 --- a/.agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-publish-all-experimental-packages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-12-publish-all-experimental-packages.md -/agent-note-publish-every-current-experimental-package: - en: e08e7bc92478407d - zh: 45501f51a5f619ec -/agent-note-publish-every-current-experimental-package/problem: - en: 7db3c27e4b5c2d09 - zh: 0a770941ec41a50a -/agent-note-publish-every-current-experimental-package/decision: - en: cc702e2596b0bd73 - zh: 3815da9b56ae653b -/agent-note-publish-every-current-experimental-package/alternatives-considered: - en: e59c9f8484629e20 - zh: d6f64683af3d0460 -/agent-note-publish-every-current-experimental-package/consequences: - en: 2721ffbcf5341d3b - zh: be0b430b9d91d7c2 diff --git a/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.i18n.yaml b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.i18n.yaml new file mode 100644 index 0000000000..b0b5d6295a --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-09-14-generated-cli-help-reference.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.md +/agent-note-generate-the-cli-reference-from-command-help: + en: 618bafe3fd2afdb9 + zh: 433df2a8d0530c41 +/agent-note-generate-the-cli-reference-from-command-help/problem: + en: 2ad571458d5d8191 + zh: 4db6bddbad65bf5c +/agent-note-generate-the-cli-reference-from-command-help/decision: + en: d99a93133c460385 + zh: 0f710ecf6e7137f2 +/agent-note-generate-the-cli-reference-from-command-help/alternatives-considered: + en: df2f7f07b3e84418 + zh: 2dd87333d9d460c3 +/agent-note-generate-the-cli-reference-from-command-help/consequences: + en: aada6686406c892e + zh: 9a30b6b1c5ab6d55 diff --git a/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.md b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.md new file mode 100644 index 0000000000..2a33722fe0 --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.md @@ -0,0 +1,25 @@ +# Agent Note: Generate the CLI reference from command help + +Status: implemented + +English | [中文](2026-09-14-generated-cli-help-reference.zh.md) + +## Problem + +Launcher flags, profile options, and nested commands have separate owners. A manually maintained command reference can omit subcommands or retain obsolete defaults. + +## Decision + +The CLI reference captures the supported `dsh` launcher's help, the plugin forwarder's help, and each shipped profile's help, then recursively discovers the subcommands listed in each page's `Commands:` section. Shipped profile names come from `PROFILE_TEMPLATES`. Every page retains its complete help text; identical alias help appears once with all invocation forms. + +Generation uses temporary Harness and Agents homes. Help exits before application work starts and requires no server or model. The plugin forwarder exposes its own help before pnpm arguments; flags after the pnpm command remain pnpm input. + +The generated bilingual pages retain CLI output in its original language. `verify-cli-help` compares the complete pages and pairing record and runs with the documentation checks. + +## Alternatives considered + +A second command inventory or help-specific runtime API would duplicate the parser's definitions. Reading actual help keeps the reference tied to the same descriptions, options, defaults, and examples users receive. + +## Consequences + +Command changes update the reference through the same generator. Reviewers can compare complete help without assembling separate command invocations. diff --git a/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.zh.md b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.zh.md new file mode 100644 index 0000000000..beba0b1ce2 --- /dev/null +++ b/.agents/notes/implemented/process/2026-09-14-generated-cli-help-reference.zh.md @@ -0,0 +1,25 @@ +# Agent Note: 从命令帮助生成 CLI 参考 + +Status: implemented + +[English](2026-09-14-generated-cli-help-reference.md) | 中文 + +## Problem + +启动器选项、profile 选项和嵌套命令由不同部分负责。手工维护的命令参考可能遗漏子命令或保留过时的默认值。 + +## Decision + +CLI 参考采集受支持的 `dsh` 启动器帮助、插件转发器帮助和各随附 profile 的帮助,并递归发现每页 `Commands:` 段落列出的子命令。随附 profile 的名称来自 `PROFILE_TEMPLATES`。每页都保留完整的帮助文本;相同的别名帮助只展示一次,并列出所有调用形式。 + +生成过程使用临时 Harness home 和 Agents home。帮助在应用开始工作前退出,不需要服务器或模型。插件转发器在 pnpm 参数之前提供自己的帮助;pnpm 命令之后的选项仍作为 pnpm 输入。 + +生成的双语页面保留 CLI 输出的原始语言。`verify-cli-help` 比较完整页面和配对记录,并随文档检查一起运行。 + +## Alternatives considered + +另一份命令清单或专供帮助使用的运行时 API 会重复解析器的定义。读取实际帮助,可以让参考文档始终对应用户收到的说明、选项、默认值和示例。 + +## Consequences + +命令变化通过同一个生成器更新参考文档。审阅者可以直接比较完整帮助,无需自行拼接各个命令的调用结果。 diff --git a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.i18n.yaml b/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.i18n.yaml deleted file mode 100644 index 14bf4d7016..0000000000 --- a/.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-pr-approval-delegation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-15-pr-approval-delegation.md -/agent-note-pr-scoped-approval-delegation: - en: 3fa5881b5ca14a79 - zh: 99dbde5fae7cdd6a -/agent-note-pr-scoped-approval-delegation/problem: - en: 5fe2f3b3cb7ce046 - zh: ed27d4efc995b8b5 -/agent-note-pr-scoped-approval-delegation/decision: - en: 41c5c90b82d323c6 - zh: 55d5ed09d88bb821 -/agent-note-pr-scoped-approval-delegation/alternatives-considered: - en: a6006217cb29613a - zh: 83968328096f1d91 -/agent-note-pr-scoped-approval-delegation/consequences: - en: e761c382b4431212 - zh: 43bb475b646c85eb diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml index f5e57341df..e3ffc60e70 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml @@ -9,11 +9,11 @@ en: dc43ba84345e1405 zh: dbc03572ab7710d2 /agent-note-ship-optional-bundles-with-the-installation/decision: - en: d64020c4ca4957e1 - zh: 241b3b851234b6f0 + en: 5210b3b746cfee78 + zh: f1907f4807ae0098 /agent-note-ship-optional-bundles-with-the-installation/alternatives-considered: - en: 05c19f616009bee8 - zh: af318640481351f0 + en: 95b48b61757f1f13 + zh: e9fb2d6ea492a4bc /agent-note-ship-optional-bundles-with-the-installation/consequences: en: 4a9d3a049a605150 zh: 67b50bbc9f7f4bb0 diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md index 059fe5bcce..151cc8ccad 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md @@ -10,15 +10,15 @@ The Web plugin page manages only the bundles a person installed into the profile ## Decision -The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts`, beside the profile templates) the bundles the installation ships for a person to switch on. Each must be a runtime dependency of `apps/cli` that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page opens its Official group with the optional bundles, tagged beta where the feature is one, ahead of the profile's own installed bundles. +The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts`, beside the profile templates) the bundles the installation ships for a person to switch on. Each must be a runtime dependency of `apps/cli` that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page opens its Official group with the optional bundles, tagged experimental when the package is experimental, ahead of the profile's own installed bundles. Default-product isolation keeps its rules with one declared exception: an optional bundle's dependency graph is outside the default product. The static gate skips the `dependencies` edge from `@deepseek-ai/dsh` to a listed bundle and still rejects a runtime import, a shipped composition, a preset, or a default template that names it, an experimental dependency the list does not name, and a listed name that is not a runtime dependency or not a bundle. The workspace-constraints check accepts the same `dependencies` edges and no other runtime section, and the packed-install release check skips them from the installed entry package while requiring each listed bundle to be installed. -The [single-bundle decision](../architecture/2026-09-18-agent-teams-single-bundle.md) supersedes the two Agent Teams selections with one `@deepseek-ai/dsh-experimental-agent-team-profile` selection. This note retains the installation-owned optional-bundle policy; the [optional-bundle decision for experimental capabilities](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) names the current list and the display metadata each entry carries. +The [single-bundle reference](../../../../packages/experimental/agent-team-profile/README.md) supersedes the two Agent Teams selections with one `@deepseek-ai/dsh-experimental-agent-team-profile` selection. This note retains the installation-owned optional-bundle policy; the [optional-bundle decision for experimental capabilities](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) owns composition and display metadata; `OPTIONAL_BUNDLES` owns the current shipped list. ## Alternatives considered -**A catalog of installable official bundles.** The page would offer names to install from the registry on demand. That keeps the installation unchanged but needs network access at the moment of switching on and a version pin per release. +**A catalog of installable official bundles.** The page would offer names to install from the registry on demand. That keeps the installation unchanged but needs network access at the moment of switching on and a version pin per release. Lightweight bundles retain the shipped policy; the [on-demand native-bundle decision](../architecture/2026-10-05-official-on-demand-bundles.md) owns the exception for costly native runtimes. **A flag on the bundle package.** A `dsh.bundle.optional` declaration would let any published bundle claim a place in the installation; the launcher's own list keeps the choice with the product. diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md index 9847095601..9ddd54da88 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md @@ -10,15 +10,15 @@ Web 插件页只管理用户装进 profile 的组合包。像 Agent Teams 这样 ## 决策 -启动器在 `OPTIONAL_BUNDLES`(`packages/boot/app-boot/src/profile.ts`,与 profile 模板并列)里点名安装随附、供用户开启的组合包。每一个都必须是 `apps/cli` 声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页以可选组合包开启「官方」分组,属于 beta 功能的带 beta 标签,排在 profile 自己已安装的组合包之前。 +启动器在 `OPTIONAL_BUNDLES`(`packages/boot/app-boot/src/profile.ts`,与 profile 模板并列)里点名安装随附、供用户开启的组合包。每一个都必须是 `apps/cli` 声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页以可选组合包开启「官方」分组,属于实验包的带实验性标签,排在 profile 自己已安装的组合包之前。 默认产品隔离的规则保持不变,只声明一个例外:可选组合包的依赖图在默认产品之外。静态门禁跳过从 `@deepseek-ai/dsh` 到列表中组合包的 `dependencies` 边,仍然拒绝运行时 import、随附组合、preset 或默认模板对它的引用,拒绝列表没有点名的实验依赖,也拒绝不是运行时依赖或不是组合包的列表项。workspace 约束检查接受同样的 `dependencies` 边而不接受其他运行时依赖段;发布时的 packed-install 检查对已安装入口包跳过这些边,并要求列表中的每个组合包都已安装。 -[单组合包决策](../architecture/2026-09-18-agent-teams-single-bundle.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)列出当前名单以及每一项携带的显示元数据。 +[单组合包参考](../../../../packages/experimental/agent-team-profile/README.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)定义组合与显示元数据;当前随附名单由 `OPTIONAL_BUNDLES` 管理。 ## 考虑过的替代方案 -**可安装官方组合包目录。** 页面按需提供从注册表安装的包名。安装本身不变,但开启那一刻需要网络,并且每次发布都要钉一个版本。 +**可安装官方组合包目录。** 页面按需提供从注册表安装的包名。安装本身不变,但开启那一刻需要网络,并且每次发布都要钉一个版本。轻量 bundle 仍采用随附策略;[按需原生 bundle 决策](../architecture/2026-10-05-official-on-demand-bundles.zh.md)管理原生运行时成本较高的例外。 **组合包自身的标记。** `dsh.bundle.optional` 声明会让任何已发布的组合包都能自称随安装提供;由启动器自己的列表来决定,选择权留在产品手里。 diff --git a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.i18n.yaml b/.agents/notes/implemented/process/2026-09-16-author-approval-credit.i18n.yaml deleted file mode 100644 index cfc6317172..0000000000 --- a/.agents/notes/implemented/process/2026-09-16-author-approval-credit.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-author-approval-credit.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-16-author-approval-credit.md -/agent-note-author-approval-credit: - en: 58068bcf9dbf6d30 - zh: ad51b869c8f3e791 -/agent-note-author-approval-credit/problem: - en: 13def594e495779d - zh: d5ceaa0d156ba043 -/agent-note-author-approval-credit/decision: - en: 5670829131121899 - zh: 7df21c9b22c4978d -/agent-note-author-approval-credit/alternatives-considered: - en: 8ab9e11e49044029 - zh: c7930455d747ceb5 -/agent-note-author-approval-credit/consequences: - en: 70ad19025f7fe6d4 - zh: c73aa8be25724790 -/agent-note-author-approval-credit/verification: - en: 0617e23e217b0a67 - zh: 82576f05a59d8db7 diff --git a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.i18n.yaml b/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.i18n.yaml deleted file mode 100644 index f25c473733..0000000000 --- a/.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-desktop-release-version-derivation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md -/agent-note-derive-desktop-test-versions-from-the-complete-dsh-version: - en: b4afaf99cba87722 - zh: bd6c3b76efebf932 -/agent-note-derive-desktop-test-versions-from-the-complete-dsh-version/problem: - en: 307dae6b4bdf80de - zh: 72c9045a2e42036f -/agent-note-derive-desktop-test-versions-from-the-complete-dsh-version/decision: - en: 1a3ef4e14511dc1f - zh: 1db763a1323d9c87 -/agent-note-derive-desktop-test-versions-from-the-complete-dsh-version/alternatives-considered: - en: cf86267e11a4e534 - zh: daa4fb017b2725a5 -/agent-note-derive-desktop-test-versions-from-the-complete-dsh-version/consequences: - en: ce62d7f641c0cd45 - zh: 08b05c154f38d816 diff --git a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml b/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml deleted file mode 100644 index e36a2aa1c1..0000000000 --- a/.agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-evidence-led-simplification-surveys.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-19-evidence-led-simplification-surveys.md -/agent-note-evidence-led-simplification-surveys: - en: f0941223baf75c96 - zh: b3cc733f7f90509a -/agent-note-evidence-led-simplification-surveys/problem: - en: 528a0c462d456493 - zh: a2af4901a8f498a7 -/agent-note-evidence-led-simplification-surveys/decision: - en: 865cfb210f13b188 - zh: 777d8155216d98c6 -/agent-note-evidence-led-simplification-surveys/historical-evidence: - en: 53eea2f1d7560461 - zh: 3c55432d9cd3ebe3 -/agent-note-evidence-led-simplification-surveys/bounded-survey-evidence: - en: 3637438d2ed7f606 - zh: d78b39ae6b67a442 -/agent-note-evidence-led-simplification-surveys/alternatives-considered: - en: 9e0039e0ff82ba1b - zh: fa4b0a52bf3dcbdd -/agent-note-evidence-led-simplification-surveys/consequences: - en: 395acde9597898c5 - zh: 45ee94d13427862a diff --git a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.i18n.yaml b/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.i18n.yaml deleted file mode 100644 index 32be76bd93..0000000000 --- a/.agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-21-desktop-build-version-as-input.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-21-desktop-build-version-as-input.md -/agent-note-pass-the-desktop-build-version-as-an-argument-instead-of-rewriting-manifests: - en: 300e3ca5e8255a56 - zh: 59f948ca2440d33b -/agent-note-pass-the-desktop-build-version-as-an-argument-instead-of-rewriting-manifests/problem: - en: 9f373ce939732eb2 - zh: a021c3f432d10fbe -/agent-note-pass-the-desktop-build-version-as-an-argument-instead-of-rewriting-manifests/decision: - en: be9fa7f7e85134f1 - zh: 51796ebbdfffc063 -/agent-note-pass-the-desktop-build-version-as-an-argument-instead-of-rewriting-manifests/alternatives-considered: - en: b5025ea37e2908bc - zh: 6f2ebcaa138f1c62 -/agent-note-pass-the-desktop-build-version-as-an-argument-instead-of-rewriting-manifests/consequences: - en: 686a5979332c0964 - zh: 89cff9e1e04e0325 diff --git a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml b/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml deleted file mode 100644 index 0cd788f54a..0000000000 --- a/.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-desktop-main-bundle-after-workspace-tsdown.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md -/agent-note-bundle-the-desktop-main-process-after-the-workspace-tsdown-pass: - en: 47f6a531ef05a7be - zh: 18dc33bcd916e2b5 -/agent-note-bundle-the-desktop-main-process-after-the-workspace-tsdown-pass/problem: - en: dc5156548e545037 - zh: 72010884a2a27877 -/agent-note-bundle-the-desktop-main-process-after-the-workspace-tsdown-pass/decision: - en: 9b3c9c696207b13e - zh: 4996ad87dd07d839 -/agent-note-bundle-the-desktop-main-process-after-the-workspace-tsdown-pass/alternatives-considered: - en: a15cfe689823ae57 - zh: 1d691cd335bdcb87 -/agent-note-bundle-the-desktop-main-process-after-the-workspace-tsdown-pass/consequences: - en: e46c28e2ba972cfb - zh: 70d4076a89e0b2ec diff --git a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.i18n.yaml b/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.i18n.yaml deleted file mode 100644 index 05753024a2..0000000000 --- a/.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-22-workspace-release-ranges.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md -/agent-note-workspace-dependency-release-ranges: - en: 8b7bf2179034aa08 - zh: a7a8b5dc60849d88 -/agent-note-workspace-dependency-release-ranges/problem: - en: ea71dd3eea6e1c60 - zh: 2a8ea98e8f6a3fbf -/agent-note-workspace-dependency-release-ranges/decision: - en: c7e9c7eb5b3e2ebf - zh: b74ef023fc894e36 -/agent-note-workspace-dependency-release-ranges/alternatives-considered: - en: b6aa87296ee61fcf - zh: f516320e589ce7a3 -/agent-note-workspace-dependency-release-ranges/consequences: - en: 49eac7a43407bbd9 - zh: 8a39766e8df596b8 diff --git a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml b/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml deleted file mode 100644 index f05c998dde..0000000000 --- a/.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-23-section-keyed-translation-pairing-records.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.md -/agent-note-section-keyed-translation-pairing-records: - en: fe3ee02933f0e25b - zh: 3518b378aebef215 -/agent-note-section-keyed-translation-pairing-records/problem: - en: 1662619f5cc6743f - zh: e98e9b39e82a2294 -/agent-note-section-keyed-translation-pairing-records/decision: - en: 333d08badeeef8a6 - zh: 2ccb1fe95b3457ff -/agent-note-section-keyed-translation-pairing-records/alternatives-considered: - en: 3e17a6963d74e3de - zh: cc086da130648d3c -/agent-note-section-keyed-translation-pairing-records/consequences: - en: 7a1f77c69ef79755 - zh: 7f12bd25f556ba9c -/agent-note-section-keyed-translation-pairing-records/measured-effect: - en: 97af2f09c25338f1 - zh: 2ec62a414fa61254 -/agent-note-section-keyed-translation-pairing-records/related: - en: 8f208df4c003e06e - zh: c1660f5b55dea743 diff --git a/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.i18n.yaml b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.i18n.yaml new file mode 100644 index 0000000000..79f3339e71 --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-pr-artifact-typechecks.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md +/agent-note-pr-artifact-builds-and-aggregate-typescript-checks: + en: a25e7cdcad03934f + zh: 48e31429730e0885 +/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/problem: + en: e5c984fa32cc5e69 + zh: 25d5742b2106d830 +/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/decision: + en: 6e84bceaba4573ca + zh: 25a37c147a675643 +/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/alternatives-considered: + en: f61da066f9ddcd62 + zh: 816fd6e0063017cd +/agent-note-pr-artifact-builds-and-aggregate-typescript-checks/consequences: + en: 46c247ab91126d52 + zh: f1be7561ff402bcc diff --git a/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md new file mode 100644 index 0000000000..7577a7c06e --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md @@ -0,0 +1,45 @@ +# Agent Note: PR artifact builds and aggregate TypeScript checks + +Status: implemented + +English | [中文](2026-10-05-pr-artifact-typechecks.zh.md) + +## Problem + +PR jobs need the same compiled packages for benchmarks, compatibility, packaging, and runtime checks. Building the repository-wide test and script programs in every artifact job repeats expensive diagnostics. In the allowed #3661 baseline, benchmark build preparation takes about 304 seconds before 432 seconds of serial measurements. A cold macOS arm64 Host compiler profile on Node 24.19.0 takes 84.01 seconds, including 55.92 seconds in the aggregate program; its 282 emitted projects take 24.33 seconds combined. Increasing concurrency consumes more shared-runner resources. + +## Decision + +Required Linux consumer and native Windows build jobs retain normal builds, including both complete aggregate programs. Public `build` and `typecheck` keep the same checks. [Node version ownership](2026-07-06-node-engine-floor.md) and [native Windows ownership](2026-08-08-native-windows-pull-request-ci.md) remain in force. + +Checked PR artifact builds compile every existing project reference from each compiler face with one serial `tsc -b` process, then run the ordinary Typert, bundling, Desktop, and Web stages. The helper reads the existing Host and Client aggregates rather than maintaining another package list. It omits only each root's additional test and script program; package diagnostics, emitted outputs, and Host-before-Client ordering remain required. Non-PR release and real-API workflow builds retain their normal checks. + +Only the CI benchmark helper selects `tsc -b --noCheck` for package emission. Benchmarks consume JavaScript and Typert runtime metadata; their declaration and build-information files are disposable and never published. Typert analyzes source types independently, while required complete builds own semantic diagnostics. Syntax and configuration failures still reject benchmark preparation. TypeScript records pending checks so a later ordinary incremental build diagnoses the same source without deleting caches or forcing a rebuild. Public benchmark scripts and every release, Python, preview, compatibility, and normal artifact builder keep their checked defaults. + +The Python executable builder selects artifact compilation with `--artifacts-only` after verifying its runtime closure. Invalid closure inputs fail before compilation, deployment, or packing; the default complete build and prebuilt `--skip-build` mode keep their existing stages. + +CI invokes the internal build helpers directly and keeps the root package manifest unchanged. Root manifest edits activate the separate native-addon platform matrix even when only build aliases change; avoiding that trigger preserves the existing workflow set and concurrent-job ceiling without weakening native validation. + +Job inventory, runner selectors, and benchmark sample counts stay fixed; gate and worker ceilings do not increase. The three compatibility cases run one job at a time; the two-target Python runtime matrix starts after the keyless Python SDK job succeeds. Four Linux lanes, the Python SDK, three Windows lanes, and one compatibility job permit at most nine worker jobs initially. Once the SDK job finishes, the remaining seven independent lanes, one compatibility job, and two runtime targets permit at most ten. The runtime plan and wheel stages precede those targets and each use one job. This permits runtime validation to overlap the Node 22 build within the original ten-job ceiling. Coverage partitions reuse compact file-cost metadata within their platform and runner pool. Parallel release packing uses pnpm's existing bounded scheduler; serial publication keeps family order. + +Each Node version runs the same seven source compatibility specs in one serial Vitest invocation. Both file parallelism and the CLI worker limit are fixed to one, and the gate sets `VITEST_MAX_WORKERS=1` because that environment variable overrides Vitest's CLI limit. Files retain fork isolation; the build-backed Node 22 lazy-search smoke keeps its separate build dependency. Three paired local samples preserve all 253 assertion outcomes and reduce startup-only median time from 11.52 to 8.69 seconds, without increasing owned descendant-process peak. + +## Alternatives considered + +**Raise job or worker concurrency.** Rejected because the shared runner must keep its current resource ceiling. + +**Remove aggregate checks from every build.** Rejected because package projects do not typecheck all repository tests and scripts; required Linux and Windows jobs must retain those diagnostics. + +**Cache compiled outputs across revisions.** Deferred because cache correctness and invalidation add obligations that removing duplicate aggregate work does not need. + +## Consequences + +The cold Host reference build takes 23.49 seconds with 1.91 GiB peak RSS, compared with 84.01 seconds and 3.87 GiB for the complete compiler pass. All 5,932 emitted files match by path and SHA-256. These are single local compiler samples, excluding bundling and tests; current-head CI owns the PR latency result. The benchmark scenarios and their budgets remain unchanged. + +Three alternating cold preparation pairs on Apple M4 Pro / Node 24.19.0 compare checked reference compilation with benchmark-only emission: 61.16, 82.53, and 57.80 seconds versus 55.53, 50.82, and 50.75 seconds. Median preparation falls from 61.16 to 50.82 seconds (16.9%); this includes native support, libraries, benchmark workers, and Web assets, excluding dependency installation and measurements. Every pair has the same 11,894 output paths, including 3,181 byte-identical JavaScript files and unchanged source maps. The 356 build-information files differ, and nine declarations only reorder union members; those files are not published from the benchmark lane. Library-build median user CPU falls from 76.45 to 64.23 seconds, while median maximum RSS stays near 2.75 GiB. Required CI determines the complete PR timing. + +Parallel packing relies on the pinned pnpm CLI to honor exact recursive filters, the pack destination, the workspace concurrency limit, and each package's lifecycle hooks. A pnpm upgrade must pass the real-CLI checks in `scripts/release/pack.spec.ts` before release packing adopts it. + +Coverage weights sum Vitest's distinct environment, preparation, setup, collection, and execution costs. CI's platform/environment/pool cache prefixes exclude the previous `coverage-times-` archives. Persistent local checkouts can retain wall-clock weights for files not measured during rollout; removing `.coverage-times.json` resets that history. These advisory weights affect partition assignment, while the complete inventory and merged coverage thresholds remain required. + +Checked artifact builds deliberately do not diagnose aggregate-only test or script errors. Required normal builds reject those errors; fixture checks also reject package errors on both paths and compare emitted JavaScript, declarations, maps, and build information. Removing or changing a required full build requires transferring both aggregate checks to another blocking owner first. Master serial references remain complete under their [existing policy](2026-07-21-serial-cross-platform-ci-reference.md). diff --git a/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md new file mode 100644 index 0000000000..c0b3c1bcf4 --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md @@ -0,0 +1,45 @@ +# Agent Note: PR 产物构建与 aggregate TypeScript 检查 + +Status: implemented + +[English](2026-10-05-pr-artifact-typechecks.md) | 中文 + +## Problem + +PR job 需要相同的编译包来运行 benchmark、兼容性、打包和运行时检查。每个产物 job 都构建仓库级测试和脚本 program,会重复执行昂贵的诊断。在允许使用的 #3661 baseline 中,benchmark 在 432 秒的串行测量前花费约 304 秒准备构建。在 macOS arm64、Node 24.19.0 上,一次冷启动 Host 编译器 profile 耗时 84.01 秒,其中 aggregate program 占 55.92 秒;282 个有 emit 的 project 合计耗时 24.33 秒。提高并发会消耗更多共享运行器资源。 + +## Decision + +必需的 Linux consumer 与原生 Windows build job 保留普通构建,包括两个完整 aggregate program。公开的 `build` 和 `typecheck` 保留相同检查。[Node 版本职责](2026-07-06-node-engine-floor.zh.md)与[原生 Windows 职责](2026-08-08-native-windows-pull-request-ci.zh.md)仍然有效。 + +带检查的 PR 产物构建通过一个串行 `tsc -b` 进程编译各 compiler face 的全部现有 project reference,然后运行普通 Typert、bundling、Desktop 和 Web 阶段。helper 读取现有 Host 与 Client aggregate,而不维护另一份包清单。它只省略各 root 附加的测试和脚本 program;包诊断、emit 输出及 Host 先于 Client 的顺序仍然必需。非 PR 的 release 和真实 API 工作流构建保留普通检查。 + +只有 CI benchmark helper 选择 `tsc -b --noCheck` 来 emit 包。benchmark 消费 JavaScript 和 Typert 运行时元数据;其声明与 build-information 文件是临时产物,不会发布。Typert 独立分析源码类型,语义诊断由必需的完整构建负责。语法与配置错误仍会拒绝 benchmark 准备。TypeScript 记录待检查状态,使后续普通增量构建无需删除缓存或强制重建,就能诊断相同源码。公开 benchmark script,以及所有 release、Python、preview、compatibility 和普通产物 builder,均保留带检查的默认行为。 + +Python executable builder 在验证 runtime closure 后,通过 `--artifacts-only` 选择产物编译。无效 closure 在编译、部署或打包前失败;默认完整构建和复用预构建产物的 `--skip-build` 模式保留原有阶段。 + +CI 直接调用内部 build helper,并保持 root package manifest 不变。即使只修改 build alias,root manifest 改动也会激活独立的 native-addon 平台矩阵;避免这一触发条件,可以在不削弱 native 验证的情况下保留现有 workflow 集合与并发 job 上限。 + +job 清单、runner 选择和 benchmark sample 数量保持固定;gate 与 worker 上限不提高。三个 compatibility case 每次运行一个 job;双目标 Python runtime 矩阵在 keyless Python SDK job 成功后启动。四个 Linux lane、Python SDK、三个 Windows lane 和一个 compatibility job,初始最多占用九个 worker job。SDK job 完成后,剩余七个独立 lane、一个 compatibility job 和两个 runtime target,最多占用十个 job。runtime plan 与 wheel 阶段先于这些 target,各占一个 job。这允许 runtime 验证与 Node 22 构建重叠,同时保持原有十个 job 的上限。覆盖率分区在各自的平台和 runner pool 内复用精简的文件成本元数据。并行 release 打包使用 pnpm 现有的有界调度器;串行发布保持 family 顺序。 + +每个 Node 版本在一次串行 Vitest 调用中运行相同的七个 source compatibility spec。文件并行和 CLI worker 上限均固定为一,gate 还设置 `VITEST_MAX_WORKERS=1`,因为该环境变量会覆盖 Vitest 的 CLI 上限。文件保持 fork 隔离;依赖构建的 Node 22 lazy-search smoke 保留独立的 build 依赖。三对本地样本保持全部 253 个 assertion outcome,将启动开销比较的 median 从 11.52 秒降至 8.69 秒,且不增加所拥有 descendant process 的峰值。 + +## Alternatives considered + +**提高 job 或 worker 并发。** 否决,因为共享运行器必须保持现有资源上限。 + +**从所有构建中移除 aggregate 检查。** 否决,因为包 project 不会检查所有仓库测试和脚本的类型;必需的 Linux 与 Windows job 必须保留这些诊断。 + +**跨 revision 缓存编译输出。** 暂缓,因为缓存正确性与失效规则增加了额外义务,而移除重复 aggregate 工作无需承担这些义务。 + +## Consequences + +冷启动 Host reference 构建耗时 23.49 秒、峰值 RSS 为 1.91 GiB;完整编译器阶段为 84.01 秒和 3.87 GiB。全部 5,932 个 emit 文件的路径与 SHA-256 相同。这些是单次本地编译器样本,不含 bundling 和测试;PR 延迟结果以当前 head 的 CI 为准。benchmark 场景与预算保持不变。 + +在 Apple M4 Pro / Node 24.19.0 上,三对交替冷启动准备构建比较带检查的 reference 编译与仅用于 benchmark 的 emit:前者为 61.16、82.53、57.80 秒,后者为 55.53、50.82、50.75 秒。准备阶段中位数从 61.16 降至 50.82 秒(16.9%);范围包括 native support、library、benchmark worker 和 Web 产物,不含依赖安装与测量。每对都包含相同的 11,894 个输出路径,其中 3,181 个 JavaScript 文件逐字节相同,source map 不变。356 个 build-information 文件不同,九个声明仅调整 union 成员顺序;benchmark lane 不会发布这些文件。library 构建的 user CPU 中位数从 76.45 降至 64.23 秒,maximum RSS 中位数保持在约 2.75 GiB。完整 PR 耗时以必需 CI 为准。 + +并行打包依赖锁定的 pnpm CLI 正确处理精确 recursive filter、pack destination、workspace 并发上限及各包的 lifecycle hook。升级 pnpm 时,必须先通过 `scripts/release/pack.spec.ts` 中的真实 CLI 检查,再让 release 打包采用该版本。 + +覆盖率权重累加 Vitest 互不重叠的 environment、preparation、setup、collection 和 execution 成本。CI 的平台/environment/pool cache prefix 不匹配原来的 `coverage-times-` archive。持久化本地 checkout 在切换期间可能为未测量的文件保留墙钟权重;删除 `.coverage-times.json` 可重置该历史。这些参考权重影响分区分配,完整清单与合并后的覆盖率阈值仍然必需。 + +带检查的产物构建有意不诊断仅属于 aggregate 的测试或脚本错误。必需的普通构建拒绝这些错误;fixture 检查还验证两个路径都会拒绝包错误,并比较 emit 的 JavaScript、声明、map 和 build 信息。删除或改变必需的完整构建前,必须先把两个 aggregate 检查转交另一处阻塞型 owner。master 串行 reference 按[现有政策](2026-07-21-serial-cross-platform-ci-reference.zh.md)保持完整。 diff --git a/.agents/notes/implemented/process/2026-10-05-product-package-classification.i18n.yaml b/.agents/notes/implemented/process/2026-10-05-product-package-classification.i18n.yaml new file mode 100644 index 0000000000..7673f38075 --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-product-package-classification.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-product-package-classification.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-10-05-product-package-classification.md +/agent-note-require-product-use-or-an-explicit-package-classification: + en: 8caa2edff065f7db + zh: 163ee0f31c7adc6a +/agent-note-require-product-use-or-an-explicit-package-classification/problem: + en: 748eb2bf5f645911 + zh: b123aec22395193a +/agent-note-require-product-use-or-an-explicit-package-classification/decision: + en: 8678a01d48195e8f + zh: 9052cc7f64ed452f +/agent-note-require-product-use-or-an-explicit-package-classification/alternatives-considered: + en: 67177e349ecb70de + zh: e8263b9445f46ded +/agent-note-require-product-use-or-an-explicit-package-classification/consequences: + en: 449278a2bccea70e + zh: 5eb28c22783ac6cf diff --git a/.agents/notes/implemented/process/2026-10-05-product-package-classification.md b/.agents/notes/implemented/process/2026-10-05-product-package-classification.md new file mode 100644 index 0000000000..2f3a76ae85 --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-product-package-classification.md @@ -0,0 +1,29 @@ +# Agent Note: Require product use or an explicit package classification + +Status: implemented + +English | [中文](2026-10-05-product-package-classification.zh.md) + +## Problem + +An installation dependency can remain dormant indefinitely. Its presence in the dependency graph does not establish that a shipped profile, runtime import, or optional capability uses it. Conversely, an embedding SDK or a deployment-configured provider can have a maintained product role without appearing in the default GUI. + +## Decision + +Nonexperimental packages have effective product use or an explicit entry in the [package policy](../../../../scripts/product-package-policy.ts). The policy distinguishes optional integrations, SDKs, build tools, test tools, Web distribution packages, and declaration-only infrastructure. Each exception records its maintained role. [Experimental status](../../../../packages/experimental/README.md#status) describes maturity and support, independently of installation, optional selection, and Official discovery. + +The [product-use check](../../../../scripts/verify-product-use.ts) follows effective shipped profiles, preset children, runtime source imports, and explicit dynamic mounts. It preserves disabled state through groups and Includes and applies patches before counting use. Manifest dependencies and type-only references do not create runtime use. Optional-bundle reachability remains separate from default reachability. Unknown packages and invalid or stale policy entries fail static CI and package hygiene. + +The [default-product isolation rule](2026-09-12-default-product-experimental-isolation.md) remains independent: its conservative installation and declaration traversal rejects unintended experimental dependencies even when they are disabled. That traversal cannot establish active product use. + +## Alternatives considered + +**Treat every installed package as used.** A redundant dependency would satisfy its own classification requirement and conceal dormant code. + +**Classify every nondefault capability as experimental.** Deployment prerequisites and installation cost do not establish immaturity; supported optional providers and SDKs need separate categories. + +**Count every declared patch row.** A disabled Include, missing target, or later replacement can prevent that row from mounting. Only the effective composition establishes use. + +## Consequences + +Adding a nonexperimental package requires a product consumer or a reviewed classification. Renames and reduced in-box availability require upgrade instructions, while recorded events and released data keep their existing obligations. Regression fixtures reject manifest-only dependencies, disabled subtrees, ineffective patches, and stale classifications. SDK, test, build, and declaration packages remain independently installable without acquiring a runtime mount requirement. diff --git a/.agents/notes/implemented/process/2026-10-05-product-package-classification.zh.md b/.agents/notes/implemented/process/2026-10-05-product-package-classification.zh.md new file mode 100644 index 0000000000..e53da65c0e --- /dev/null +++ b/.agents/notes/implemented/process/2026-10-05-product-package-classification.zh.md @@ -0,0 +1,29 @@ +# Agent Note: Require product use or an explicit package classification + +Status: implemented + +[English](2026-10-05-product-package-classification.md) | 中文 + +## 问题 + +安装依赖可能长期处于休眠状态。出现在依赖图中,并不表明随附 profile、运行时导入或可选能力使用了它。反过来,嵌入式 SDK 或需要部署配置的提供方即使不出现在默认 GUI 中,也可以承担维护中的产品职责。 + +## 决策 + +非实验包必须具有有效产品用途,或在[包策略](../../../../scripts/product-package-policy.ts)中显式列出。策略区分可选集成、SDK、构建工具、测试工具、Web 分发包和纯声明基础设施,每个例外记录其维护职责。[实验性状态](../../../../packages/experimental/README.zh.md#status)描述成熟度与支持承诺,独立于安装、可选启用与官方发现。 + +[产品用途检查](../../../../scripts/verify-product-use.ts)跟踪有效的随附 profile、预设子行、运行时源码导入与显式动态挂载。检查在组与 Include 中保留禁用状态,并先应用 patch 再统计用途。Manifest 依赖和纯类型引用不产生运行时用途。可选组合包的可达关系与默认可达关系分别计算。未知包及无效或过时的策略条目会使静态 CI 和包卫生检查失败。 + +[默认产品隔离规则](2026-09-12-default-product-experimental-isolation.zh.md)保持独立:其保守的安装与声明遍历即使对已禁用行,也会拒绝意外实验依赖。该遍历不能证明活动产品用途。 + +## 考虑过的替代方案 + +**将所有已安装包视为已使用。** 冗余依赖本身就能满足分类要求,从而掩盖休眠代码。 + +**将所有非默认能力归为实验性。** 部署前提与安装成本不能证明不成熟;受支持的可选提供方与 SDK 需要独立分类。 + +**统计所有声明的 patch 行。** 禁用的 Include、缺失目标或后续替换都可能阻止该行挂载。只有有效组合才能证明用途。 + +## 影响 + +新增非实验包需要产品消费者或经过审查的分类。改名和减少随附可用性需要升级说明,已记录事件和已发布数据继续遵守既有义务。回归 fixture 拒绝只有 manifest 依赖、禁用子树、无效 patch 和过时分类的情况。SDK、测试、构建与声明包仍可独立安装,无需新增运行时挂载。 diff --git a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml deleted file mode 100644 index be5d21a505..0000000000 --- a/.agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-20-collapse-trace-only-session-events.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md -/agent-note-fold-trace-only-session-facts-into-load-bearing-events: - en: e7ada88fff270fa2 - zh: 501f2d2ab2122718 -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/problem: - en: 909f9925807d0e0a - zh: eba8fb3aba92caa8 -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/decision: - en: d07841a539028e7a - zh: 5ebaef62105a0da8 -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/alternatives-considered: - en: f99cc05ab13c85c3 - zh: 93c92b3894cee7b1 -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/verification: - en: 67e74bfc51b0a1ed - zh: dfdc9364ec831b9f -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/consequences: - en: 1c0e59938ce8408f - zh: 8c891d273da28f27 -/agent-note-fold-trace-only-session-facts-into-load-bearing-events/implementation-note: - en: 40e0c79252a438bc - zh: 27332c259b4bdf95 diff --git a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.i18n.yaml index 3e2757185e..6610e2cc7c 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.i18n.yaml @@ -6,8 +6,8 @@ en: a3b5f1ad744e332a zh: f56748eb6f1c1e8f /agent-note-unify-the-agent-id-and-the-session-id/problem: - en: 13081f69bc061561 - zh: 6062223de9302202 + en: 98310c2d52fdf13b + zh: ccea68b6f7b55b2c /agent-note-unify-the-agent-id-and-the-session-id/decision: en: 58fe3d6c1fbe5aff zh: 564332602546955e diff --git a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md index 4084912f95..cbf88d7d7d 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md +++ b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md @@ -10,7 +10,7 @@ A live agent/session pair needs one identity for registry routing, event sourcin ACP uses the same value for both identities. Stdio and hooks also operate on the session event stream and need the corresponding live agent directly; no production path reattaches one live agent object to several sessions or drives one session through several agent ids. -The [agent-scope runtime](../architecture/2026-07-12-agent-scope-runtime-design.md) uses one `AgentCreationTransaction` for create and resume, and agent/session entries share the same final-entry collision rule. A second identity would not represent separate liveness, rollback, or quiescence; it would only add API and translation state around the same transaction. +The [agent-scope runtime](../../../../packages/core/agent-loop/README.md) uses one `AgentCreationTransaction` for create and resume, and agent/session entries share the same final-entry collision rule. A second identity would not represent separate liveness, rollback, or quiescence; it would only add API and translation state around the same transaction. Session identity likewise has one home in `Session.header.id`; `Session.id` is a derived accessor rather than independent state that needs duplicate validation. diff --git a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.zh.md b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.zh.md index 3adba379f1..cb3eb40da4 100644 --- a/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.zh.md +++ b/.agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.zh.md @@ -10,7 +10,7 @@ Status: implemented ACP(Agent Client Protocol)对两种 identity 使用相同值。Stdio 和钩子也在会话事件流上工作,并且直接需要对应的存活 agent;没有生产路径会把一个存活 agent 对象重新附着到多个会话,或通过多个 agent id 驱动一个会话。 -[agent 范围运行时](../architecture/2026-07-12-agent-scope-runtime-design.zh.md)使用同一个 `AgentCreationTransaction` 执行创建和恢复,agent/会话条目共享相同的最终条目冲突规则。第二个 identity 并不代表单独的存活性、回滚或完全停稳;它只会围绕同一事务增加 API 与转换状态。 +[agent 范围运行时](../../../../packages/core/agent-loop/README.zh.md)使用同一个 `AgentCreationTransaction` 执行创建和恢复,agent/会话条目共享相同的最终条目冲突规则。第二个 identity 并不代表单独的存活性、回滚或完全停稳;它只会围绕同一事务增加 API 与转换状态。 会话 identity 同样只有一个归属,即 `Session.header.id`;`Session.id` 是派生访问器,而非需要重复验证的独立状态。 diff --git a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml b/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml deleted file mode 100644 index 413cf22c2d..0000000000 --- a/.agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.i18n.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-26-fsspec-style-fs-seam.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin: - en: 181df186598c42fc - zh: 07b7687434105955 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/problem: - en: 4069760b095aa1b6 - zh: ea8baf0050362519 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/decision: - en: 6f6dc20965bb9948 - zh: 5a858a0359f96a98 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/provider-contract: - en: a04b6ceab91aa265 - zh: 5fae55161f754f4d -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/policy-contract: - en: bd54f18f4665b1ec - zh: eee54b7890c9d8be -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/tool-contract: - en: b517106d3e0ac7ef - zh: 2b128d7af26ebed2 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/concurrency-boundary: - en: 6a459d121a49988c - zh: 048de517a0f952b6 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/supersedes: - en: 780789585ce58330 - zh: 7b4b8d171dfdefae -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/verification: - en: ee6e0f4922f991af - zh: 3ce55e7b7d0fd431 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/later-extension: - en: b56210bf2c8f8dec - zh: 27decb39624c0ef7 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/alternatives-considered: - en: 3d169e07a912cbab - zh: d8a550835cfe7801 -/agent-note-split-the-filesystem-seam-provider-text-mutations-plus-the-dsh-fs-observation-policy-plugin/consequences: - en: 70264e0d9422b871 - zh: ae93add503e8a9bc diff --git a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml index cea714ade6..c5d4927d16 100644 --- a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml @@ -9,8 +9,8 @@ en: ed05e7ef01abfafc zh: 40293ad57c00f902 /agent-note-remove-implicit-batching-from-ordinary-sends/decision: - en: 354fbb4e6a776538 - zh: c78879212400bf0f + en: b173fc358ed6ac4b + zh: 549ae0cf806b90d4 /agent-note-remove-implicit-batching-from-ordinary-sends/alternatives-considered: en: f944e18c49b30b4c zh: cfcc0b18ce94f37c diff --git a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md index 71ea5d23c3..a2e89a7af1 100644 --- a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md +++ b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md @@ -16,7 +16,7 @@ This grouping changes behavior, not just the number of model calls. One ordinary Each successful `send()` creates one independent FIFO queue item. If that item runs, it is the only ordinary message in its turn. An item can be dropped before it starts, so the precise guarantee is at most one turn rather than exactly one; two sends are never silently combined. -Before inserting a message, `send()` checks the agent state and accepts an already identified, deeply frozen value. The durable splice and `agent/inbox/inserted { message }` retain its `MessageId`; the pending message remains addressable through `Inbox.replace()` and `Inbox.remove()` until the driver claims or discards it. The [claimed pre-step inbox decision](../architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.md) owns the current lifecycle. +Before inserting a message, `send()` checks the agent state and accepts an already identified, deeply frozen value. The durable splice and `agent/inbox/inserted { message }` retain its `MessageId`; the pending message remains addressable through `Inbox.replace()` and `Inbox.remove()` until the driver claims or discards it. The [claimed pre-step inbox reference](../../../../packages/core/agent-loop/README.md) owns the current lifecycle. If messages A and B are both processed, B's turn starts only after A records `turn/end` and A's durability checkpoint settles. B's request therefore sees whatever closed result A left in the same session log. A checkpoint error is reported, but settlement only releases this ordering barrier; it does not make a failed write durable. Broad `cancel()`, disposal, or a failure before `turn/start` can instead discard an unstarted item without opening an empty turn. diff --git a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md index 8478f3fb35..e9cf36b825 100644 --- a/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md @@ -16,7 +16,7 @@ Status: implemented 一次成功的 `send()` 创建一个独立的 FIFO 队列项。该队列项如果运行,就是所在轮次中唯一的普通消息。队列项可能在启动前被丢弃,因此精确保证是最多一个轮次,而不是必定一个轮次;两次 send 绝不会被悄悄合并。 -消息插入之前,`send()` 会检查 agent 状态,并接受已有标识且经过深度冻结的值。持久化 splice 与 `agent/inbox/inserted { message }` 会保留其 `MessageId`;在驱动器领取或丢弃该消息之前,可以通过 `Inbox.replace()` 与 `Inbox.remove()` 寻址。当前生命周期由[已领取 pre-step inbox 决策](../architecture/2026-07-31-claimed-pre-step-inbox-lifecycle.zh.md)规定。 +消息插入之前,`send()` 会检查 agent 状态,并接受已有标识且经过深度冻结的值。持久化 splice 与 `agent/inbox/inserted { message }` 会保留其 `MessageId`;在驱动器领取或丢弃该消息之前,可以通过 `Inbox.replace()` 与 `Inbox.remove()` 寻址。当前生命周期由[已领取 pre-step inbox 参考](../../../../packages/core/agent-loop/README.zh.md)规定。 如果消息 A、B 都进入处理,B 的轮次只能在 A 记录 `turn/end` 且 A 的持久性检查点处理结束后开始。因此,B 的请求能看到 A 在同一会话日志中留下的已关闭结果。检查点错误会照常报告,但处理结束只表示解除这道顺序屏障,不表示失败的写入已经持久化。面向整个 agent 的 `cancel()`、dispose(资源释放)或 `turn/start` 之前的失败也可能丢弃尚未启动的队列项,而不打开一个空轮次。 diff --git a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml deleted file mode 100644 index bcce3bc92b..0000000000 --- a/.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-20-unwrap-injected-content-envelopes.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes: - en: 488b566c13d0cd05 - zh: e9b9a14dc644a313 -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes/problem: - en: 0005839a50aa093b - zh: bfb21448bbec1ac9 -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes/decision: - en: ed1a833070d10b97 - zh: b7ce57a2b927f064 -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes/alternatives-considered: - en: 93dca58966e5d55e - zh: 9fe7fd528d0114a2 -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes/consequences: - en: 2b369a5b2ee9435b - zh: f72957abff1d2327 -/agent-note-project-injected-content-verbatim-dropping-the-xml-envelopes/deferred: - en: a772793aea7ee7ee - zh: a57b31af4422406d diff --git a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml deleted file mode 100644 index ad1c43c6dd..0000000000 --- a/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-22-plan-specific-collaboration-state.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md -/agent-note-plan-specific-collaboration-state: - en: b1e94e354a36ef2b - zh: bbc98d4b9a837a5a -/agent-note-plan-specific-collaboration-state/problem: - en: e79a03a0ec1ebc34 - zh: 94adee6ef4f1bcc2 -/agent-note-plan-specific-collaboration-state/decision: - en: 1255fd4c4852ea9c - zh: 27e431da895a9a69 -/agent-note-plan-specific-collaboration-state/decision/boundary-and-model-contract: - en: f9d67f4f91e14f9d - zh: 3284e2432e8a8466 -/agent-note-plan-specific-collaboration-state/decision/reviewed-exit: - en: 283fec44700c41e5 - zh: 99b8634a1dff791c -/agent-note-plan-specific-collaboration-state/deleted-api: - en: 7da48b6397bad4d0 - zh: cc0af0e26a81c7e0 -/agent-note-plan-specific-collaboration-state/alternatives-considered: - en: 4098e049cd6d28d8 - zh: 4ab332ad6af53ccf -/agent-note-plan-specific-collaboration-state/verification: - en: 41179b6ab02b3f90 - zh: 47f5a602496281cd -/agent-note-plan-specific-collaboration-state/consequences: - en: a71537da7d3921e2 - zh: a2ab69676bfe985d diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml index 0be5f93a6e..26726b4dde 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md /agent-note-acp-as-an-automation-only-protocol: - en: e8a5a82b0137b656 - zh: 8b371b4add7c17b4 + en: 2881e2b2f824bbb5 + zh: 8c9020f06bd61a0e /agent-note-acp-as-an-automation-only-protocol/problem: en: 5123f99030ae618e zh: fa62dc7b9a4b9fef diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md index d3d6a7abb6..221fa0b38f 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.md @@ -4,7 +4,7 @@ Status: implemented English | [中文](2026-07-23-acp-automation-only-protocol.zh.md) -> The automation-only boundary remains current. [Standard ACP v1 automation controls](../feature/2026-08-22-standard-acp-automation-controls.md) supersedes only this note's prompt-only method, configuration, MCP, update, and lifecycle inventory; it does not restore ACP as a UI. +> The automation-only boundary remains current. [Standard ACP v1 automation controls](../../../../packages/acp/acp/README.md) supersedes only this note's prompt-only method, configuration, MCP, update, and lifecycle inventory; it does not restore ACP as a UI. ## Problem diff --git a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md index d332e3f7c4..f9318905ba 100644 --- a/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-23-acp-automation-only-protocol.zh.md @@ -4,7 +4,7 @@ Status: implemented [English](2026-07-23-acp-automation-only-protocol.md) | 中文 -> 仅面向自动化的边界仍然有效。[标准 ACP v1 自动化控制](../feature/2026-08-22-standard-acp-automation-controls.zh.md)仅取代本说明中仅支持提示词的方法、配置、MCP、更新和生命周期清单;它不会把 ACP 恢复为 UI。 +> 仅面向自动化的边界仍然有效。[标准 ACP v1 自动化控制](../../../../packages/acp/acp/README.zh.md)仅取代本说明中仅支持提示词的方法、配置、MCP、更新和生命周期清单;它不会把 ACP 恢复为 UI。 ## 问题 diff --git a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml deleted file mode 100644 index 0e8ec12298..0000000000 --- a/.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-24-agent-loop-observable-state-machine.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.md -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine: - en: b7998bdbf46a06b1 - zh: ea7b4e8ef8e3cef5 -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine/problem: - en: c55232633eaacf5a - zh: a71c538c61234c42 -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine/decision: - en: 75a1c677da39192e - zh: 81a07f55fce3cbc0 -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine/alternatives-considered: - en: 0d61d49e9f5964c2 - zh: f5e8802c7fe67839 -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine/consequences: - en: 675d4ae63174031d - zh: 2649109d067352e2 -/agent-note-collapse-agent-loop-events-around-the-observable-state-machine/related: - en: f1610942ca31fdb8 - zh: 04eb3d4ce8e641af diff --git a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml deleted file mode 100644 index 204f3b5cba..0000000000 --- a/.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-27-request-error-retry-action.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.md -/agent-note-request-error-retry-action: - en: 672e3acc184e72f7 - zh: 10334d48de7dcc0e -/agent-note-request-error-retry-action/problem: - en: 8b9476349def23b6 - zh: e3b034bae896e24b -/agent-note-request-error-retry-action/decision: - en: 3937a99236b23d80 - zh: ec5e9cb21744e3ae -/agent-note-request-error-retry-action/alternatives-considered: - en: 8f50ac6497dfd03e - zh: 4f1d07901132a184 -/agent-note-request-error-retry-action/consequences: - en: 3e60793a09964f5a - zh: b28626761a229d85 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml index 036542d074..25b91c2e27 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.i18n.yaml @@ -9,14 +9,14 @@ en: 33413b092d535f4f zh: 02f79f372227c104 /agent-note-remove-synthetic-turns-for-log-only-events/decision: - en: a49da9273f224a87 - zh: 870ffea9aacca6c7 + en: 87283d21751d8950 + zh: edeb2901bf770ae6 /agent-note-remove-synthetic-turns-for-log-only-events/alternatives-considered: - en: fbee860bf43530a7 - zh: 276647ba5402d6a8 + en: f9849e72fa2c3e9f + zh: c5dec6de58d9e6bf /agent-note-remove-synthetic-turns-for-log-only-events/verification: - en: bb7b19f80c4fbed6 - zh: ef06defe5b745e38 + en: 4290c31853d42929 + zh: 7fd3dea33c8e8f8d /agent-note-remove-synthetic-turns-for-log-only-events/consequences: en: 0c636b744dfa4ca9 zh: 05f409a4fd962015 diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md index 073ac08880..92c88c1a87 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md @@ -16,13 +16,13 @@ The generic helper also duplicated domain policy. Its marker map said which plug `SessionStore.appendOutOfBand()`, `OutOfBandSessionEventMap`, and `OutOfBandSessionEventType` do not exist. A plugin that owns a log-only event appends it through `Session`; when the operation promises durability, it explicitly awaits `ctx.sessions.flush(session)`. No turn is opened solely to obtain that checkpoint. -Core session invariants continue to enforce core-owned execution relations: turn and step numbering, enclosure of steering, assistant, tool, todo, and request-header events, and same-step tool call/result pairing. Core permits merge-extensible events between turns because only their declaring plugin knows whether they are execution-scoped or standalone. Plugin invariant companions remain responsible for their own event relations. +Core permits merge-extensible events between turns because only their declaring plugin knows whether they are execution-scoped or standalone. The title service appends `session/title` directly after its existing service, revision, cancellation, and live-session checks. The bundled model helper appends its literal `session/title-llm-request` record before dispatch. Persistence admits both through the bounded `session/event` path and drains them at ordinary checkpoints and lifecycle teardown; neither append forces a flush merely because it is between turns. A fallback, auxiliary request record, or accepted provider title may therefore appear after `turn/end` and before the next `turn/start`. Manual compaction uses the same between-turn capability for a `compaction/* { turn: null }` bracket, but explicitly flushes the closed attempt because `/compact` promises durability before releasing queued prompt admission. -`SessionStore.fork()` preserves standalone title and other plugin-owned log-only records. The [exact-event fork decision](../feature/2026-08-18-arbitrary-seq-session-fork.md) owns cut selection and child-owned closers for an open tail. +`SessionStore.fork()` preserves standalone title and other plugin-owned log-only records. The [exact-event fork reference](../../../../packages/core/session/README.md) owns cut selection and child-owned closers for an open tail. -The historical [universal turn-enclosure decision](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md) remains useful only as the reason the synthetic mechanism was introduced. The [context-injection decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) established the current meaning: one turn represents one model-loop execution. The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) applies that rule to a durable multi-event bracket and owns its marker and admission semantics. +The historical [universal turn-enclosure decision](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md) remains useful only as the reason the synthetic mechanism was introduced. The [context-injection decision](../architecture/2026-07-24-separate-context-injection-from-turn-execution.md) established the current meaning: one turn represents one model-loop execution. The [queued manual compaction reference](../../../../packages/compaction/compaction/README.md) applies that rule to a durable multi-event bracket and owns its marker and admission semantics. ## Alternatives considered @@ -32,11 +32,11 @@ The historical [universal turn-enclosure decision](../../archived/architecture/2 **Store titles as mutable session metadata.** This avoids between-turn events but creates a second mutation, replay, persistence, and fork protocol beside the append-only log. Titles remain replayable latest-wins events instead. -**Require every plugin event to declare standalone eligibility to core.** This keeps a central allowlist but makes absence mean an execution relation that core cannot verify. Merge-extensible unions already assign semantic ownership to the declaring plugin; its invariant companion is the correct enforcement point. +**Require every plugin event to declare standalone eligibility to core.** This keeps a central allowlist but makes absence mean an execution relation that core cannot verify. Merge-extensible unions already assign semantic ownership to the declaring plugin. ## Verification -Core invariant tests accept an unknown plugin event between turns while continuing to reject built-in execution events there. Hook, plan-mode, PTC mode dispatch, and approval invariant companions reject their execution-scoped events when no turn is open; the compaction companion separately accepts a balanced `turn: null` manual bracket between turns and requires numeric owners to match an open turn. Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. A JSONL round trip preserves a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. +Session-title service tests pin one direct fallback event under concurrent refresh, detached-session rejection, and newest-revision acceptance. A JSONL round trip preserves a title appended after `turn/end` through the persistence lifecycle drain, and fork tests retain a standalone log-only tail. A keyless assembled ACP snapshot delays the model-backed title until after `turn/end` and pins one standalone provider title with no synthetic turn. Generated API and type-equivalence catalogs contain no removed symbol. ## Consequences diff --git a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md index 67b24c7423..7c20a1b429 100644 --- a/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md @@ -16,13 +16,13 @@ Status: implemented `SessionStore.appendOutOfBand()`、`OutOfBandSessionEventMap` 和 `OutOfBandSessionEventType` 均不再存在。拥有纯日志事件的插件通过 `Session` 追加该事件;当操作承诺持久性时,插件会显式等待 `ctx.sessions.flush(session)`。系统不会仅为获得该检查点而打开轮次。 -核心会话不变量继续强制核心所属的执行关系:轮次与步骤编号、steering(中途引导)、助手、工具、待办和请求头事件的封闭,以及同一步骤内的工具调用/结果配对。核心允许可合并扩展事件位于轮次之间,因为只有声明它们的插件知道这些事件受执行作用域约束,还是可以独立存在。插件的不变量配套组件仍负责其自身的事件关系。 +核心允许可合并扩展事件位于轮次之间,因为只有声明它们的插件知道这些事件受执行作用域约束,还是可以独立存在。 标题服务会在完成既有的服务状态、修订、取消和活跃会话检查后,直接追加 `session/title`。随附模型辅助函数会在发起调用前追加其字面量 `session/title-llm-request` 记录。持久化通过有界 `session/event` 路径接纳两者,并在常规检查点与生命周期结束时排空;二者都不会仅因为位于轮次之间就强制刷写。因此,回退标题、辅助请求记录或已接受的提供方标题可以出现在 `turn/end` 之后、下一个 `turn/start` 之前。手动压缩(compaction)利用同一项轮次间能力记录 `compaction/* { turn: null }` 标记对,但会显式刷写已闭合的尝试,因为 `/compact` 承诺在放行排队中的提示词前完成持久化。 -`SessionStore.fork()` 保留独立标题和其他插件所属的纯日志记录。[精确事件 fork 决策](../feature/2026-08-18-arbitrary-seq-session-fork.zh.md)负责切点选择与开放尾部的子会话自有结束事件。 +`SessionStore.fork()` 保留独立标题和其他插件所属的纯日志记录。[精确事件 fork 参考](../../../../packages/core/session/README.zh.md)负责切点选择与开放尾部的子会话自有结束事件。 -历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。 +历史上的[通用轮次封闭决策](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)如今只适合用于解释为何曾引入合成机制。[上下文注入决策](../architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md)确立了当前语义:一个轮次表示一次模型循环执行。[排队手动压缩参考](../../../../packages/compaction/compaction/README.zh.md)将该规则应用于持久多事件标记对,并拥有其标记与接纳语义。 ## 曾考虑的替代方案 @@ -32,11 +32,11 @@ Status: implemented **将标题存储为可变会话元数据。** 这可以避免轮次间事件,却会在仅追加日志之外建立第二套变更、回放、持久化和 fork 协议。标题仍采用可回放、后写覆盖的事件。 -**要求每个插件事件向核心声明可独立存在的资格。** 这可以保留中心准入列表,但会让缺少声明意味着一种核心无法验证的执行关系。可合并扩展联合类型已经将语义所有权赋予声明事件的插件;该插件的不变量配套组件才是正确的强制位置。 +**要求每个插件事件向核心声明可独立存在的资格。** 这可以保留中心准入列表,但会让缺少声明意味着一种核心无法验证的执行关系。可合并扩展联合类型已经将语义所有权赋予声明事件的插件。 ## 验证 -核心不变量测试会接受轮次之间的未知插件事件,同时继续拒绝位于该处的内置执行事件。钩子、plan-mode、PTC mode 分发和审批的不变量配套组件会在没有开放轮次时拒绝其执行作用域事件;压缩配套组件则另外接受轮次之间平衡的 `turn: null` 手动标记对,并要求数字 owner匹配一个开放轮次。会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 +会话标题服务测试会在并发刷新、拒绝已脱离会话和接受最新修订的场景下,固定一个直接追加的回退事件。JSONL 往返测试会通过持久化生命周期排空保留追加在 `turn/end` 之后的标题;fork 测试会保留独立纯日志尾部。一个无密钥、经完整组装的 ACP(Agent Client Protocol)快照会将模型生成的标题延迟到 `turn/end` 之后,并固定一个不含合成轮次的独立提供方标题。生成的 API 和类型等价性目录不含任何已移除符号。 ## 后果 diff --git a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml b/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml deleted file mode 100644 index 8ccac77c7e..0000000000 --- a/.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-28-omit-unneeded-invariant-companions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md -/agent-note-omit-invariant-companions-without-independent-observations: - en: 1aaf29f8d757fcc8 - zh: 76d99dd71bfabbaa -/agent-note-omit-invariant-companions-without-independent-observations/problem: - en: 53d9687bdef70d91 - zh: 61d225b1cd374c28 -/agent-note-omit-invariant-companions-without-independent-observations/decision: - en: a15c7c13f167e5ac - zh: 63bcc2ef60c2f344 -/agent-note-omit-invariant-companions-without-independent-observations/decision/independent-observations-justify-publication: - en: ee3e2ff2f0565d94 - zh: 232ddf075bf3b353 -/agent-note-omit-invariant-companions-without-independent-observations/decision/omission-is-explicit-in-the-package-readme: - en: 465c523c29fd7836 - zh: 9967d7e5b17f1adf -/agent-note-omit-invariant-companions-without-independent-observations/decision/audit-result: - en: b19c08cd1f7df630 - zh: 30e872df9ee3ff0a -/agent-note-omit-invariant-companions-without-independent-observations/alternatives-considered: - en: 5908a05a2aeb3f82 - zh: 9a01748e8d8b86f4 -/agent-note-omit-invariant-companions-without-independent-observations/consequences: - en: c648b67008e676f1 - zh: c89400eae3fef89c diff --git a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml deleted file mode 100644 index 2f39ebf65f..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-03-minimal-profiles-persistent-shell-only.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-03-minimal-profiles-persistent-shell-only.md -/agent-note-minimal-profiles-expose-only-a-persistent-shell: - en: d48e78dc2040921c - zh: 70122e09d8c78e77 -/agent-note-minimal-profiles-expose-only-a-persistent-shell/problem: - en: a5d116f8ba3ed48b - zh: d60d307237a69573 -/agent-note-minimal-profiles-expose-only-a-persistent-shell/decision: - en: 95a1c2d9322ed9da - zh: 04bc770915863950 -/agent-note-minimal-profiles-expose-only-a-persistent-shell/alternatives-considered: - en: 55fc282c31b7d4e8 - zh: 6daa72fb5527fcca -/agent-note-minimal-profiles-expose-only-a-persistent-shell/consequences: - en: 4f0e22319608c3be - zh: a6393182b9452c4a diff --git a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.i18n.yaml deleted file mode 100644 index e70adc677c..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-05-base-default-file-editor.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-05-base-default-file-editor.md -/agent-note-shared-base-default-file-editor-selection: - en: 2adaf509a18a68eb - zh: 3103eec286980735 -/agent-note-shared-base-default-file-editor-selection/problem: - en: b9cf9c91689aeca7 - zh: 4fce9354bb6c2af0 -/agent-note-shared-base-default-file-editor-selection/decision: - en: 8aa4e6ecc02abdb8 - zh: 9925a29181c53e81 -/agent-note-shared-base-default-file-editor-selection/alternatives-considered: - en: 172ed2cdc1d47e20 - zh: e12bbb0576244f43 -/agent-note-shared-base-default-file-editor-selection/consequences: - en: 886dab29e1e6580f - zh: 49a7b2ceafac0c48 -/agent-note-shared-base-default-file-editor-selection/verification: - en: 7e8cd8a29f33aff3 - zh: ee6c24c9adf54998 diff --git a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml deleted file mode 100644 index 6c68b294d5..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-agent-request-freeze-evidence.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-06-agent-request-freeze-evidence.md -/agent-note-reuse-only-loop-proven-message-freezes: - en: 7ec7e2c8c1eb46bb - zh: 93a619878774a795 -/agent-note-reuse-only-loop-proven-message-freezes/problem: - en: af6aa6181efb2163 - zh: 3f1630947294db0a -/agent-note-reuse-only-loop-proven-message-freezes/decision: - en: 64e5058366d82142 - zh: 073f4e1856c58294 -/agent-note-reuse-only-loop-proven-message-freezes/measurement-evidence: - en: 731e00bb5059b1f3 - zh: 118464dc6e640431 -/agent-note-reuse-only-loop-proven-message-freezes/measurement-evidence/standard-hosted-ci-calibration: - en: 22ad93c82afc0572 - zh: 86e43487e7791148 -/agent-note-reuse-only-loop-proven-message-freezes/alternatives-considered: - en: 8a00552d7797826c - zh: 765a6b84eb663bab -/agent-note-reuse-only-loop-proven-message-freezes/consequences: - en: 5947b38f7722d4db - zh: 147b5ea3d681ebd7 diff --git a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml deleted file mode 100644 index 7943e40a05..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-07-file-content-scan.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-file-content-scan.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-07-file-content-scan.md -/agent-note-scan-file-content-without-per-array-callbacks: - en: a814034fce2407dc - zh: 7de9ec55cbbb30ab -/agent-note-scan-file-content-without-per-array-callbacks/problem: - en: cb13a0053453cf45 - zh: ce40afb7fa03f9ed -/agent-note-scan-file-content-without-per-array-callbacks/decision: - en: e3d83658869a24f7 - zh: 44dd61458a47df6c -/agent-note-scan-file-content-without-per-array-callbacks/measurement-evidence: - en: ad23fa4ee386f3cf - zh: 41e54e28d26b3d02 -/agent-note-scan-file-content-without-per-array-callbacks/alternatives-considered: - en: 0128940c217dd77c - zh: 9394e713dac9675d -/agent-note-scan-file-content-without-per-array-callbacks/consequences: - en: dace71e8857e57b1 - zh: 0ee88fdaa4e067ee diff --git a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml deleted file mode 100644 index ebd9b4005d..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-web-subagent-catalog-projections.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.md -/agent-note-web-subagent-catalogs-consume-shared-projections: - en: 90a80df3b19785a2 - zh: 8e29073af6c61213 -/agent-note-web-subagent-catalogs-consume-shared-projections/problem: - en: 21600a979400fc8a - zh: 59ba11ed5210986e -/agent-note-web-subagent-catalogs-consume-shared-projections/decision: - en: de9d755497c49716 - zh: 872a15b960e01618 -/agent-note-web-subagent-catalogs-consume-shared-projections/alternatives-considered: - en: 2a1d32d867c38abd - zh: 1f473782ade94fa5 -/agent-note-web-subagent-catalogs-consume-shared-projections/consequences: - en: 44d73213900d4e43 - zh: 2beec2c2634320e2 diff --git a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.i18n.yaml index 5a3be08ed1..f00222dc57 100644 --- a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.i18n.yaml @@ -15,5 +15,5 @@ en: 72b871064c7ae315 zh: a09e5168f8124c4c /agent-note-keep-loader-mutations-non-transactional/consequences: - en: 2b1eece19252ad80 - zh: 8c9f5faf201521e0 + en: 100f687b9d498308 + zh: 1c4bc2d989e43ec7 diff --git a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.md b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.md index 98b0b1ce0c..d37539cb3e 100644 --- a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.md +++ b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.md @@ -32,4 +32,4 @@ Two #932-specific vendor changes remain: awaited initial-file creation and force A plugin activation failure can leave the new options and a failed fiber in place. Callers that require active plugins must audit after settlement; awaiting `Loader.create()` alone does not establish activation. Automatic plugin rollback requires a separate future decision with evidence that its recovery benefit warrants the additional lifecycle implementation. -[Live-patch tests](../testing/2026-09-09-user-patch-hmr-test-delivery.md) retain controlled event delivery and native watcher coverage, while asserting failure reporting without rollback. The [terminal-release policy](../bug-fix/2026-07-31-fail-loud-releases-the-terminal.md) remains applicable to fatal errors and partial boot teardown. Web preset composition and a real CLI webhook-created model Session provide the application-level verification beyond hand-mounted plugins. +[Live-patch tests](../../../../packages/boot/app-boot/tests/user-patches.spec.ts) retain controlled event delivery and native watcher coverage, while asserting failure reporting without rollback. The [terminal-release policy](../../../../packages/boot/app-boot/README.md) remains applicable to fatal errors and partial boot teardown. Web preset composition and a real CLI webhook-created model Session provide the application-level verification beyond hand-mounted plugins. diff --git a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.zh.md b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.zh.md index 77ae1ce91a..57ad3bb687 100644 --- a/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.zh.md +++ b/.agents/notes/implemented/simplification/2026-09-09-nontransactional-loader.zh.md @@ -32,4 +32,4 @@ Fiber、Entry 和 isolate 保持上游的更新返回行为。App boot 通过现 插件激活失败可能保留新选项和失败的 fiber。要求插件处于激活状态的调用者必须在结算后检查;仅等待 `Loader.create()` 不能证明激活。自动插件回滚需要后续独立决策,并证明其恢复收益值得额外的生命周期实现。 -[实时 patch 测试](../testing/2026-09-09-user-patch-hmr-test-delivery.zh.md) 保留受控事件投递和原生监视覆盖,同时断言不回滚时的失败报告。[终端释放策略](../bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md) 仍适用于致命错误和部分启动拆卸。Web preset 组合与真实 CLI webhook 创建的模型 Session 提供手动挂载插件之外的应用级验证。 +[实时 patch 测试](../../../../packages/boot/app-boot/tests/user-patches.spec.ts) 保留受控事件投递和原生监视覆盖,同时断言不回滚时的失败报告。[终端释放策略](../../../../packages/boot/app-boot/README.zh.md) 仍适用于致命错误和部分启动拆卸。Web preset 组合与真实 CLI webhook 创建的模型 Session 提供手动挂载插件之外的应用级验证。 diff --git a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml deleted file mode 100644 index 282d674791..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-derived-workspace-recency.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-10-derived-workspace-recency.md -/agent-note-derive-workspace-recency-independently-of-manual-order: - en: 96d0de6bdc9dcac1 - zh: a50ab8a60f58a24d -/agent-note-derive-workspace-recency-independently-of-manual-order/problem: - en: 796dc4d186169b89 - zh: 058b6a8438e46238 -/agent-note-derive-workspace-recency-independently-of-manual-order/decision: - en: c598d4991271fbf4 - zh: 6454e56191922501 -/agent-note-derive-workspace-recency-independently-of-manual-order/alternatives-considered: - en: 0e74f1c4f176107a - zh: 66a66eaa811c8b11 -/agent-note-derive-workspace-recency-independently-of-manual-order/consequences: - en: f4658d8f0f3ff8d3 - zh: 68c3c8c87b9c4e31 -/agent-note-derive-workspace-recency-independently-of-manual-order/testing: - en: 82b15f31cd5b5315 - zh: 5f14bfe0d0e8ef0c diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml index fd8eb2f048..80b5f329b3 100644 --- a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.i18n.yaml @@ -15,8 +15,8 @@ en: dc8a267887d4a65c zh: 806f4a6c844b6284 /agent-note-remove-the-e2b-execution-providers/consequences: - en: ec119616577fb094 - zh: 4dafe3fd08e11b79 + en: ce197a29f22dd125 + zh: 282ca4d511475df6 /agent-note-remove-the-e2b-execution-providers/reintroduction-conditions: en: ddc5f44430a6b6d5 zh: 372f28000c98cbd6 diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md index 6ff8f51e94..a73c5849ff 100644 --- a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.md @@ -36,7 +36,7 @@ This decision removes one provider family. It changes neither PTC execution nor Custom compositions that depend on the repository's E2B packages lose that supplied backend. External adoption is unknown. Removing the integration also removes its live remote composition evidence; remaining local and delayed-provider tests protect their own contracts, not an unimplemented replacement. -The [native containment](../architecture/2026-08-28-subprocess-native-containment.md), [outbound proxy](../architecture/2026-08-27-outbound-proxy-policy.md) and [workspace-file service](../architecture/2026-09-05-workspace-files-service.md) decisions remain active. Their E2B implementation and verification inventories are retired while their process ownership, routing and file-access requirements remain intact. The portability decision is partially superseded only in its E2B realization. +The [native containment](../../../../packages/subprocess/subprocess-local/README.md), [outbound proxy](../architecture/2026-08-27-outbound-proxy-policy.md) and [workspace-file service](../architecture/2026-09-05-workspace-files-service.md) decisions remain active. Their E2B implementation and verification inventories are retired while their process ownership, routing and file-access requirements remain intact. The portability decision is partially superseded only in its E2B realization. ## Reintroduction conditions diff --git a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md index ebc592fb56..1a092a8043 100644 --- a/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md +++ b/.agents/notes/implemented/simplification/2026-09-11-remove-e2b-providers.zh.md @@ -36,7 +36,7 @@ PTC Node 约束需要独立于任意程序 stdin/stdout/stderr 的双向控制 依赖仓库 E2B 包的自定义组合失去该后端。外部采用情况未知。移除集成也会移除其真实远程组合证据;保留的本地与延迟提供方测试保护各自约定,并不验证尚未实现的替代方案。 -[原生约束](../architecture/2026-08-28-subprocess-native-containment.zh.md)、[出站代理](../architecture/2026-08-27-outbound-proxy-policy.zh.md)和[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md)决策保持有效。它们的 E2B 实现与验证清单被撤下,进程所有权、路由和文件访问要求保持完整。可移植性决策仅有 E2B 实现部分被取代。 +[原生约束](../../../../packages/subprocess/subprocess-local/README.zh.md)、[出站代理](../architecture/2026-08-27-outbound-proxy-policy.zh.md)和[工作区文件服务](../architecture/2026-09-05-workspace-files-service.zh.md)决策保持有效。它们的 E2B 实现与验证清单被撤下,进程所有权、路由和文件访问要求保持完整。可移植性决策仅有 E2B 实现部分被取代。 ## 重新引入条件 diff --git a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml deleted file mode 100644 index 2f8e4c8416..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-ralph-off-in-shipped-defaults.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-12-ralph-off-in-shipped-defaults.md -/agent-note-ship-ralph-off-in-the-default-compositions: - en: 59fe1293d618be67 - zh: 0da7f1b4447f10f1 -/agent-note-ship-ralph-off-in-the-default-compositions/problem: - en: c8c152617cca4d5d - zh: 1846b119989b9e5b -/agent-note-ship-ralph-off-in-the-default-compositions/decision: - en: 7d95a71842e74d38 - zh: fc0d360ff3acc8b5 -/agent-note-ship-ralph-off-in-the-default-compositions/alternatives-considered: - en: 4e6ed0566a2de3a8 - zh: c1e7ba6dd4047fa0 -/agent-note-ship-ralph-off-in-the-default-compositions/consequences: - en: 427242f0a8df0c66 - zh: ca3dd1bd4cd54605 -/agent-note-ship-ralph-off-in-the-default-compositions/verification: - en: 2511c9dd0da7ab60 - zh: 6d4e801f33f3d81b diff --git a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml deleted file mode 100644 index 5029c4401d..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-host-only-remote-input-validation.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.md -/agent-note-validate-remote-inputs-only-on-the-host: - en: 4434bfb455727454 - zh: 776fc3b42ce339e6 -/agent-note-validate-remote-inputs-only-on-the-host/problem: - en: 6cddd9c31c5bf1e5 - zh: f2730b6448f7fc26 -/agent-note-validate-remote-inputs-only-on-the-host/decision: - en: 6e3ef9540b02336b - zh: e8abe4ea84d95b95 -/agent-note-validate-remote-inputs-only-on-the-host/alternatives-considered: - en: 1c9ed80b25403e03 - zh: edbc8301e50cf3a1 -/agent-note-validate-remote-inputs-only-on-the-host/consequences: - en: caf3ffc95133ac40 - zh: f29864e4b636d6f7 diff --git a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml deleted file mode 100644 index a259fd41c0..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-15-model-agent-availability-and-team-targets.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.md -/agent-note-model-agent-availability-and-team-targets: - en: 7c8722f7b156cd8c - zh: aad147cf37021d82 -/agent-note-model-agent-availability-and-team-targets/problem: - en: 66cc6cea878418a6 - zh: 5df1e5508e10fc02 -/agent-note-model-agent-availability-and-team-targets/decision: - en: 044f44c456540021 - zh: 975794704c37186f -/agent-note-model-agent-availability-and-team-targets/alternatives-considered: - en: a19b8766bcff7e49 - zh: 0851603e9c8315c6 -/agent-note-model-agent-availability-and-team-targets/consequences: - en: 9b622b43322a407e - zh: ffe2e0199ba85b5e diff --git a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml deleted file mode 100644 index 3d56b5605f..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-16-subagent-catalog-membership-only.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-16-subagent-catalog-membership-only.md -/agent-note-keep-model-configuration-outside-subagent-catalogs: - en: 58a38a2045e8bd43 - zh: 1a1b02b0a93ebc9e -/agent-note-keep-model-configuration-outside-subagent-catalogs/problem: - en: d817b413b7e38939 - zh: a4a3a12df1407a34 -/agent-note-keep-model-configuration-outside-subagent-catalogs/decision: - en: e46b59047e7cbe7d - zh: 2249547faa8e6a72 -/agent-note-keep-model-configuration-outside-subagent-catalogs/alternatives-considered: - en: 415a276e8011a2ab - zh: d86739d9fbe72fb9 -/agent-note-keep-model-configuration-outside-subagent-catalogs/consequences: - en: 6849156944864ab6 - zh: b74ba42a7d5711d9 diff --git a/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.i18n.yaml new file mode 100644 index 0000000000..cf9a9b80a3 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.i18n.yaml @@ -0,0 +1,25 @@ +# Bilingual-pair consistency record for 2026-09-17-unified-subagent-activations.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.md +/agent-note-unified-subagent-activations: + en: bf9959f51e79029c + zh: 9526781af4b72f6a +/agent-note-unified-subagent-activations/problem: + en: c97919745873e705 + zh: b531e60b626c4304 +/agent-note-unified-subagent-activations/decision: + en: 86c924e5eedb919d + zh: 36dd301bdba6325d +/agent-note-unified-subagent-activations/decision/retained-decisions: + en: 670043a18f9cd32c + zh: d925485386bab362 +/agent-note-unified-subagent-activations/alternatives-considered: + en: f1fe44375cd78882 + zh: 9abe43a7aa63c1b0 +/agent-note-unified-subagent-activations/consequences: + en: d9f618345ec98d3b + zh: 7aceba46b132414e +/agent-note-unified-subagent-activations/verification: + en: ee6acf6a9ae509db + zh: e239187d787f24fd diff --git a/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.md b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.md new file mode 100644 index 0000000000..07268e4016 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.md @@ -0,0 +1,49 @@ +# Agent Note: Unified subagent activations + +Status: implemented + +English | [中文](2026-09-17-unified-subagent-activations.zh.md) + +## Problem + +Local one-shot execution and continuable residency separately owned child creation, cancellation, structured capture, results, and cleanup. Foreground and Job-backed tool paths added more ownership paths. Workflow code still needed to await a result, while external backends needed one process execution without acquiring a multi-turn protocol. + +## Decision + +`startActivation()` is the consumer entry point for every provider. A published handle exposes child identity, a result promise, and disposal of that exact activation. The caller signal cancels unpublished creation only. Local results wait for pending input and owned descendants before closing admission, so descendant replies can contribute to the final answer. Both local and external result promises settle after cleanup and notifications. Parent waits use that same completion promise; a captured execution result survives cleanup failure, which disposal reports separately. Workflow consumers already wait for cleanup before continuing, so activation ownership needs no independent result-readiness and release signals. + +Local spawn and fork providers contribute only `prepareContinuable()` data; the subagent service owns Agent creation and residency. The standalone in-process driver package is removed. Structured tools, validation, instructions, and terminal guards attach to one activation and close with it. After capture, that activation rejects further input; cold resume reconstructs an ordinary conversation without the previous schema. + +ACP, DSH SDK, Codex, and Claude Code retain their existing single-execution provider adapters. They join the same activation capacity and ownership graph without accepting follow-up messages or fabricating a local child Session. The parent-owned catalog registers every local child and parent-delivery external leaves. Local membership remains independent of result delivery so completed workflow Sessions and their descendants stay discoverable. Caller delivery leaves external membership tracking to its consumer. External entries carry `mode: 'external'` because they have no local Session to open. Creation records membership once; execution and cleanup do not update the catalog. Execution status has no required consumer and remains outside the membership record. Caller delivery returns the complete result; parent delivery queues the complete completion notice. + +The model-facing tool always returns a background child id and promises the manager's completion notice. It has no foreground switch or Job integration. Workflows select caller delivery, await the activation result, and dispose the handle before finishing; they add neither a completion notice nor initial return guidance to the parent/child exchange. Headless completion waits for its own child tree and subsequent parent turns. + +Parent completion notices include final text regardless of `send_message` availability or use. Tool availability cannot establish that the child sent its answer: it may send only progress or finish without calling the tool. An answer already sent through the tool can appear twice; accepting that duplication avoids making result delivery depend on model compliance or another delivery ledger. + +### Retained decisions + +The native Session reader rejects duplicate child membership for both local and external catalog entries. + +The [continuable residency](../feature/2026-07-28-continuable-subagent-conversations.md), [fork request prefix](../architecture/2026-08-10-fork-children-stay-one-shot.md), and [parent-owned catalog](../architecture/2026-09-01-parent-owned-subagent-catalog.md) records retain their independent rationale. The [subagent package reference](../../../../packages/subagent/subagent/README.md) owns current provider, result-delivery, and activation-capacity contracts; the corresponding [capability seam](../../archived/feature/2026-06-21-subagent-capability-seam.md), [settlement delivery](../../archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md), and [capacity](../../archived/feature/2026-09-15-continuable-activation-capacity.md) records remain historical snapshots. This decision owns the shared entry point, external participation, and caller-versus-parent result delivery. + +Released Session generations remain immutable. Historical one-shot descriptors stay readable; removing current execution paths does not justify rewriting or discarding durable history. + +## Alternatives considered + +**Keep a separate external execution projection.** It duplicates membership, restoration, and client update handling already supplied by the parent catalog. Repeated directory snapshots would also retransmit every retained result. The catalog retains only membership; results remain with their recipient. + +**Keep one-shot local execution for synchronous workflows.** Awaiting a result is a consumer requirement. A second Agent lifecycle duplicates cancellation and cleanup solely to supply a promise, which the activation can supply directly. + +**Add multi-turn support to every external backend.** This expands backend protocols, recovery, and authenticated routing without being necessary for shared ownership. The adapters retain their existing execution capability. + +**Use Jobs for all background children.** Jobs add a second work registry and cancellation authority beside activation residency and the Agent inbox. Child discovery and completion already belong to the subagent service. + +**Send every result to the parent.** A workflow already consumes and presents its child result. Another wake adds duplicate context and can disturb a parent that is awaiting the workflow tool. Delivery is an explicit consumer choice; the model-facing tool's notice remains unconditional. + +## Consequences + +One manager controls admission, cancellation, ownership, and resource release across providers. Workflow result collection remains synchronous at its API, while model delegation leaves the parent's tool step promptly. External children share accounting and durable discovery but gain no continuation or independent result archive. Restoring foreground or Job-backed local execution would require a concrete capability that result collection through an activation cannot provide. + +## Verification + +Focused tests cover local spawn/fork inheritance, structured capture and schema-free cold resume, explicit cancellation, external catalog membership, result-versus-disposal ordering, workflow collection without parent notices, and headless child-tree completion. Existing replay generations are preserved; updated recorded-session cases cover the current model-visible tool and notice behavior. diff --git a/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.zh.md b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.zh.md new file mode 100644 index 0000000000..66c0ed81f6 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-09-17-unified-subagent-activations.zh.md @@ -0,0 +1,49 @@ +# Agent Note: 统一 subagent activation + +Status: implemented + +[English](2026-09-17-unified-subagent-activations.md) | 中文 + +## 问题 + +本地一次性执行与可继续驻留分别拥有子级创建、取消、结构化捕获、结果和清理。前台与 Job 支持的工具路径又增加了所有权路径。工作流仍需等待结果,而外部后端需要一次进程执行,无需获得多轮协议。 + +## 决策 + +`startActivation()` 是所有提供方的消费者入口。已发布句柄暴露子级身份、结果 promise 和对应 activation 的资源释放。调用方信号仅取消未发布的创建。本地结果等待待处理输入与所拥有的后代结束后才关闭准入,使后代回复能够进入最终答案。本地与外部结果 promise 都在清理和通知结束后完成。父级使用同一个完成 promise 等待;清理失败不覆盖已捕获的执行结果,由 disposal 单独报告。工作流消费者本就等待清理后再继续,因此 activation 所有权不需要独立的结果就绪与释放信号。 + +本地 spawn 和 fork 提供方只贡献 `prepareContinuable()` 数据,由 subagent 服务拥有 Agent 创建与驻留。独立进程内驱动器包被移除。结构化工具、校验、指令与终止保护附着于一次 activation,并随其关闭。捕获后该 activation 拒绝继续输入;冷恢复重建普通对话,不携带此前的 schema。 + +ACP、DSH SDK、Codex 和 Claude Code 保留单次执行适配器,共享 activation 容量与所有权,不接受后续消息,也不伪造本地子 Session。父级拥有的 catalog 登记所有本地子级,以及 parent 交付的外部叶子项。本地成员关系独立于结果交付,让已完成的工作流 Session 及其后代仍可被发现。caller 交付由消费方跟踪外部成员关系。外部条目携带 `mode: 'external'`,因为没有可打开的本地 Session。创建时记录一次成员关系;执行与清理不更新目录。执行状态没有必需的消费者,不属于成员记录。caller 交付返回完整结果,parent 交付将完整完成通知加入父级队列。 + +面向模型的工具始终返回后台子级 ID,并承诺管理器的完成通知。它没有前台开关或 Job 集成。工作流选择调用方投递,等待 activation 结果并在完成前释放句柄;它们不向父子交互添加完成通知或初始返回指导。Headless 完成流程等待其自身子树与后续父级轮次。 + +父级完成通知会附带最终文本,不受 `send_message` 是否可用或是否调用影响。工具可用不能证明子级已经发送答案:它可能只汇报进展,也可能未调用工具便结束。已经通过工具发送的答案可能出现两次;接受这类重复,可以避免让结果投递依赖模型遵循指令或额外的投递台账。 + +### 保留的决策 + +原生 Session 读取器对本地与外部目录条目都拒绝重复的子级成员记录。 + +[可继续驻留](../feature/2026-07-28-continuable-subagent-conversations.zh.md)、[fork 请求前缀](../architecture/2026-08-10-fork-children-stay-one-shot.zh.md)与[父级拥有的目录](../architecture/2026-09-01-parent-owned-subagent-catalog.zh.md)保留各自独立理由。具名提供方、结果投递与 activation 容量的当前约定见 [subagent 包文档](../../../../packages/subagent/subagent/README.zh.md);相应的[能力 seam](../../archived/feature/2026-06-21-subagent-capability-seam.md)、[结算投递](../../archived/feature/2026-08-06-manager-owned-subagent-settlement-delivery.md)与[容量](../../archived/feature/2026-09-15-continuable-activation-capacity.md)记录作为历史快照保留。本决策拥有共享入口、外部执行参与以及调用方与父级之间的结果投递选择。 + +已发布 Session 世代保持不可变。历史一次性描述符仍可读取;移除当前执行路径不构成改写或丢弃持久历史的理由。 + +## 考虑过的替代方案 + +**保留独立的外部执行投影。** 它重复父级 catalog 已提供的成员登记、恢复与客户端更新,每次目录快照还会重传全部结果。catalog 仅保留成员关系,完整结果由接收方负责。 + +**为同步工作流保留本地一次性执行。** 等待结果是消费者需求。仅为提供 promise 而维护第二套 Agent 生命周期会重复取消与清理;activation 可以直接提供该 promise。 + +**为所有外部后端增加多轮支持。** 这会扩展后端协议、恢复和经过授权的路由,却不是共享所有权的必要条件。适配器保留现有执行能力。 + +**让所有后台子级使用 Jobs。** Jobs 在 activation 驻留和 Agent 收件箱之外增加第二套工作注册表与取消权限。子级发现和完成已属于 subagent 服务。 + +**把每个结果都发送给父级。** 工作流已经消费并展示子级结果;再次唤醒会重复上下文,也可能干扰正在等待工作流工具的父级。投递是显式消费者选择,面向模型工具的通知仍无条件提供。 + +## 后果 + +同一个管理器控制所有提供方的准入、取消、所有权和资源释放。工作流 API 仍同步收集结果,模型委派则及时结束父级工具 step。外部子级获得共享计数与持久发现,但不获得继续执行能力或独立结果档案。恢复前台或 Job 支持的本地执行,需要 activation 结果收集无法提供的具体能力。 + +## 验证 + +聚焦测试覆盖本地 spawn/fork 继承、结构化捕获与无 schema 冷恢复、显式取消、外部目录成员关系、结果与释放顺序、不发父级通知的工作流收集,以及 headless 子树完成。既有回放世代得到保留;更新的录制会话用例覆盖当前面向模型的工具与通知行为。 diff --git a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.i18n.yaml b/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.i18n.yaml deleted file mode 100644 index b97e9bb8dd..0000000000 --- a/.agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-deepseek-messages-only.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-19-deepseek-messages-only.md -/agent-note-one-messages-transport-for-the-official-deepseek-route: - en: 7f7651d2e098b62d - zh: 0b12845f48f7ae93 -/agent-note-one-messages-transport-for-the-official-deepseek-route/problem: - en: 050718df19ce3089 - zh: 676838e85ba91e25 -/agent-note-one-messages-transport-for-the-official-deepseek-route/decision: - en: 33738d00a2d9564b - zh: 00101971b1a18d64 -/agent-note-one-messages-transport-for-the-official-deepseek-route/alternatives-considered: - en: f92695788ada65aa - zh: b885b30ba9f846f8 -/agent-note-one-messages-transport-for-the-official-deepseek-route/consequences: - en: 23f7d09fb7cf709c - zh: 1db0bf29cdc38871 diff --git a/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.i18n.yaml b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.i18n.yaml new file mode 100644 index 0000000000..ed1a46d891 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-native-or-ptc-presentation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.md +/agent-note-one-tool-invocation-form-per-agent: + en: d45e87191f6e7667 + zh: 4350b7a9ff83b310 +/agent-note-one-tool-invocation-form-per-agent/problem: + en: 55d25e331b37b260 + zh: d1b091a38d1b106d +/agent-note-one-tool-invocation-form-per-agent/decision: + en: 42e5b09fbab63e84 + zh: 883ab2072a60a138 +/agent-note-one-tool-invocation-form-per-agent/alternatives-considered: + en: 62b0c9c4470b241d + zh: 4cf40e5dc6eebd63 +/agent-note-one-tool-invocation-form-per-agent/consequences: + en: a08a45bec640e3f6 + zh: 9f61a47d8133f3ee diff --git a/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.md b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.md new file mode 100644 index 0000000000..97068af298 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.md @@ -0,0 +1,25 @@ +# Agent Note: One tool invocation form per agent + +Status: implemented + +English | [中文](2026-10-05-native-or-ptc-presentation.zh.md) + +## Problem + +Mixed tool presentation carries direct function schemas and a program SDK for the same capabilities. It adds schema-ordering, admission, configuration, and test combinations without a separate execution capability. The [PTC decision](../feature/2026-06-15-ptc.md) still owns the registry, runtime, and nested-call rules. + +## Decision + +Each agent selects `native` or `ptc`. Native presentation exposes visible capability schemas. PTC presentation exposes only `run_code` and the generated SDK. The registry rejects `both` in configuration. Presets can select different supported modes in one application. + +## Alternatives considered + +**Keep mixed presentation.** This lets one agent choose direct calls or programs for each step, but duplicates the advertised capability information and preserves a third admission case. Separate native and PTC agents provide the supported choices. + +**Silently map mixed presentation to another mode.** Either mapping changes how existing programs or direct calls execute. Explicit configuration rejection and the [upgrade guide](../../../../docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.md) make that choice visible. + +## Consequences + +An agent cannot alternate between direct end-tool calls and `run_code` through one mixed presentation. Reintroduction requires a concrete need for that behavior and evidence that it warrants the additional interface. + +Tests for shared policy, cancellation, nested context, and multimodal results use native or PTC setups. Older Session generations and explicitly retained recordings keep their original bytes and remain readable. The removal changes accepted configuration, not the plugin-author registration API or the guarded execution pipeline. diff --git a/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.zh.md b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.zh.md new file mode 100644 index 0000000000..f6950cb50b --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-05-native-or-ptc-presentation.zh.md @@ -0,0 +1,25 @@ +# Agent Note: 每个 agent 使用一种工具调用形式 + +Status: implemented + +[English](2026-10-05-native-or-ptc-presentation.md) | 中文 + +## Problem + +混合工具呈现为同一组能力同时携带直接函数 schema 和程序 SDK。它增加了 schema 排序、准入、配置和测试组合,却没有提供独立的执行能力。[PTC 决策](../feature/2026-06-15-ptc.zh.md) 仍负责注册表、运行时和嵌套调用规则。 + +## Decision + +每个 agent 选择 `native` 或 `ptc`。原生呈现公开可见能力的 schema。PTC 呈现仅公开 `run_code` 和生成的 SDK。注册表拒绝配置中的 `both`。同一应用中的不同 preset 可以选择不同的受支持模式。 + +## Alternatives considered + +**保留混合呈现。** 这允许同一个 agent 在每一步选择直接调用或程序,但会重复通告能力信息,并保留第三种准入情况。独立的原生与 PTC agent 提供受支持的选择。 + +**将混合呈现静默映射到另一种模式。** 任一映射都会改变现有程序或直接调用的执行方式。明确拒绝配置,并提供[升级指南](../../../../docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.zh.md),可以让这一选择可见。 + +## Consequences + +agent 不能通过一种混合呈现在直接调用具体工具与 `run_code` 之间切换。重新引入该模式,需要存在具体的行为需求,并有证据表明值得增加这种接口。 + +共享策略、取消、嵌套上下文和多模态结果测试使用原生或 PTC 配置。较早的 Session 代际及显式保留的记录保持原始字节,并且仍可读取。移除该模式改变的是受支持的配置,而非插件作者使用的注册 API 或受控执行流水线。 diff --git a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml b/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml deleted file mode 100644 index f2eedbf389..0000000000 --- a/.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.i18n.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-19-acp-snapshot-tests.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md -/agent-note-acp-snapshot-tests-record-once-replay-deterministic: - en: 2f19b76966d5cf54 - zh: cbaed521d650e079 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/problem: - en: d1738d268738633e - zh: ce62a5b0ff65ce8c -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision: - en: a9e85418ad5d2bbd - zh: 8e86b30a2a5bd049 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/the-fixture-projects-the-persisted-session-jsonl: - en: bdd1333366edfc30 - zh: ed8a0fbab516a62b -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/replay-derives-the-model-script-from-the-log: - en: cb71313f2b14f855 - zh: f85809cfd19ce6af -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/the-in-memory-replay-entry-honors-the-full-llm-contract: - en: 5d750b82fd88597c - zh: 22dc6dec0d638449 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/positional-replay-one-in-flight-stream: - en: 9ecd0c3efce1b2a1 - zh: 51612956e0c4fc85 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/recording-harvests-the-log-keyless-replay-needs-a-providerless-config: - en: 17239981275e59c8 - zh: 8f7fc2d9e6abd3ee -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/two-outputs-normalize-then-compare: - en: ee891a839a0c05e9 - zh: 677259307e4357fa -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/isolation-normalization-now-sandbox-later: - en: ce58ef2e3180d0d3 - zh: 1e9354614e4d7219 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/the-replay-plugin-is-its-own-package: - en: 6c3f938c57f4458b - zh: f64bb14ffb80ab90 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/decision/two-subcommands-replay-in-the-default-gate: - en: a50cb4cd2be177f7 - zh: a29f45aafb83fa6c -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/alternatives-considered: - en: c4e83a568e8f483e - zh: eff19f1872565ff6 -/agent-note-acp-snapshot-tests-record-once-replay-deterministic/consequences: - en: ea210a6a43707447 - zh: 8c1e2a474d595b96 diff --git a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.i18n.yaml b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.i18n.yaml index 5958148df6..52e964bf01 100644 --- a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.i18n.yaml @@ -30,8 +30,8 @@ en: fbbcc9a926dfc943 zh: 506acdb4e034574b /agent-note-real-api-e2e-in-ci-against-the-external-deepseek-api/decision/scope-runtime-shape: - en: c59c441547da2a4d - zh: b08d50e7cfd40cd5 + en: 31cf2f272a42f476 + zh: b3f879aa0fc47bdb /agent-note-real-api-e2e-in-ci-against-the-external-deepseek-api/security: en: 1fd3925ab26c11d4 zh: 0d7103fd0b1afa89 diff --git a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md index bdb20b3d54..8a63c4ad63 100644 --- a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md +++ b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md @@ -54,7 +54,7 @@ The repo secret is named `DEEPSEEK_API_KEY_EXTERNAL`; it is mapped to the `DEEPS ### Scope, runtime shape -The job runs only `test:e2e` on Node 24; keyless gates and version compatibility belong to the main CI workflow. Tests run unbuilt through the workspace paths map with a bounded configurable worker pool, per-test retries, and a job timeout. The [superseded-CI cancellation policy](../process/2026-09-09-cancel-superseded-ci.md) cancels older runs in the same workflow/ref group across PR, push, schedule, and manual triggers; a post-merge or nightly trigger does not guarantee completion. +The job runs only `test:e2e` on Node 24; keyless gates and version compatibility belong to the main CI workflow. Tests run unbuilt through the workspace paths map with a bounded configurable worker pool, per-test retries, and a job timeout. The [superseded-CI cancellation policy](../../../../.github/workflows/e2e.yml) cancels older runs in the same workflow/ref group across PR, push, schedule, and manual triggers; a post-merge or nightly trigger does not guarantee completion. The DeepSeek native `web_search` probe is registered but skipped. The live Anthropic-compatible endpoint can return a successful response without structured source blocks, so its positive-source assertion is not a reliable merge signal; unit coverage still pins response parsing, but CI does not prove the live source-block wire shape. diff --git a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md index b95f7df62e..e18a5caefc 100644 --- a/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md +++ b/.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md @@ -54,7 +54,7 @@ repo secret 命名为 `DEEPSEEK_API_KEY_EXTERNAL`;映射到适配器和测试 ### 范围与运行时形态 -job 仅在 Node 24 上运行 `test:e2e`;无密钥门禁和版本兼容性属于主 CI 工作流。测试通过 workspace paths 映射以未构建形式运行,使用有界的可配置 worker 池、逐测试重试和 job 超时。[被取代 CI 的取消策略](../process/2026-09-09-cancel-superseded-ci.zh.md)会在 PR、push、schedule 和手动触发之间取消同一工作流/引用组内的旧运行;合并后或每夜触发并不保证完成。 +job 仅在 Node 24 上运行 `test:e2e`;无密钥门禁和版本兼容性属于主 CI 工作流。测试通过 workspace paths 映射以未构建形式运行,使用有界的可配置 worker 池、逐测试重试和 job 超时。[被取代 CI 的取消策略](../../../../.github/workflows/e2e.yml)会在 PR、push、schedule 和手动触发之间取消同一工作流/引用组内的旧运行;合并后或每夜触发并不保证完成。 DeepSeek 原生 `web_search` 探测已注册但会跳过。线上 Anthropic 兼容端点可能返回成功响应却没有结构化来源块,因此对来源存在性的正向断言不是可靠的合并信号;单元测试仍会锁定响应解析行为,但 CI 不会验证线上端点返回的来源块协议格式(wire format)。 diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml index 432ef80b37..3554853dcc 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.i18n.yaml @@ -6,8 +6,8 @@ en: acbf72f6a3c5bea9 zh: 4d655027ac2b78db /agent-note-keyless-browser-e2e-lane-for-the-web-gui/problem: - en: 547a3f60ac28095e - zh: 85a9d996257f0273 + en: 63a2f51853b2c530 + zh: 2babef0ecce89b21 /agent-note-keyless-browser-e2e-lane-for-the-web-gui/decision: en: c2f86db5f0b6b5a4 zh: dd07b529cc65e7d2 @@ -27,8 +27,8 @@ en: c529437d4fbaeeb3 zh: fe13551e56cba7df /agent-note-keyless-browser-e2e-lane-for-the-web-gui/decision/ci-stance: - en: 23b625d86ba7f5e2 - zh: 88f33b233fca0a82 + en: a4ed258cd19db1c0 + zh: 913e079a6250d7d7 /agent-note-keyless-browser-e2e-lane-for-the-web-gui/prior-art: en: 7472e212043721dd zh: 61dd9f5a8e12730b diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md index eb79591e7b..cbbcee49c7 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md @@ -6,7 +6,7 @@ English | [中文](2026-07-24-web-gui-browser-e2e-lane.zh.md) ## Problem -The web GUI ships as a real assembled chain — chromium page → client plugin bundles → HTTP unary RPC + two SSE streams → `toFetchHandler`/apiproxy → the host agent loop, tools, and JSONL persistence — and no test exercised that chain keylessly and deterministically. The [GUI testing system](../process/2026-07-20-gui-testing-system.md) covers tier 1 (wire isomorphism in node), tier 2 (object-layer state machines), and tier-3 smokes, but the former keyless smoke drove a standalone client fixture — no host, no wire, no agent loop — while the full-chain smoke needed `DEEPSEEK_API_KEY` and a live model, so it was nondeterministic and self-skipped in keyless CI. The snapshot philosophy of [docs/testing.md](../../../../docs/testing.md) — record once with a key, replay forever keyless, refresh on format churn — already covered the ACP, headless `stream-json`, and TUI transcript surfaces; the web surface was the one assembled product shape without it. The gap is exactly where the two confirmed GUI P0s hid: the wire carriage chain the standalone fixture short-circuited. +The web GUI ships as a real assembled chain — chromium page → client plugin bundles → HTTP unary RPC + two SSE streams → `toFetchHandler`/apiproxy → the host agent loop, tools, and JSONL persistence — and no test exercised that chain keylessly and deterministically. The [GUI testing system](../../../../docs/testing.md) covers tier 1 (wire isomorphism in node), tier 2 (object-layer state machines), and tier-3 smokes, but the former keyless smoke drove a standalone client fixture — no host, no wire, no agent loop — while the full-chain smoke needed `DEEPSEEK_API_KEY` and a live model, so it was nondeterministic and self-skipped in keyless CI. The snapshot philosophy of [docs/testing.md](../../../../docs/testing.md) — record once with a key, replay forever keyless, refresh on format churn — already covered the ACP, headless `stream-json`, and TUI transcript surfaces; the web surface was the one assembled product shape without it. The gap is exactly where the two confirmed GUI P0s hid: the wire carriage chain the standalone fixture short-circuited. ## Decision @@ -50,7 +50,7 @@ The lane covers three behavior families. Live-turn scenarios pin ordinary tool e ### CI stance -The lane is a required compare-only gate for Linux pull requests under the [browser snapshot CI decision](2026-07-30-web-browser-snapshot-ci-gate.md). The `node 24 / snapshots and artifacts` consumer job owns the [single Linux build](../../archived/process/2026-07-30-independent-ci-consumer-build.md), installs the lockfile-selected Chromium and WebKit, restores their OS-and-lockfile-keyed cache, and runs the lane with `DSH_SNAPSHOT=replay`. This is an intentional plane split: the host and specs use the [tsx source-launch contract](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md), while the browser consumes `apps/web/dist` and package `lib/client.js` artifacts, so the gate depends on `built-package-invariants` for those client artifacts. The self-hosted default-branch Linux serial standby runs the same gate; there is no hosted Linux serial producer of the browser cache consumed by pull requests, and the persistent self-hosted pool needs no hosted cache. CI never records or refreshes goldens. Scenarios remain POSIX-oriented and stay outside the Windows and macOS matrices. +The lane is a required compare-only gate for Linux pull requests under the [browser snapshot CI reference](../../../../docs/testing.md). The `node 24 / snapshots and artifacts` consumer job owns the [single Linux build](../../archived/process/2026-07-30-independent-ci-consumer-build.md), installs the lockfile-selected Chromium and WebKit, restores their OS-and-lockfile-keyed cache, and runs the lane with `DSH_SNAPSHOT=replay`. This is an intentional plane split: the host and specs use the [tsx source-launch contract](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md), while the browser consumes `apps/web/dist` and package `lib/client.js` artifacts, so the gate depends on `build` for those client artifacts. The self-hosted default-branch Linux serial standby runs the same gate; there is no hosted Linux serial producer of the browser cache consumed by pull requests, and the persistent self-hosted pool needs no hosted cache. CI never records or refreshes goldens. Scenarios remain POSIX-oriented and stay outside the Windows and macOS matrices. High-cardinality performance diagnostics use the separate opt-in `apps/web/tests/**/*.perf.ts` inventory selected only by `vitest.web.perf.config.ts`. The isolated `complex-history.perf.ts` cases reuse the real scaffold: the workspace case seeds 1,000 compact sessions plus one 500-turn history containing 500 tool calls, exhausts and remounts that history in Chat, and reports Chromium main-thread, DOM, listener, heap, paging, search, and Trajectory measurements. Two continuation cases seed the same long history but compare the default 24-turn Chat window with all 500 turns expanded before each continues eight identical turns through the real composer, agent loop, SSE wire, tools, and persistence; two turns execute a real `bash` call and assert its durable result, while the final turn fills an 8,232-character mixed-language prompt and replays 120 paced text deltas. A separate soak case starts from a blank session, drives 100 consecutive real composer turns with a `bash` call and result every tenth turn, forces GC every ten turns, and reports ten-turn latency windows plus retained browser state. It then submits a 101st text-only turn with a trusted browser click and measures browser-clock send-to-transcript-DOM and send-to-post-paint latency, excluding the composer's draft mirrors, separately from full-turn completion. Per-turn diagnostics cover composer fill, click-to-user-echo, click-to-first-chunk, completion, browser mutations, persisted chunks, and tool events; the synthetic replay model has enough context capacity to keep fixture cardinality stable instead of consuming scripted calls through compaction. Structural assertions pin the intended load, stream, and tool shapes, but timing remains threshold-free because machine speed is not a correctness contract. The required `vitest.web.config.ts` inventory remains limited to `*.e2e.ts` and `*.snapshot.ts`, so neither `test:web:built` nor its CI gate collects performance cases. diff --git a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md index f715f4ca93..f9c522a559 100644 --- a/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md +++ b/.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md @@ -6,7 +6,7 @@ Status: implemented ## 问题 -Web GUI 以一条真实组装链交付——chromium 页面 → client 插件 bundle → HTTP 单次 RPC + 两条 SSE(Server-Sent Events)流 → `toFetchHandler`/apiproxy → host 端的 agent loop(智能体循环)、工具与 JSONL 持久化——却没有任何测试无密钥且确定性地检验这条链。[GUI 测试体系](../process/2026-07-20-gui-testing-system.zh.md)覆盖第 1 层(Node 中的协议同构)、第 2 层(对象层状态机)与第 3 层冒烟测试,但原有无密钥冒烟驱动的是独立 Client fixture——没有 host、没有 wire、没有 agent loop——而全链路冒烟需要 `DEEPSEEK_API_KEY` 和真实模型,因此不确定、在无密钥 CI 中自行跳过。[docs/testing.md](../../../../docs/testing.zh.md) 的快照哲学——带密钥录制一次、永久无密钥回放、格式变动时刷新——已覆盖 ACP(Agent Client Protocol)、headless `stream-json` 与 TUI 三个 transcript(文本记录)表面;web 表面是唯一没有这层保障的组装形态。而缺口恰恰是两起已实证 GUI P0 藏身之处:独立 fixture 短路掉的 wire 承载链。 +Web GUI 以一条真实组装链交付——chromium 页面 → client 插件 bundle → HTTP 单次 RPC + 两条 SSE(Server-Sent Events)流 → `toFetchHandler`/apiproxy → host 端的 agent loop(智能体循环)、工具与 JSONL 持久化——却没有任何测试无密钥且确定性地检验这条链。[GUI 测试体系](../../../../docs/testing.zh.md)覆盖第 1 层(Node 中的协议同构)、第 2 层(对象层状态机)与第 3 层冒烟测试,但原有无密钥冒烟驱动的是独立 Client fixture——没有 host、没有 wire、没有 agent loop——而全链路冒烟需要 `DEEPSEEK_API_KEY` 和真实模型,因此不确定、在无密钥 CI 中自行跳过。[docs/testing.md](../../../../docs/testing.zh.md) 的快照哲学——带密钥录制一次、永久无密钥回放、格式变动时刷新——已覆盖 ACP(Agent Client Protocol)、headless `stream-json` 与 TUI 三个 transcript(文本记录)表面;web 表面是唯一没有这层保障的组装形态。而缺口恰恰是两起已实证 GUI P0 藏身之处:独立 fixture 短路掉的 wire 承载链。 ## 决策 @@ -50,7 +50,7 @@ Web GUI 以一条真实组装链交付——chromium 页面 → client 插件 bu ### CI 立场 -根据[浏览器快照 CI 决策](2026-07-30-web-browser-snapshot-ci-gate.zh.md),该车道是 Linux 拉取请求必需的只比较门禁。`node 24 / snapshots and artifacts` 消费方任务在[消费方独立构建](../../archived/process/2026-07-30-independent-ci-consumer-build.md)中负责唯一一次 Linux 构建,安装锁文件选定的 Chromium 和 WebKit,恢复以操作系统和锁文件为键的缓存,并用 `DSH_SNAPSHOT=replay` 运行该车道。这是有意的平面切分:host 与 spec 使用 [tsx 源码启动约定](../architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md),浏览器则消费 `apps/web/dist` 和包的 `lib/client.js` 产物,因此门禁依赖 `built-package-invariants` 提供这些客户端产物。自托管的默认分支 Linux 串行热备运行同一门禁;不存在托管 Linux 串行生产者生成供 PR 消费的浏览器缓存,持久化自托管池则不需要托管侧缓存。CI 从不录制或刷新预期输出。场景仍面向 POSIX,并继续置于 Windows 和 macOS 矩阵之外。 +根据[浏览器快照 CI 说明](../../../../docs/testing.zh.md),该车道是 Linux 拉取请求必需的只比较门禁。`node 24 / snapshots and artifacts` 消费方任务在[消费方独立构建](../../archived/process/2026-07-30-independent-ci-consumer-build.md)中负责唯一一次 Linux 构建,安装锁文件选定的 Chromium 和 WebKit,恢复以操作系统和锁文件为键的缓存,并用 `DSH_SNAPSHOT=replay` 运行该车道。这是有意的平面切分:host 与 spec 使用 [tsx 源码启动约定](../architecture/2026-07-29-dsh-source-launch-tsx-esm.zh.md),浏览器则消费 `apps/web/dist` 和包的 `lib/client.js` 产物,因此门禁依赖 `build` 提供这些客户端产物。自托管的默认分支 Linux 串行热备运行同一门禁;不存在托管 Linux 串行生产者生成供 PR 消费的浏览器缓存,持久化自托管池则不需要托管侧缓存。CI 从不录制或刷新预期输出。场景仍面向 POSIX,并继续置于 Windows 和 macOS 矩阵之外。 高基数性能诊断使用单独按需启用的 `apps/web/tests/**/*.perf.ts` 清单,并且只由 `vitest.web.perf.config.ts` 选中。`complex-history.perf.ts` 的隔离用例复用真实 scaffold:工作区用例播种 1,000 个紧凑会话以及一份包含 500 次工具调用的 500 轮次历史,在 Chat 中穷尽并重新挂载该历史,并报告 Chromium 主线程、DOM、监听器、堆内存、分页、搜索和 Trajectory 测量结果。两个续聊用例播种同一份长历史,但比较默认的 24 轮次 Chat 窗口与展开全部 500 轮次的状态,然后各自通过真实输入框、agent loop、SSE wire、工具和持久化继续进行 8 个相同轮次;其中两轮执行真实 `bash` 调用并断言其持久化结果,最后一轮则填入一条包含 8,232 个字符的混合语言提示词,并回放 120 个带节奏的文本增量。一个单独的 soak 用例从空白会话开始,通过真实输入框连续驱动 100 轮,每第 10 轮执行一次 `bash` 调用并产生结果,每 10 轮强制执行一次 GC,并报告每 10 轮的延迟窗口及保留的浏览器状态。随后它通过受信任的浏览器点击提交第 101 个纯文本轮次,并使用浏览器时钟分别测量发送到 transcript DOM 和发送到绘制后的延迟,排除输入框的草稿镜像,并与完整轮次完成时间分开。逐轮诊断涵盖输入框填入、点击到用户消息回显、点击到首个分片、完成、浏览器变更、持久化分片和工具事件;合成回放模型拥有足够的上下文容量,可使 fixture 基数保持稳定,而不会因压缩(compaction)消耗脚本化调用。结构性断言钉住预期的负载、流和工具形状,但时间仍不设阈值,因为机器速度不属于正确性约定。必需的 `vitest.web.config.ts` 清单仍仅限 `*.e2e.ts` 和 `*.snapshot.ts`,因此 `test:web:built` 及其 CI 门禁都不会收集性能用例。 diff --git a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml b/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml deleted file mode 100644 index 8a40db5638..0000000000 --- a/.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-30-web-browser-snapshot-ci-gate.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md -/agent-note-required-ci-gate-for-web-browser-expected-outputs: - en: 246ce605a87c0592 - zh: 37b3fb230abcb280 -/agent-note-required-ci-gate-for-web-browser-expected-outputs/problem: - en: e04a5ecbd6d6a39e - zh: 049ce77a1a679daa -/agent-note-required-ci-gate-for-web-browser-expected-outputs/decision: - en: 915efba11d7ece34 - zh: 540495b04ef9a305 -/agent-note-required-ci-gate-for-web-browser-expected-outputs/alternatives-considered: - en: 145e763735dd33b1 - zh: 4ea34838629cd3bf -/agent-note-required-ci-gate-for-web-browser-expected-outputs/consequences: - en: d4c6e69ad04c5748 - zh: 5f3034df8f13b106 diff --git a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml index be0529a4d9..4f8b351852 100644 --- a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.i18n.yaml @@ -21,8 +21,8 @@ en: 0f98f5b5f78b5603 zh: 95ee8bafa66d6b1b /agent-note-installed-wheel-python-runtime-pull-request-validation/decision/required-targets: - en: 4eafb728b9b3d596 - zh: e127fdfe44659767 + en: 7a33cde000508475 + zh: 142fb5ec6b59a280 /agent-note-installed-wheel-python-runtime-pull-request-validation/existing-decisions-and-supersession: en: 04aa439d4e607324 zh: f4c1d24b6a418cc4 diff --git a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md index a23f7ea1b0..dfa71a9787 100644 --- a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md +++ b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.md @@ -30,7 +30,7 @@ Fork and Dependabot pull requests never receive the repository secret. Their nat ### Required targets -The pull-request `python-runtime` job calls the reusable builder for Linux x64 and Windows x64; master pushes select Linux arm64 and both macOS architectures under the [master-only platform policy](../process/2026-09-06-master-only-platform-ci.md). Its aggregate result remains a dependency of `all checks passed`, so a failed, cancelled, or missing native carrier blocks the required verdict. The [sdk-runtime README](../../../../python/sdk-runtime/README.md) owns the Windows target and its PowerShell-specific minimal snapshot. +The pull-request `python-runtime` job calls the reusable builder for Linux x64 and Windows x64; master pushes select Linux arm64 and both macOS architectures under the [master-only platform policy](../../../../.github/workflows/ci-master.yml). Its aggregate result remains a dependency of `all checks passed`, so a failed, cancelled, or missing native carrier blocks the required verdict. The [sdk-runtime README](../../../../python/sdk-runtime/README.md) owns the Windows target and its PowerShell-specific minimal snapshot. ## Existing decisions and supersession diff --git a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md index 1751430445..fe2e6b6ece 100644 --- a/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md +++ b/.agents/notes/implemented/testing/2026-08-23-installed-python-wheel-black-box-ci.zh.md @@ -30,7 +30,7 @@ Fork 与 Dependabot 拉取请求永远不会获得仓库密钥。它们的原生 ### 必需目标 -拉取请求的 `python-runtime` job 针对 Linux x64 与 Windows x64 调用可复用构建器;master 推送根据[仅 master 平台策略](../process/2026-09-06-master-only-platform-ci.zh.md)选择 Linux arm64 与两种 macOS 架构。其聚合结果仍是 `all checks passed` 的依赖项,因此任一原生载体失败、取消或缺失都会阻止必需判定通过。[sdk-runtime README](../../../../python/sdk-runtime/README.zh.md) 负责 Windows 目标及其 PowerShell 专属极简快照。 +拉取请求的 `python-runtime` job 针对 Linux x64 与 Windows x64 调用可复用构建器;master 推送根据[仅 master 平台策略](../../../../.github/workflows/ci-master.yml)选择 Linux arm64 与两种 macOS 架构。其聚合结果仍是 `all checks passed` 的依赖项,因此任一原生载体失败、取消或缺失都会阻止必需判定通过。[sdk-runtime README](../../../../python/sdk-runtime/README.zh.md) 负责 Windows 目标及其 PowerShell 专属极简快照。 ## Existing decisions and supersession diff --git a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.i18n.yaml b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.i18n.yaml index a82fdb832c..f0943aa8a0 100644 --- a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.i18n.yaml @@ -9,8 +9,8 @@ en: ec6f331737be909e zh: 5faee4c81cf2314f /agent-note-session-log-snapshot-corpus/decision: - en: 1611b352461c63c5 - zh: f32a5229bac3264a + en: 2cee77ed3a9bb339 + zh: be2cc1724e1fc13a /agent-note-session-log-snapshot-corpus/alternatives-considered: en: 76db318c1c2dad2d zh: b868b7c9b29c1e00 diff --git a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.md b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.md index a9001c2eab..47d2f8aae4 100644 --- a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.md +++ b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.md @@ -14,7 +14,7 @@ The term snapshot also covers unrelated ARIA, geometry, generator, and package-u Reserve the top-level `snapshots/` tree and `*.snapshot.ts` suffix for scenarios that own or explicitly reference recorded session JSONL. Each process-level scenario launches a shipped profile through `dsh`; a small adapter controls headless, SDK, ACP, or Web behavior without becoming another application entrypoint. A declarative `snapshot.yml` holds only profile, patch, lifecycle-control, platform, header-pin, and workspace facts that the completed session cannot express. -This decision supersedes the ACP-specific placement and controller ownership in the [record-once/replay-deterministic decision](2026-06-19-acp-snapshot-tests.md), while that note remains authoritative for session-log replay, exceptional overrides, normalization, and ACP transcript comparison. +This decision supersedes the ACP-specific placement and controller ownership in the [record-once/replay-deterministic reference](../../../../packages/test-support/session-snapshot/README.md), while that note remains authoritative for session-log replay, exceptional overrides, normalization, and ACP transcript comparison. The recorded session remains the primary input and, for current-generation scenarios, the expected output. Human-originated messages drive the selected public interface, recorded assistant chunks drive deterministic model replay, and the normalized persisted result must equal the fixture. Parent and child sessions share one typed redaction map. Committed fixtures contain relationship-preserving identity tokens and replace request system prompts and tool schemas with tokens; each distinct header class retains one explicit sidecar owner. diff --git a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.zh.md b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.zh.md index d02c798af2..d344988f61 100644 --- a/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.zh.md +++ b/.agents/notes/implemented/testing/2026-08-24-session-log-snapshot-corpus.zh.md @@ -14,7 +14,7 @@ Status: implemented 顶层 `snapshots/` 树和 `*.snapshot.ts` 后缀只用于拥有或显式引用录制会话 JSONL 的场景。每个进程级场景都通过 `dsh` 启动一个随附 profile;小型适配器控制 headless、SDK、ACP 或 Web 行为,但不成为另一个应用入口。声明式 `snapshot.yml` 只保存已完成会话无法表达的 profile、patch、生命周期控制、平台、header pin 和 workspace 事实。 -本决策取代[一次录制/确定性回放决策](2026-06-19-acp-snapshot-tests.zh.md)中 ACP 专属的放置位置与控制器所有权;后者继续负责会话日志回放、例外 override、规范化和 ACP transcript 比较。 +本决策取代[一次录制/确定性回放说明](../../../../packages/test-support/session-snapshot/README.zh.md)中 ACP 专属的放置位置与控制器所有权;后者继续负责会话日志回放、例外 override、规范化和 ACP transcript 比较。 录制会话仍是主要输入,并在当前代际场景中同时作为预期输出。来自用户的消息驱动所选公开接口,录制的 assistant chunk 驱动确定性模型回放,规范化后的持久化结果必须等于 fixture。父会话和子会话共享同一类型化脱敏映射。提交的 fixture 使用保留关系的身份 token,并将请求 system prompt 和工具 schema 替换为 token;每个不同 header 类仍保留一个显式 sidecar 所有者。 diff --git a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.i18n.yaml b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.i18n.yaml index fa8f7c9390..d03a5ff8c5 100644 --- a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.i18n.yaml @@ -9,8 +9,8 @@ en: 00ba1f66195351e2 zh: a738b81adceda351 /agent-note-required-ci-performance-gate-for-opening-large-sessions/decision: - en: 75679c69eeeb1e02 - zh: 49ea45fa46ae3fcb + en: 4f94b61c48cceaf4 + zh: dbb51bfe1fb0f681 /agent-note-required-ci-performance-gate-for-opening-large-sessions/calibration-evidence: en: f1001a139470f6b7 zh: b7b46f96b84b1271 diff --git a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.md b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.md index 1b2b0aabb7..926203ea5b 100644 --- a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.md +++ b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.md @@ -12,7 +12,7 @@ Measuring only `SessionPersistence.open()` does not stably describe the result f ## Decision -Linux pull requests run a required `node 24 / benchmarks` job that executes `pnpm run check:ci:bench` → `pnpm run test:bench`. The private `@deepseek-ai/dsh-benchmarks` workspace owns benchmark-only dependencies. The command first builds workspace libraries and dedicated workers under `benchmarks/.dsh-build/`, then invokes `vitest.bench.config.ts`. The [standard hosted runner decision](2026-09-06-standard-hosted-benchmark-runner.md) owns runner selection and the outer job timeout. The job runs the benchmark lane alone; Vitest runs one file at a time and only prepares input, starts measurement children, aggregates results, and enforces budgets. Every timed Node CPU path executes compiled JavaScript under plain Node with `NODE_OPTIONS` removed and no TypeScript loader; bare workspace imports therefore resolve from `benchmarks/node_modules` through package exports to built `lib/` entries. +Linux pull requests run a required `node 24 / benchmarks` job that executes `pnpm run check:ci:bench` → `scripts/run-ci-bench.ts`. Its disposable library build omits repeated TypeScript diagnostics under the [PR artifact build policy](../process/2026-10-05-pr-artifact-typechecks.md); required full builds retain them. The private `@deepseek-ai/dsh-benchmarks` workspace owns benchmark-only dependencies. The command first builds workspace libraries and dedicated workers under `benchmarks/.dsh-build/`, then invokes `vitest.bench.config.ts`. The [standard hosted runner reference](../../../../.github/workflows/ci.yml) owns runner selection and the outer job timeout. The job runs the benchmark lane alone; Vitest runs one file at a time and only prepares input, starts measurement children, aggregates results, and enforces budgets. Every timed Node CPU path executes compiled JavaScript under plain Node with `NODE_OPTIONS` removed and no TypeScript loader; bare workspace imports therefore resolve from `benchmarks/node_modules` through package exports to built `lib/` entries. Required performance gates live under top-level `benchmarks/`, grouped by measured user path rather than package ownership. Host files use `*.bench.ts`, Client-face files use `*.bench.client.ts`, and scenario-specific workers and fixtures stay beside their benchmark without a benchmark suffix. Package-local `.perf.ts` files remain non-gating diagnostics; `scripts/` owns orchestration rather than benchmark cases. diff --git a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.zh.md b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.zh.md index 9ad56796aa..e8a64dca20 100644 --- a/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.zh.md +++ b/.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.zh.md @@ -12,7 +12,7 @@ Session format v2 的推出改变了两条成本随模型输出增长的路径 ## 决定 -Linux pull request 运行必需的 `node 24 / benchmarks` job,执行 `pnpm run check:ci:bench` → `pnpm run test:bench`。私有 `@deepseek-ai/dsh-benchmarks` workspace 拥有 benchmark 专属依赖。该命令先构建 workspace library 和 `benchmarks/.dsh-build/` 下的专用 worker,再调用 `vitest.bench.config.ts`。[标准托管运行器决策](2026-09-06-standard-hosted-benchmark-runner.zh.md)拥有运行器选择及外层 job 超时。该 job 单独运行 benchmark lane;Vitest 逐文件运行,只负责准备输入、启动测量子进程、汇总结果和执行预算断言。每条被计时的 Node CPU 路径都以纯 Node 执行编译后的 JavaScript,并移除 `NODE_OPTIONS` 且不加载 TypeScript runtime;workspace 裸导入因此从 `benchmarks/node_modules` 通过 package exports 解析到构建后的 `lib/` 入口。 +Linux pull request 运行必需的 `node 24 / benchmarks` job,执行 `pnpm run check:ci:bench` → `scripts/run-ci-bench.ts`。其临时 library 构建按 [PR 产物构建政策](../process/2026-10-05-pr-artifact-typechecks.zh.md)省略重复的 TypeScript 诊断;必需的完整构建保留这些诊断。私有 `@deepseek-ai/dsh-benchmarks` workspace 拥有 benchmark 专属依赖。该命令先构建 workspace library 和 `benchmarks/.dsh-build/` 下的专用 worker,再调用 `vitest.bench.config.ts`。[标准托管运行器说明](../../../../.github/workflows/ci.yml)拥有运行器选择及外层 job 超时。该 job 单独运行 benchmark lane;Vitest 逐文件运行,只负责准备输入、启动测量子进程、汇总结果和执行预算断言。每条被计时的 Node CPU 路径都以纯 Node 执行编译后的 JavaScript,并移除 `NODE_OPTIONS` 且不加载 TypeScript runtime;workspace 裸导入因此从 `benchmarks/node_modules` 通过 package exports 解析到构建后的 `lib/` 入口。 必需性能 gate 位于顶层 `benchmarks/`,按被测用户路径而非 package 归属组织。Host 文件使用 `*.bench.ts`,Client 面文件使用 `*.bench.client.ts`,场景专属 worker 与 fixture 留在对应 benchmark 旁且不带 benchmark 后缀。包内 `.perf.ts` 文件仍是非门禁诊断;`scripts/` 负责编排而不承载 benchmark case。 diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml index 60da3ac257..afb6f6cd25 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml @@ -9,11 +9,11 @@ en: c6a89dac7dea293a zh: b10610932d55996c /agent-note-performance-baselines-for-tool-heavy-backend-continuation/decision: - en: 3429525a40e02b95 - zh: 92f12beb81003101 + en: a6dca710f2f43b03 + zh: 49b507d106c5ec81 /agent-note-performance-baselines-for-tool-heavy-backend-continuation/calibration-evidence: - en: 4f47ca6859cc2d70 - zh: 5bfe8314fd30d82b + en: b1bdf65b8b017b68 + zh: 7e8ed6c18bc6aad1 /agent-note-performance-baselines-for-tool-heavy-backend-continuation/alternatives-considered: en: 3a61dff7c926eb22 zh: 8f2633b16be78233 diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md index a2423a404e..dd545253ff 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md @@ -23,7 +23,7 @@ The shared history has 800 completed two-step turns, four tool calls per turn, a The tool execution pipeline, request preparation, Session projections required by those services, persistence, and catalog observations remain production code. Only the model adapter and bounded tool body are synthetic. The adapter retains a request counter, not request objects, so the fixture cannot manufacture a growing retention cost. Sequential input means each idle interval belongs to the one request delivered by this worker; it does not generalize idle to a per-message completion API under concurrent input. -The SDK fixture explicitly inserts `fs-local` and `str_replace_editor` through its profile patch. This preserves the calibrated file-view workload independently of the [minimal profile's shell-only defaults](../simplification/2026-09-03-minimal-profiles-persistent-shell-only.md). File reads, timing endpoints, and budgets remain the same. +The SDK fixture explicitly inserts `fs-local` and `str_replace_editor` through its profile patch. This preserves the calibrated file-view workload independently of the [minimal profile's shell-only defaults](../../../../packages/bundle/sdk-minimal/README.md). File reads, timing endpoints, and budgets remain the same. Five samples report raw wall time, CPU user/system time, peak RSS, endpoint counts, and the minimum, median, and maximum total wall time. Each aggregate report also records CPU models, available parallelism, platform, architecture, and Node/V8 versions after the timed workers exit. Budgets enforce the unrounded median. Continuation additionally measures retained heap against an initialized Host: two explicit GCs separated by an event-loop yield precede and follow the timed operation, while the idle Agent remains reachable. The measured delta therefore includes the resident historical Session and live additions, not just newly appended turns. GC and teardown are outside timing; flush is inside. Request-history retention starts after resume and is diagnostic only. Catalog peak RSS is diagnostic; no retained-heap budget claims to measure already-released parent observations. @@ -39,7 +39,7 @@ The implementation reference is the PR #3537 merge on Apple M4 Pro, macOS arm64, | Tool continuation | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 | | Child catalog | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 | -Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. SDK time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Request history uses the direct hosted expectation in the [request-freeze calibration](../simplification/2026-09-06-agent-request-freeze-evidence.md), without the 2× scale. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix. +Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. SDK time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Request history uses the direct hosted expectation in the [request-freeze calibration](../../../../benchmarks/agent-continuation/agent-continuation.bench.ts), without the 2× scale. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix. A separate plain-Node request-history CPU profile attributes 132.876 ms of sampled self time to deepFreeze called by buildRequest during a 211.300 ms operation. This identifies repeated traversal of already-frozen history as a focused investigation target, not a proven optimization result. Catalog first/repeat timings separately report the first and second parent-catalog observations. diff --git a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md index 942cfd3752..9817b72a44 100644 --- a/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md +++ b/.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md @@ -23,7 +23,7 @@ Status: implemented 工具执行管线、请求准备、这些服务所需的 Session 投影、持久化和目录观察均保留生产代码。只有模型适配器和有界工具体是合成的。适配器只保留请求计数,不保留请求对象,因此 fixture(测试前置数据)不会制造不断增长的保留成本。顺序输入使每个空闲区间对应此 worker 提交的唯一请求;这不代表并发输入时可以把空闲状态推广为逐消息完成 API。 -SDK fixture 通过 profile patch 显式插入 `fs-local` 和 `str_replace_editor`。这使经校准的文件查看负载不依赖[极简 profile 只提供 shell 的默认组合](../simplification/2026-09-03-minimal-profiles-persistent-shell-only.zh.md)。文件读取、计时终点和预算保持不变。 +SDK fixture 通过 profile patch 显式插入 `fs-local` 和 `str_replace_editor`。这使经校准的文件查看负载不依赖[极简 profile 只提供 shell 的默认组合](../../../../packages/bundle/sdk-minimal/README.zh.md)。文件读取、计时终点和预算保持不变。 五个样本报告原始壁钟时间、CPU 用户态/内核态时间、峰值 RSS、终点计数及总壁钟时间的最小值、中位数和最大值。每份汇总报告还在计时 worker 退出后记录 CPU 型号、可用并行度、平台、架构以及 Node/V8 版本。预算约束未经舍入的中位数。续聊还相对已初始化 Host 测量保留堆内存:计时操作前后各执行两次显式 GC,中间让出一次事件循环,空闲 Agent 始终可达。因此该增量包含常驻历史 Session 和实时追加,而不只是新轮次。GC 与资源释放不计时;flush 计时。请求历史的内存基线从恢复后开始,只作诊断。目录峰值 RSS 仅作诊断;没有保留堆预算声称衡量已经释放的父会话观察。 @@ -39,7 +39,7 @@ SDK fixture 通过 profile patch 显式插入 `fs-local` 和 `str_replace_editor | 工具续聊 | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 | | 子会话目录 | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 | -续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。SDK 时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。请求历史使用[请求冻结校准](../simplification/2026-09-06-agent-request-freeze-evidence.zh.md)中的直接托管期望值,不乘以 2× 比例。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。 +续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。SDK 时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。请求历史使用[请求历史基准](../../../../benchmarks/agent-continuation/agent-continuation.bench.ts)中的直接托管期望值,不乘以 2× 比例。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。 独立的纯 Node 请求历史 CPU profile 在一次 211.300 ms 操作中,将 132.876 ms 采样自身时间归因于 buildRequest 调用的 deepFreeze。这把重复遍历已冻结历史定位为聚焦调查目标,不是已证实的优化结果。目录首次/重复时间分别报告第一次和第二次 parent 目录观察。 diff --git a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.i18n.yaml deleted file mode 100644 index f4079749aa..0000000000 --- a/.agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-client-assembly-test-line.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-client-assembly-test-line.md -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock: - en: f11c0f43c9f9be09 - zh: 991625645ac91f06 -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/problem: - en: 33c0c3670dae25c3 - zh: 4d91a11eea9bbb8f -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/decision: - en: af415345cccb067e - zh: 8e82f7136f61a80e -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/product-exports-added-for-the-tier: - en: b45dd80dbe278c5f - zh: cdfb145cd8fab276 -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/alternatives-considered: - en: 2898d540f4bb1ee2 - zh: a3e2f1ea4cc8154f -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/consequences: - en: b6a9721dec64d304 - zh: 160edfa839b7d024 -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/deferred: - en: 1f18c8a38967698a - zh: f6809d290748d9cf -/agent-note-whole-client-test-tier-over-an-endpoint-named-remote-mock/testing: - en: 84fd4b8cc4a4abc8 - zh: f5d8fcb31a6d103b diff --git a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.i18n.yaml index 1d0b70851d..5669420993 100644 --- a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.i18n.yaml @@ -9,20 +9,26 @@ en: 9bf1200b5f980008 zh: d2dc07922817c581 /agent-note-frontend-large-session-performance-budgets/decision: - en: 0ff66ffe55696498 - zh: c05736fde021bcc4 + en: db46b4d42d64aa1b + zh: a21caffa675e8b42 /agent-note-frontend-large-session-performance-budgets/calibration: - en: f3be2a0d346e80cc - zh: c49cc56e7714f68c + en: 2bbee3ec0bc9e618 + zh: b4f937d7f4f09ae5 /agent-note-frontend-large-session-performance-budgets/calibration/actual-ci-runs: en: d4d6aebafcdb8c12 zh: 112d3ca1a4ea8ae1 /agent-note-frontend-large-session-performance-budgets/calibration/standard-hosted-expectations-and-input-scheduling: - en: 8cb6299dbb42a9fe - zh: 51ef49d4fcaeb92f + en: 346cd1b3d5acf0ea + zh: 42e08cd387f982c8 +/agent-note-frontend-large-session-performance-budgets/calibration/older-page-control-preparation: + en: de392f6c19df72ee + zh: 32cbabf9ebcacbcc +/agent-note-frontend-large-session-performance-budgets/calibration/trajectory-readiness-polling: + en: bdcfefd7358e335b + zh: f718f66ca5944203 /agent-note-frontend-large-session-performance-budgets/alternatives-considered: - en: 1472bd425ef0daec - zh: a89811758d70331e + en: 4d2a4a64ccd14507 + zh: 8a4aaf4d4abea13d /agent-note-frontend-large-session-performance-budgets/consequences: en: 3f4f510612875d54 zh: 3a4af464f159bfe8 diff --git a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.md b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.md index ea91edf8d3..c6c53c364e 100644 --- a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.md +++ b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.md @@ -12,9 +12,9 @@ A fast Node conversation fold does not prove that a browser paints a long conver The existing serial benchmark inventory includes two frontend owners: [active reconnect](../../../../benchmarks/active-stream-reconnect/README.md) and a [browser workflow](../../../../benchmarks/long-session-browser/README.md). The browser workflow combines cold open, older-page navigation, first Trajectory activation, return to Chat, and a paced response with trusted keyboard input into one sequential scenario. These are endpoints of one workflow, not independent cold scenarios. The settled conversation-fold benchmark remains unchanged. -`build:bench` keeps the Node-only library and worker build. `test:bench` additionally builds the Web shell before running all cases; the required benchmark CI job follows the [standard hosted runner decision](2026-09-06-standard-hosted-benchmark-runner.md), unconditionally provisions Chromium and its Linux dependencies on that hosted runner, and enables the existing verbose gate output so successful raw samples remain available for calibration. Browser cases reuse the shipped-composition Web scaffold with private temporary roots and an atomically assigned loopback port. Only the nondeterministic model is replaced by synthetic replay. The scaffold Host runs under the existing Vitest source resolver; measured Client rendering runs built bundles in fresh Chromium processes. Browser wall times therefore include this test Host, transport, Playwright actionability, and rendering, and are not claims about a published Host process. +`build:bench` keeps the Node-only library and worker build. `test:bench` additionally builds the Web shell before running all cases; the required benchmark CI job follows the [standard hosted runner reference](../../../../.github/workflows/ci.yml), unconditionally provisions Chromium and its Linux dependencies on that hosted runner, and enables the existing verbose gate output so successful raw samples remain available for calibration. Browser cases reuse the shipped-composition Web scaffold with private temporary roots and an atomically assigned loopback port. Only the nondeterministic model is replaced by synthetic replay. The scaffold Host runs under the existing Vitest source resolver; measured Client rendering runs built bundles in fresh Chromium processes. Browser wall times therefore include this test Host, transport, Playwright actionability, and rendering, and are not claims about a published Host process. -The browser input contains 240 closed turns, 40 tool results, and 20 code fences, plus mixed-language prose and reasoning. Historical Assistant records carry matching compact streams built through the production accumulator with 12-character reasoning/text deltas and 8-character tool-argument deltas; empty streams would omit stored and transferred payload costs. Nine older-page actions exhaust this input from its observed 25-turn initial window; the readiness probe follows mounted turn growth rather than duplicating the pagination algorithm. Each sample uses a fresh scaffold and browser. Setup, seeding, browser launch, initial shell load, and sidebar expansion are excluded from open timing. Open ends at transcript availability and an editable composer; page and navigation timings end at their target DOM state. Two animation frames include a rendering opportunity, not hardware presentation or a guarantee that every offscreen node painted. +The browser input contains 240 closed turns, 40 tool results, and 20 code fences, plus mixed-language prose and reasoning. Historical Assistant records carry matching compact streams built through the production accumulator with 12-character reasoning/text deltas and 8-character tool-argument deltas; empty streams would omit stored and transferred payload costs. Nine older-page actions exhaust this input from its observed 25-turn initial window; the readiness probe follows mounted turn growth rather than duplicating the pagination algorithm. Each sample uses a fresh scaffold and browser. Setup, seeding, browser launch, initial shell load, and sidebar expansion are excluded from open timing. Paging resolves exactly one current `Load earlier` control before timing, excluding its read-only accessibility lookup. The 875 ms paging ceiling includes actionability, the trusted click, mounted turn-tail growth and the final two animation frames; a replaced control fails. Open ends at transcript availability and an editable composer; page and navigation timings end at their target DOM state. Two animation frames include a rendering opportunity, not hardware presentation or a guarantee that every offscreen node painted. The continuation sends 120 text deltas at 16 ms replay pacing. The input witness is installed before Enter submission from the focused composer; typing retains focus without a mouse-refocus action or a separate pre-input animation-frame wait. First/final marker lookups sample visible text inside the latest Assistant step on animation frames, avoiding selector retry backoff. Hidden text and markers in older steps cannot satisfy the observer. The first observation captures reply markers and focus in the browser; diagnostics are retrieved after typing to avoid an additional pre-input round trip. Diagnostics also capture browser-clock timestamps and focus at the first actual input event. Replay never waits for input; starvation can still fail overlap. The synchronous input witness reads that same bounded reply. Whole-history text and accessibility queries add observer CPU and garbage collection to the measured interval, so reducing that observer work is benchmark repair, not product optimization. It records Enter-to-first-visible-reply, trusted draft typing whose first actual input event observes the first reply but no completion marker, complete reply wall time through settled persistence and the new rendered turn-tail, and Chromium main-thread task duration. The complete wall budget adds the fixed 1984 ms scripted pacing to a scaled overhead allowance; input and completion have their own enforced budgets. Post-GC browser heap and DOM counts remain diagnostics because one endpoint does not prove a leak. @@ -22,7 +22,7 @@ Reconnect uses three fresh compiled plain-Node children. Each creates a 100,000- ## Calibration -Three-sample medians on the arm64 reference machine, Node 24.19 and Chromium 149.0.7827.55, at the PR #3537 merge, establish the baseline below. An isolated repeat follows a complete workflow smoke. Each browser sample reports raw endpoint values and every page; the paging verdict uses the median of the sample maxima. Reconnect reports all child measurements. The following historical reference table uses 8 ms replay pacing and includes a two-frame wait in first-reply timing. Standard-hosted open, paging, Trajectory, and reconnect expectations are recorded separately below; other source reference constants retain these allowances. The bounded-observer 261.60 ms paging median exceeds its 260 ms reference allowance but remains below its 650 ms CI limit; the shared 2× time scale and 1.25× variance allowance produce CI limits. Memory uses only variance allowance. The shared scale originates in Node CI calibration. Both actual x64 browser runs below pass the fixed budgets on unchanged benchmark code; this supplies repeated-run evidence for these runners, not a universal browser speed ratio. +Three-sample medians on the arm64 reference machine, Node 24.19 and Chromium 149.0.7827.55, at the PR #3537 merge, establish the baseline below. An isolated repeat follows a complete workflow smoke. Each browser sample reports raw endpoint values and every page; the paging verdict uses the median of the sample maxima. Reconnect reports all child measurements. The following historical reference table uses 8 ms replay pacing and includes a two-frame wait in first-reply timing. Its paging samples include the accessibility lookup of the older-page control. Standard-hosted open, paging, Trajectory, and reconnect expectations are recorded separately below; other source reference constants retain these allowances. The bounded-observer 261.60 ms paging median exceeds its 260 ms reference allowance but remains below its 650 ms CI limit; the shared 2× time scale and 1.25× variance allowance produce CI limits. Memory uses only variance allowance. The shared scale originates in Node CI calibration. Both actual x64 browser runs below pass the fixed budgets on unchanged benchmark code; this supplies repeated-run evidence for these runners, not a universal browser speed ratio. | Endpoint | Measured median | Reference allowance | Historical CI limit | |---|---:|---:|---:| @@ -58,7 +58,9 @@ All six browser samples report `inputOverlapped: true` and finish after the 241s ### Standard hosted expectations and input scheduling -Run 34033336246, job 101487216170 on standard hosted Ubuntu with two CPUs records reconnect replacements of 46.574411, 46.067910, and 44.193704 ms, with 23.028 MiB retained heap. The endpoint-specific expectation is 50 ms; the existing 1.25× headroom gives a 63 ms integer ceiling. The 30 MiB memory budget and shared machine factor remain unchanged. Browser open records 681.276514 and 541.051233 ms before the third sample fails input overlap; both exceed the historical 500 ms limit. Repeated hosted open measurements below set its expectation and ceiling. Deterministic controls pass these recorded values and reject values above the new ceilings through the same assertions as the measured verdicts. Complete repeated hosted verdicts remain required; the two open values are not a three-sample median. +Reconnect uses a 50 ms reference-machine allowance through the shared 2× CI time scale and 1.25× headroom, giving a 125 ms ceiling. Two isolated three-sample runs on Linux x64 EPYC 7763 / Node 24.18.0 measure 47.13 and 43.08 ms medians. Hosted Node 24.21.0 runs 36619857675 and 36621172285 measure 55.47 ms on Xeon 8573C and 65.69 ms on EPYC 9V74 with two available CPUs; their measured production path is unchanged. The latter run is 1.39–1.53× the local reconnect medians; its preparing-write and preparing-bash medians are 1.54–1.55× and 1.34–1.42× their local counterparts. The shared 2× factor covers these observed ratios; the raw samples do not isolate hardware from Node-version or scheduling effects. Both local and hosted reconnect runs retain 23.03 MiB, so the 30 MiB memory ceiling is unchanged. The same time assertion accepts recorded hosted medians, rejects the 65.69 ms median at the former 63 ms ceiling, and rejects synthetic 126 and 150 ms values at 125 ms. This is budget calibration, not a product speedup or a passing future CI verdict. + +Run 34033336246, job 101487216170 on standard hosted Ubuntu with two CPUs records reconnect replacements of 46.574411, 46.067910, and 44.193704 ms, with 23.028 MiB retained heap. Browser open records 681.276514 and 541.051233 ms before the third sample fails input overlap; both exceed the historical 500 ms limit. Repeated hosted open measurements below set its expectation and ceiling. Deterministic controls pass these recorded values and reject values above the new ceilings through the same assertions as the measured verdicts. Complete repeated hosted verdicts remain required; the two open values are not a three-sample median. A local diagnostic with temporary 3× Chromium CPU throttling reproduces the overlap failure: the first marker becomes visible at 1321 ms, two animation frames finish at 1370 ms, and the composer click finishes at 1660 ms; the actual input is trusted but already sees DONE. Removing the frame wait and installing the witness before Send still leaves a run with first visibility at 1415 ms and click completion at 1726 ms, after the original 992 ms scripted stream. The fixed 16 ms cadence keeps the same 120 deltas and payload, providing 1984 ms of scripted pacing for this workload. Only that pacing term changes in the complete-wall allowance (4484 ms); input, first-reply, and main-thread overhead allowances remain unchanged. With the same diagnostic slowdown, three 16 ms samples reach first visibility at 1307/1479/1599 ms and accept trusted input before DONE; their post-DONE controls reject it. The diagnostic is not a CPU-ratio calibration. Each measured sample still requires trusted input while FIRST is present and DONE absent; a post-measurement trusted key after DONE must fail that same assertion. Host settlement and the 241st rendered turn-tail remain completion witnesses. @@ -70,6 +72,20 @@ A controlled mouse-refocus delay waits for the real DONE marker without pausing The current Trajectory acceptance limit is 650 ms, represented by a 520 ms target with the shared 1.25× headroom. Run 35095609422 records medians of 666.651723 ms and 630.843184 ms on its original attempt and retry. This explicit 4% relaxation of the former 625 ms limit accepts the retry median but still rejects the original median; it is a budget decision, not a measured product speedup. Recorded retry samples exercise the accepted range, and the same verdict assertion rejects 651 ms. Other endpoint budgets and workload parameters remain unchanged. +### Older-page control preparation + +Older-page measurements wait for a uniquely named attached control before capturing it. Availability and read-only accessibility lookup are preparation; visibility, enabled/stable actionability, the trusted click, turn-tail growth and the final two frames remain timed. The historical 260 ms reference allowance, 261.60 ms median and 700 ms hosted expectation include the lookup that this endpoint excludes. + +Local samples with lookup excluded are 141.202083/126.114041/140.003334 ms, median 140.003334 ms. Hosted run 37272669722, benchmark job 111642813553 records 524.083466/355.648835/349.959862 ms, median 355.648835 ms, under the same 875 ms ceiling. These are observed samples, not a new calibration; the 700 ms expectation and 875 ms ceiling remain conservative retained limits pending repeated hosted measurements of this endpoint. The earlier same-checkout 887.624/441.989 ms CI medians include lookup and establish retry-cleared failure, not the composition of the first miss. + +### Trajectory readiness polling + +Trajectory activation samples its visible searchbox and a visible record inside `[data-trajectory-scroll]` on animation frames. Playwright's selector polling uses increasing delays up to 500 ms, so DOM readiness can precede the observer by hundreds of milliseconds. The trusted tab click, its actionability checks and the final two-frame wait remain timed; workload and the current 650 ms budget are unchanged. + +A controlled macOS arm64 diagnostic on Node 24.19.0, Chromium 149.0.7827.55 and Playwright 1.61.1 makes the searchbox and record ready after 300 or 350 ms. Selector waits take 793.082/791.053 ms and observe readiness 493.5/439.2 ms late; frame sampling takes 307.324/357.423 ms and observes it 8.1/7.9 ms late. This isolates conditional observer delay, not product latency or the cause of the first #4119 CI miss. + +Complete local 240-turn runs produce Trajectory samples of 157.795/146.737/148.571 ms before the observer change and 130.184/191.247/157.543 ms afterward, with medians 148.571 and 157.543 ms. Both runs pass every fixed endpoint budget; no local speedup or hosted calibration is claimed. The readiness regression rejects a hidden searchbox or record despite a visible unrelated table row; restoring the original selector waits makes that test fail. + ## Alternatives considered **Use the Node fold as paint evidence.** Rejected because it never performs DOM mutation, layout, or browser scheduling. The focused reconnect case likewise makes no GUI speed claim. @@ -80,6 +96,8 @@ The current Trajectory acceptance limit is 650 ms, represented by a 520 ms targe **Search the entire loaded history for every stream marker.** Rejected because Playwright injects text and accessibility scans into the same renderer whose CPU the benchmark measures. Scoping queries to the composer and latest Assistant preserves visible completion checks without making observer cost proportional to loaded history. +**Raise the Trajectory budget or retry failed samples.** This retains the measured selector backoff and can accept slower product behavior. Frame-based readiness removes that observer delay without changing the budget. + **Measure stream CPU alone.** Rejected because transport stalls and final-settlement delays can leave main-thread CPU low. The independent input, first-reply, and complete-wall budgets cover those waits. ## Consequences diff --git a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.zh.md b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.zh.md index 84c115ef09..f2127f195b 100644 --- a/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.zh.md +++ b/.agents/notes/implemented/testing/2026-09-06-frontend-performance-budgets.zh.md @@ -12,9 +12,9 @@ Node 对话折叠很快,并不能证明浏览器能绘制长对话或在流式 现有串行基准清单包含两个前端所有者:[活跃重连](../../../../benchmarks/active-stream-reconnect/README.zh.md)和[浏览器工作流](../../../../benchmarks/long-session-browser/README.zh.md)。浏览器工作流在一个顺序场景中组合冷打开、更早分页导航、首次激活 Trajectory、返回 Chat,以及伴随真实键盘输入的有节奏回复。这些是同一工作流的测量终点,而不是相互独立的冷场景。已结算对话折叠基准保持不变。 -`build:bench` 保留仅 Node 的 library 与 worker 构建。`test:bench` 额外构建 Web shell 后再运行所有用例;必需的基准 CI job 遵循[标准托管运行器决策](2026-09-06-standard-hosted-benchmark-runner.zh.md),在该托管运行器上无条件安装 Chromium 及其 Linux 依赖,并启用现有门禁详细输出,使成功用例的原始样本可用于校准。浏览器用例复用产品组合的 Web scaffold,使用私有临时目录和原子分配的回环端口。只有不确定的模型被合成重放替代。scaffold Host 通过现有 Vitest 源码解析器运行;被测 Client 渲染在全新 Chromium 进程中执行构建后的 bundle。因此浏览器壁钟时间包含测试 Host、传输、Playwright 可交互性等待及渲染,不代表发布版 Host 进程。 +`build:bench` 保留仅 Node 的 library 与 worker 构建。`test:bench` 额外构建 Web shell 后再运行所有用例;必需的基准 CI job 遵循[标准托管运行器说明](../../../../.github/workflows/ci.yml),在该托管运行器上无条件安装 Chromium 及其 Linux 依赖,并启用现有门禁详细输出,使成功用例的原始样本可用于校准。浏览器用例复用产品组合的 Web scaffold,使用私有临时目录和原子分配的回环端口。只有不确定的模型被合成重放替代。scaffold Host 通过现有 Vitest 源码解析器运行;被测 Client 渲染在全新 Chromium 进程中执行构建后的 bundle。因此浏览器壁钟时间包含测试 Host、传输、Playwright 可交互性等待及渲染,不代表发布版 Host 进程。 -浏览器输入包含 240 个已关闭轮次、40 个工具结果和 20 个代码块,以及混合语言正文和推理。历史 Assistant 记录携带匹配的紧凑 stream,通过生产 accumulator 按 12 字符推理/文本 delta 和 8 字符工具参数 delta 构建;空 stream 会遗漏存储与传输负载成本。从观察到的初始 25 轮窗口开始,九次更早分页操作读完该输入;就绪探针跟踪已挂载轮次增长,不复制分页算法。每个样本使用全新 scaffold 和浏览器。环境准备、数据播种、浏览器启动、初始 shell 加载及侧栏展开不计入打开时间。打开测量在对话可用且输入框可编辑时结束;分页与导航测量在目标 DOM 状态出现时结束。两次动画帧包含一次渲染机会,不代表硬件显示或保证每个屏幕外节点都已绘制。 +浏览器输入包含 240 个已关闭轮次、40 个工具结果和 20 个代码块,以及混合语言正文和推理。历史 Assistant 记录携带匹配的紧凑 stream,通过生产 accumulator 按 12 字符推理/文本 delta 和 8 字符工具参数 delta 构建;空 stream 会遗漏存储与传输负载成本。从观察到的初始 25 轮窗口开始,九次更早分页操作读完该输入;就绪探针跟踪已挂载轮次增长,不复制分页算法。每个样本使用全新 scaffold 和浏览器。环境准备、数据播种、浏览器启动、初始 shell 加载及侧栏展开不计入打开时间。分页计时前解析恰好一个当前的 `Load earlier` 控件,将只读的无障碍查找归入准备。875 ms 分页上限仍包含可操作性等待、受信任的点击、已挂载 turn-tail 增长及最后两次动画帧;控件被替换时失败。打开测量在对话可用且输入框可编辑时结束;分页与导航测量在目标 DOM 状态出现时结束。两次动画帧包含一次渲染机会,不代表硬件显示或保证每个屏幕外节点都已绘制。 续接以 16 ms 重放间隔发送 120 个文本 delta。输入观察器在从已聚焦输入框按 Enter 提交前安装;键入保留焦点,不执行鼠标重新聚焦,也不单独等待输入前动画帧。首段/最终标记查找在动画帧上采样最新 Assistant step 内的可见文本,避免选择器重试退避。隐藏文本和较早步骤中的标记均不能满足观察条件。首次观察在浏览器内记录回复标记与焦点;诊断在键入后取回,避免增加输入前的通信往返。诊断还记录首个实际输入事件的浏览器时钟时间戳与焦点。重放从不等待输入;响应阻塞仍可能导致重叠失败。同步输入证据读取同一个受限回复。全历史文本与无障碍查询会向测量区间加入观察器 CPU 和垃圾回收成本,因此减少此类观察工作属于基准修正,而非产品优化。它记录 Enter 提交到首段可见回复的时间、首个实际输入事件观察到首段回复且完成标记尚未出现时的真实草稿键入、直到持久化结算并渲染新 turn-tail 的完整回复壁钟时间,以及 Chromium 主线程任务时间。完整壁钟预算在缩放后的额外开销额度上加固定的 1984 ms 脚本节奏;输入和完成均有独立执行的预算。强制 GC 后的浏览器 heap 和 DOM 数量仍仅供诊断,因为单个终点不能证明泄漏。 @@ -22,7 +22,7 @@ Node 对话折叠很快,并不能证明浏览器能绘制长对话或在流式 ## 校准 -在 arm64 参考机器、Node 24.19、Chromium 149.0.7827.55 和PR #3537 合并结果 上,三个样本的中位数建立下表基线。完整工作流 smoke 后执行一次隔离重复测量。每个浏览器样本报告原始终点数据和每一页;分页判定使用各样本最大值的中位数。重连报告全部子进程测量。下列历史参考表使用 8 ms 重放节奏,首段回复计时包含两帧等待。标准托管打开、分页、Trajectory 和重连预期在下文单独记录;其他源码参考常量保留这些额度。受限观察器的分页中位数 261.60 ms 超过 260 ms 参考额度,但仍低于 650 ms CI 限制;共享的 2× 时间倍率和 1.25× 方差余量产生 CI 限制。内存仅使用方差余量。共享倍率源自 Node CI 校准。下述两次实际 x64 浏览器运行在基准代码不变的情况下均通过固定预算;这提供这些 runner 的重复运行证据,而非普遍适用的浏览器速度比。 +在 arm64 参考机器、Node 24.19、Chromium 149.0.7827.55 和PR #3537 合并结果 上,三个样本的中位数建立下表基线。完整工作流 smoke 后执行一次隔离重复测量。每个浏览器样本报告原始终点数据和每一页;分页判定使用各样本最大值的中位数。重连报告全部子进程测量。下列历史参考表使用 8 ms 重放节奏,首段回复计时包含两帧等待。其中的分页样本包含较早分页控件的无障碍查找。标准托管打开、分页、Trajectory 和重连预期在下文单独记录;其他源码参考常量保留这些额度。受限观察器的分页中位数 261.60 ms 超过 260 ms 参考额度,但仍低于 650 ms CI 限制;共享的 2× 时间倍率和 1.25× 方差余量产生 CI 限制。内存仅使用方差余量。共享倍率源自 Node CI 校准。下述两次实际 x64 浏览器运行在基准代码不变的情况下均通过固定预算;这提供这些 runner 的重复运行证据,而非普遍适用的浏览器速度比。 | 终点 | 实测中位数 | 参考额度 | 历史 CI 限制 | |---|---:|---:|---:| @@ -58,7 +58,9 @@ Node 对话折叠很快,并不能证明浏览器能绘制长对话或在流式 ### 标准托管预期与输入调度 -运行 34033336246,job 101487216170 在双 CPU 标准托管 Ubuntu 上记录重连替换时间 46.574411、46.067910 和 44.193704 ms,保留 heap 为 23.028 MiB。该终点的预期为 50 ms;现有 1.25× 余量产生向上取整后的 63 ms 上限。30 MiB 内存预算及共享机器倍率不变。浏览器打开记录 681.276514 和 541.051233 ms,第三个样本因输入重叠失败而中止;两个值均超过历史 500 ms 上限。下文的托管打开重复测量决定其预期与上限。确定性对照通过这些记录值,并使用与测量判定相同的断言拒绝超过新上限的值。仍需完整的托管重复运行判定;这两个打开值不是三样本中位数。 +重连使用 50 ms 参考机器额度,通过共享的 2× CI 时间倍率与 1.25× 余量得到 125 ms 上限。Linux x64 EPYC 7763 / Node 24.18.0 上两轮独立三样本运行的中位数为 47.13 和 43.08 ms。托管 Node 24.21.0 运行 36619857675 与 36621172285 均有两个可用 CPU,分别在 Xeon 8573C 和 EPYC 9V74 上测得 55.47 与 65.69 ms;两次被测的生产路径没有变化。后一轮为本地重连中位数的 1.39–1.53×,其中 preparing-write 与 preparing-bash 中位数分别为本地对应值的 1.54–1.55× 和 1.34–1.42×。共享 2× 系数覆盖这些实测比例;原始样本未区分硬件、Node 版本或调度各自的影响。本地和托管重连都保留 23.03 MiB,因此 30 MiB 内存上限不变。同一个时间断言接受托管记录中位数,在旧 63 ms 上限下拒绝 65.69 ms 中位数,并在 125 ms 上限下拒绝合成的 126 与 150 ms 值。这是预算校准,不代表产品提速,也不代表后续 CI 已通过。 + +运行 34033336246,job 101487216170 在双 CPU 标准托管 Ubuntu 上记录重连替换时间 46.574411、46.067910 和 44.193704 ms,保留 heap 为 23.028 MiB。浏览器打开记录 681.276514 和 541.051233 ms,第三个样本因输入重叠失败而中止;两个值均超过历史 500 ms 上限。下文的托管打开重复测量决定其预期与上限。确定性对照通过这些记录值,并使用与测量判定相同的断言拒绝超过新上限的值。仍需完整的托管重复运行判定;这两个打开值不是三样本中位数。 临时使用 3× Chromium CPU 降速的本地诊断复现重叠失败:首个标记在 1321 ms 可见,两次动画帧在 1370 ms 结束,输入框点击在 1660 ms 完成;实际输入是真实事件,但已看到 DONE。移除帧等待并在发送前安装观察器后,一次运行仍在 1415 ms 才看到首个标记,点击在 1726 ms 完成,晚于原先 992 ms 的脚本流。固定 16 ms 节奏保留相同的 120 个 delta 和负载,为该工作负载提供 1984 ms 脚本节奏。完整壁钟额度仅改变该节奏项(4484 ms);输入、首段回复及主线程额外开销额度不变。在相同诊断降速下,三个 16 ms 样本在 1307/1479/1599 ms 达到首段可见状态,并接受 DONE 之前的真实输入;其 DONE 之后的对照拒绝该输入。该诊断不是 CPU 比率校准。每个测量样本仍要求真实输入发生时 FIRST 存在且 DONE 不存在;测量后在 DONE 之后发送的真实按键必须无法通过同一个断言。Host 结算和第 241 个已渲染 turn-tail 仍是完成证据。 @@ -70,6 +72,20 @@ Node 对话折叠很快,并不能证明浏览器能绘制长对话或在流式 当前 Trajectory 验收上限为 650 ms,由 520 ms 目标与共享的 1.25× 余量表示。运行 35095609422 的首次执行与重试分别记录中位数 666.651723 ms 和 630.843184 ms。相较于原先的 625 ms 上限,这次明确的 4% 放宽接受重试中位数,但仍拒绝首次中位数;这是预算决策,不代表测得产品提速。记录的重试样本覆盖新增的接受区间,同一个判定断言拒绝 651 ms。其他终点预算与工作负载参数保持不变。 +### 较早分页控件准备 + +较早分页测量先等待具有唯一名称的控件挂载,再捕获该控件。可用性与只读的无障碍查找归入准备;可见、启用及稳定等可操作性检查、受信任的点击、turn-tail 增长和最后两帧仍计时。历史 260 ms 参考额度、261.60 ms 中位数及 700 ms 托管预期包含当前终点已排除的查找。 + +排除查找后的本地样本为 141.202083/126.114041/140.003334 ms,中位数 140.003334 ms。托管运行 37272669722 的 benchmark job 111642813553 记录 524.083466/355.648835/349.959862 ms,中位数 355.648835 ms,仍通过相同的 875 ms 上限。这些是观察样本,不是新校准;700 ms 预期与 875 ms 上限作为保守的现有值保留,待此终点的重复托管测量核验。先前相同 checkout 的 887.624/441.989 ms CI 中位数包含查找,只建立重跑可清除的失败,不分解首次超限的组成。 + +### Trajectory 就绪轮询 + +Trajectory 激活在动画帧上采样可见搜索框与 `[data-trajectory-scroll]` 内的可见记录。Playwright 选择器轮询使用逐步增加、最高 500 ms 的延迟,因此 DOM 可能比观察器早数百毫秒就绪。真实 tab 点击、其可交互性检查与最后两帧等待仍计时;工作负载与当前 650 ms 预算不变。 + +在 macOS arm64、Node 24.19.0、Chromium 149.0.7827.55 和 Playwright 1.61.1 上,受控诊断使搜索框与记录在 300 或 350 ms 后就绪。选择器等待耗时 793.082/791.053 ms,晚 493.5/439.2 ms 观察到就绪;动画帧采样耗时 307.324/357.423 ms,晚 8.1/7.9 ms 观察到就绪。这隔离了有条件发生的观察器延迟,不代表产品延迟,也不能证明 #4119 首次 CI 超限的原因。 + +完整本地 240 轮运行在修改观察器前得到 Trajectory 样本 157.795/146.737/148.571 ms,修改后为 130.184/191.247/157.543 ms,中位数分别为 148.571 和 157.543 ms。两次运行均通过所有固定终点预算;不声称本地提速或托管环境校准。即使存在可见的无关表格行,就绪回归测试仍拒绝隐藏的搜索框或记录;恢复原选择器等待会使该测试失败。 + ## 考虑过的替代方案 **用 Node 折叠作为绘制证据。** 拒绝,因为它不执行 DOM 修改、布局或浏览器调度。聚焦重连用例同样不声称 GUI 提速。 @@ -80,6 +96,8 @@ Node 对话折叠很快,并不能证明浏览器能绘制长对话或在流式 **每次流式标记查找都搜索全部已加载历史。** 拒绝,因为 Playwright 把文本与无障碍扫描注入同一个被测 CPU 的渲染进程。将查询限定在输入框和最新 Assistant 中,可保留可见完成检查,同时避免观察器成本随已加载历史增长。 +**提高 Trajectory 预算或重试失败样本。** 这会保留已测得的选择器退避,并可能接受更慢的产品行为。基于动画帧的就绪观察移除该观察器延迟,不改变预算。 + **只测量流式 CPU。** 拒绝,因为传输停顿和最终结算延迟可能不增加主线程 CPU。独立的输入、首段回复和完整壁钟预算覆盖这些等待。 ## 影响 diff --git a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml deleted file mode 100644 index aa5bb20d1f..0000000000 --- a/.agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.i18n.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-pr-ci-runner-temporary-storage.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.md -/agent-note-runner-owned-temporary-storage-for-pr-ci: - en: 2f9ebea4c52687b3 - zh: 5924c9657eea7ff7 -/agent-note-runner-owned-temporary-storage-for-pr-ci/problem: - en: 5787327a360024d9 - zh: ee9b8cb63d79e551 -/agent-note-runner-owned-temporary-storage-for-pr-ci/decision: - en: 44990c35e1cb69cb - zh: 38a6bed07b5596e3 -/agent-note-runner-owned-temporary-storage-for-pr-ci/recorded-acp-completion-order: - en: 51f2f962db14ba9c - zh: 5fc8a908431c3865 -/agent-note-runner-owned-temporary-storage-for-pr-ci/workspace-grant-fixture-placement: - en: 1008b2fc61d01b10 - zh: f5314d10980611ef -/agent-note-runner-owned-temporary-storage-for-pr-ci/live-verification-and-browser-fixture-inputs: - en: 0f8e8478a3fb6486 - zh: 5d74c6902ea9831c -/agent-note-runner-owned-temporary-storage-for-pr-ci/alternatives-considered: - en: 8b0f2798f91ff190 - zh: f6e871a723e4d079 -/agent-note-runner-owned-temporary-storage-for-pr-ci/consequences: - en: ff73f9c3daeb33be - zh: 28f4245c275c4109 diff --git a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml deleted file mode 100644 index 4a7ab9e93d..0000000000 --- a/.agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-python-runtime-install-retry.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-python-runtime-install-retry.md -/agent-note-bounded-retry-for-python-runtime-dependency-install: - en: 610eeadbda25f100 - zh: e063790ef0b766c9 -/agent-note-bounded-retry-for-python-runtime-dependency-install/problem: - en: ea99de707130a978 - zh: 0d8d853cf38e903a -/agent-note-bounded-retry-for-python-runtime-dependency-install/decision: - en: 8a91f9e5ade1fbbe - zh: 82851f2d37f6a216 -/agent-note-bounded-retry-for-python-runtime-dependency-install/alternatives-considered: - en: c9bee05dfbb068aa - zh: 7f0712a941209116 -/agent-note-bounded-retry-for-python-runtime-dependency-install/consequences: - en: 4d036332d39c88a6 - zh: aac5dd7e7f09df2a diff --git a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml b/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml deleted file mode 100644 index 50c9f04da5..0000000000 --- a/.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-06-standard-hosted-benchmark-runner.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.md -/agent-note-standard-hosted-runner-for-required-benchmarks: - en: 07d81b0ddc7349cd - zh: 0494136a627f09a4 -/agent-note-standard-hosted-runner-for-required-benchmarks/problem: - en: e930cd992a15b811 - zh: fe791e9581ff6a35 -/agent-note-standard-hosted-runner-for-required-benchmarks/decision: - en: 07bc990d5c68d617 - zh: 5bf08df36181b2a6 -/agent-note-standard-hosted-runner-for-required-benchmarks/alternatives-considered: - en: 82f0eab2a8e66383 - zh: 6677a34a53b8915f -/agent-note-standard-hosted-runner-for-required-benchmarks/consequences: - en: 173caff985f5ff21 - zh: 83f3ef068ec62439 diff --git a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml b/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml deleted file mode 100644 index 336d27a518..0000000000 --- a/.agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-publint-test-subprocess-lifetime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-07-publint-test-subprocess-lifetime.md -/agent-note-publint-test-subprocesses-inherit-the-execution-lane-deadline: - en: 762197e4e97b62a1 - zh: a2ac103348f6e692 -/agent-note-publint-test-subprocesses-inherit-the-execution-lane-deadline/problem: - en: a5fb106b97023934 - zh: 537bd40564850192 -/agent-note-publint-test-subprocesses-inherit-the-execution-lane-deadline/decision: - en: 62cc75fa54d44d80 - zh: 72d296ea1d5448bc -/agent-note-publint-test-subprocesses-inherit-the-execution-lane-deadline/alternatives-considered: - en: 5cc52a67c84dd039 - zh: 9fdc5ac07da9a056 -/agent-note-publint-test-subprocesses-inherit-the-execution-lane-deadline/consequences: - en: a2d7dabc61d37634 - zh: 753addcc45383715 diff --git a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml b/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml deleted file mode 100644 index d9e1bb9952..0000000000 --- a/.agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-pwsh-ci-observable-completion.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-07-pwsh-ci-observable-completion.md -/agent-note-powershell-ci-completion-and-profile-expectations: - en: 30366de41f8f71f6 - zh: d343887adae38ef4 -/agent-note-powershell-ci-completion-and-profile-expectations/problem: - en: 5806986dfd58191c - zh: ac082211e112c2d4 -/agent-note-powershell-ci-completion-and-profile-expectations/decision: - en: f23d0691bc168ad7 - zh: 2873eb08c925f2f6 -/agent-note-powershell-ci-completion-and-profile-expectations/alternatives-considered: - en: 6fde4f1b34fdd5e7 - zh: 6bc1ef73ce0e2645 -/agent-note-powershell-ci-completion-and-profile-expectations/consequences: - en: d486f9db082655db - zh: 01c46ff6b700cb15 diff --git a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml b/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml deleted file mode 100644 index bd3c2101c7..0000000000 --- a/.agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-07-subagent-teardown-test-budgets.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-07-subagent-teardown-test-budgets.md -/agent-note-subagent-teardown-tests-inherit-their-execution-lane-budgets: - en: b704be92bc9b9b3c - zh: 4154bd2d89177e5d -/agent-note-subagent-teardown-tests-inherit-their-execution-lane-budgets/problem: - en: 5853599207f6b3bb - zh: d58b5e8e19d93a4f -/agent-note-subagent-teardown-tests-inherit-their-execution-lane-budgets/decision: - en: f2f641fe1f271edf - zh: 321df1f4ea9e783d -/agent-note-subagent-teardown-tests-inherit-their-execution-lane-budgets/alternatives-considered: - en: 3ad30b2574818a61 - zh: d323adddd5065ae4 -/agent-note-subagent-teardown-tests-inherit-their-execution-lane-budgets/consequences: - en: 20d060fe30ada016 - zh: c73937bffbb251c0 diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.i18n.yaml b/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.i18n.yaml deleted file mode 100644 index dc92e99d72..0000000000 --- a/.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-ci-completion-observations.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-08-ci-completion-observations.md -/agent-note-ci-fixture-completion-and-isolation: - en: f5c180b0aad40b22 - zh: 89af7791bfbed707 -/agent-note-ci-fixture-completion-and-isolation/problem: - en: b7350054dcefd411 - zh: 61fd5b508f804ca1 -/agent-note-ci-fixture-completion-and-isolation/decision: - en: 8b245a4f4974e855 - zh: 93963695545d13ed -/agent-note-ci-fixture-completion-and-isolation/decision/built-client-import-classification: - en: 443f119bc5d5b49f - zh: b1c53efd3a216d63 -/agent-note-ci-fixture-completion-and-isolation/alternatives-considered: - en: 569976702b273da6 - zh: 3a5e5faba0074398 -/agent-note-ci-fixture-completion-and-isolation/consequences: - en: 168df70afdad308d - zh: 0e77a4741fa8024b diff --git a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.i18n.yaml b/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.i18n.yaml deleted file mode 100644 index bef8854202..0000000000 --- a/.agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-08-ci-readiness-and-completion.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-08-ci-readiness-and-completion.md -/agent-note-ci-assertions-wait-for-owned-completion: - en: 784e4056f020fab9 - zh: 941e7091a7621c9e -/agent-note-ci-assertions-wait-for-owned-completion/problem: - en: a7fe3958c0c18911 - zh: cce83727500187bc -/agent-note-ci-assertions-wait-for-owned-completion/decision: - en: 2b0ff4ab1d18bd1c - zh: 39556bf95aec3c56 -/agent-note-ci-assertions-wait-for-owned-completion/alternatives-considered: - en: 07ee7017cc6f6e74 - zh: b42f742932edd3f8 -/agent-note-ci-assertions-wait-for-owned-completion/consequences: - en: 70258f58a1e781bb - zh: fabc014ced9872f5 diff --git a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml b/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml deleted file mode 100644 index 4eb8c76f25..0000000000 --- a/.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-09-user-patch-hmr-test-delivery.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md -/agent-note-user-patch-tests-control-filesystem-event-delivery: - en: c2c70a16f6deea8b - zh: 3b11c81b87910ce7 -/agent-note-user-patch-tests-control-filesystem-event-delivery/problem: - en: 3f8862080b8c3c89 - zh: 0e83c485f02c10f1 -/agent-note-user-patch-tests-control-filesystem-event-delivery/decision: - en: 601a3437a4332b69 - zh: 2b6bcc4636314c31 -/agent-note-user-patch-tests-control-filesystem-event-delivery/alternatives-considered: - en: 5d559ee291e2e15e - zh: 828e7def587d71e8 -/agent-note-user-patch-tests-control-filesystem-event-delivery/consequences: - en: da9f64f4d269cece - zh: 6bbbcc771c5fbcde diff --git a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.i18n.yaml b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.i18n.yaml index f61ba057a5..eccd96c807 100644 --- a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.i18n.yaml @@ -15,5 +15,5 @@ en: f93809633864e337 zh: 4bd2141c2a36e1ac /agent-note-qualify-desktop-update-downloads-with-an-isolated-local-server/consequences: - en: 4ef44c91915206b5 - zh: f4ecf7b2d6df4932 + en: eeb58f3bd35349b8 + zh: d9276458e6a57ae6 diff --git a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.md b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.md index 10c0a38410..543fa05b99 100644 --- a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.md +++ b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.md @@ -30,7 +30,7 @@ The executable scenarios cover no update, downgrade rejection, HTTP 404/408 and The ordinary polling regression uses the real coordinator with a fake clock, instance-local random samples, and deferred network completion. It verifies completion-based jitter, capped exponential backoff, successful reset, manual failure visibility while joining an automatic check, retained download failures and prepared packages, and no timer rearming after disposal. Main-entry tests connect the same deadline to focus, resume, explicit IPC, and application shutdown. Removing failure backoff makes the timing assertion fail. These deterministic tests establish request timing, not fleet capacity or startup-burst distribution. -The actual [mandatory-update window](../feature/2026-09-11-desktop-mandatory-update-client.md), sandboxed preload, and renderer button handlers run against the policy server and updater. A stalled policy request reaches its real deadline without clearing the block. Owner-local DOM expectations pin Chinese text and actions. Screenshot failures are recorded separately; missing screenshots cannot establish visual acceptance. +The actual [mandatory-update window](../../../../apps/desktop/README.md), sandboxed preload, and renderer button handlers run against the policy server and updater. A stalled policy request reaches its real deadline without clearing the block. Owner-local DOM expectations pin Chinese text and actions. Screenshot failures are recorded separately; missing screenshots cannot establish visual acceptance. The [workspace browser scenario](../../../../apps/web/tests/desktop-updates.e2e.ts) checks built sidebar composition in both locales against the production presentation function. A page-local carrier supplies update state and records actions; the Host, client plugins, styles, and connection remain real. This separates slot placement and click-guard evidence from Electron IPC, task authorization, and installation evidence. Screenshots and result files use an invocation-specific ignored directory and do not replace release qualification. diff --git a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.zh.md b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.zh.md index 9d554bc500..94c95e8cc8 100644 --- a/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.zh.md +++ b/.agents/notes/implemented/testing/2026-09-10-desktop-local-updater-qualification.zh.md @@ -30,7 +30,7 @@ Desktop 更新交互依赖清单解析、网络失败、下载完整性和平台 常规轮询回归使用真实协调器、模拟时钟、实例私有随机样本和可控的网络完成时机。它验证从完成时刻起算的抖动、有上限的指数退避、成功重置、手动复用自动检查时的失败可见性、下载失败和已准备安装包保留,以及 dispose 后不重新启动计时器。主入口测试将同一期限连接到窗口聚焦、系统恢复、显式 IPC 和应用退出。移除失败退避会使计时断言失败。这些确定性测试证明请求时序,不证明客户端总体容量或启动峰值分布。 -真实的[强更窗口](../feature/2026-09-11-desktop-mandatory-update-client.zh.md)、沙箱预加载和页面按钮处理会对接策略服务器与 updater。停滞的策略请求达到真实截止时间后不会清除阻塞。所属测试目录内的 DOM 预期输出约束中文文案和操作。截图失败单独记录;缺少截图不能证明视觉验收通过。 +真实的[强更窗口](../../../../apps/desktop/README.zh.md)、沙箱预加载和页面按钮处理会对接策略服务器与 updater。停滞的策略请求达到真实截止时间后不会清除阻塞。所属测试目录内的 DOM 预期输出约束中文文案和操作。截图失败单独记录;缺少截图不能证明视觉验收通过。 [工作区浏览器场景](../../../../apps/web/tests/desktop-updates.e2e.ts) 使用生产展示函数检查两种语言下构建后的侧栏组合。页面私有的载体提供更新状态并记录操作;Host、客户端插件、样式和连接保持真实。slot 位置与点击保护的证据因此与 Electron IPC、任务授权和安装证据分开。截图和结果文件使用每次调用独有的忽略目录,不替代发布验收。 diff --git a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml b/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml deleted file mode 100644 index 72274416f8..0000000000 --- a/.agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-10-hosted-image-test-assumptions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-10-hosted-image-test-assumptions.md -/agent-note-hosted-image-assumptions-in-the-coverage-suite: - en: b9e6c590cb18c637 - zh: 02591776cec956ea -/agent-note-hosted-image-assumptions-in-the-coverage-suite/problem: - en: c1299114bcbbbbc2 - zh: 1b38aa9c8b0a4eef -/agent-note-hosted-image-assumptions-in-the-coverage-suite/decision: - en: 00b0e2ef25435ad3 - zh: 9bf59f5ca765a94b -/agent-note-hosted-image-assumptions-in-the-coverage-suite/alternatives-considered: - en: 7cc99ab98600ed3f - zh: c302b579791a403d -/agent-note-hosted-image-assumptions-in-the-coverage-suite/consequences: - en: 96ad46667a025f85 - zh: ba04b1436b82a620 diff --git a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml b/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml deleted file mode 100644 index 52fcc8494f..0000000000 --- a/.agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-12-connection-and-compaction-fixture-preconditions.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-12-connection-and-compaction-fixture-preconditions.md -/agent-note-connection-and-compaction-fixture-preconditions: - en: ea762fbdd18bdce3 - zh: 5795a728fd210f69 -/agent-note-connection-and-compaction-fixture-preconditions/problem: - en: 43949801eda059d6 - zh: 83b579735b9d3e3f -/agent-note-connection-and-compaction-fixture-preconditions/decision: - en: 4e7091381ce185cf - zh: 6e36499f5b5e46f9 -/agent-note-connection-and-compaction-fixture-preconditions/alternatives-considered: - en: b8842654dbe80f4c - zh: e4b80e80d89203b0 -/agent-note-connection-and-compaction-fixture-preconditions/consequences: - en: 054ce1f776693cc9 - zh: dd542b1ccb1cc07d diff --git a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml index 17cae8945a..e5f30837c8 100644 --- a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.i18n.yaml @@ -9,8 +9,8 @@ en: 8d63b992ff6d3161 zh: 86d333255a5400d3 /agent-note-isolate-installed-update-materials-from-releases-and-user-data/decision: - en: 7778333c76ab9b5d - zh: fa35b0a12a31ea23 + en: 815a1c062134d958 + zh: 05d7d89099e6911c /agent-note-isolate-installed-update-materials-from-releases-and-user-data/alternatives-considered: en: c608f186cc94a9d3 zh: 39750c8347e8bece diff --git a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md index 1813c6d9bb..24209c1cce 100644 --- a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md +++ b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.md @@ -10,7 +10,7 @@ An installed-update walkthrough needs two increasing versions with a shared appl ## Decision -Version derivation follows the [release version decision](../process/2026-09-16-desktop-release-version-derivation.md); this record continues to govern material, application identity, and user-data isolation. +Version derivation follows the [release version reference](../../../../apps/desktop/README.md); this record continues to govern material, application identity, and user-data isolation. The [material preparer](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts) accepts a retained test-only manifest with a random identity and distribution namespace. It copies a verified source runtime into separate derived test versions, changes only release-family versions and matching dependency references, and seals and verifies both inventories. The original runtime is verified again and its descriptor hash must remain unchanged. Existing output directories are not overwritten; a failed copy retains a failure record and cannot create a completion receipt. diff --git a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md index b92bb70b32..22ceec3f7b 100644 --- a/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md +++ b/.agents/notes/implemented/testing/2026-09-14-desktop-installed-update-materials.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -版本派生遵循[发布版本决策](../process/2026-09-16-desktop-release-version-derivation.zh.md);本记录继续规定物料、应用身份和用户数据的隔离。 +版本派生遵循[发布版本说明](../../../../apps/desktop/README.zh.md);本记录继续规定物料、应用身份和用户数据的隔离。 [物料准备器](../../../../apps/desktop/scripts/prepare-installed-update-runtime.ts)接受保留的 test 专用清单,其中包含随机身份和分发命名空间。它将已验证的原始运行时复制为独立的派生测试版本,仅修改发布家族版本和匹配的依赖引用,再生成并验证两个完整性清单。原始运行时再次验证,其描述文件哈希必须保持不变。已有输出目录不被覆盖;复制失败保留失败记录,不能生成完成回执。 diff --git a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml b/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml deleted file mode 100644 index 8f91e16be8..0000000000 --- a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-26-web-conversation-clock-fixture-day.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md -/agent-note-web-conversation-clocks-on-the-fixture-day: - en: 13b5d9d0bb3626af - zh: 0bd01e1848284a35 -/agent-note-web-conversation-clocks-on-the-fixture-day/problem: - en: 6f334488a6d3a28d - zh: b33aed9b10d0cd84 -/agent-note-web-conversation-clocks-on-the-fixture-day/decision: - en: ba875c82795d58e1 - zh: 13a7df6f8c4e0aa6 -/agent-note-web-conversation-clocks-on-the-fixture-day/alternatives-considered: - en: 9c0a2afb1473c1d1 - zh: 36c9e72a70072f5e -/agent-note-web-conversation-clocks-on-the-fixture-day/verification: - en: 5031ca866fed24dd - zh: c11f71af70c1f5ec -/agent-note-web-conversation-clocks-on-the-fixture-day/consequences: - en: c5cdbbd3b2889df7 - zh: 8458ef92443cf1a6 diff --git a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.i18n.yaml b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.i18n.yaml index 31f8796929..21215ea682 100644 --- a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.i18n.yaml @@ -15,8 +15,8 @@ en: 00e59c561085e081 zh: 4f6ac4dfc3259f58 /agent-note-windows-lane-observations-wait-on-the-state-they-read/consequences: - en: 46517270dd3b22b1 - zh: 94e300066237cece + en: ee581d7bbdfb3da8 + zh: ea0fdd1b35543dc2 /agent-note-windows-lane-observations-wait-on-the-state-they-read/deferred: en: 8e182739fdf90baf zh: 5f93f7b7d9518764 diff --git a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.md b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.md index 2f7a15921e..67d6a1d22b 100644 --- a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.md +++ b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.md @@ -30,7 +30,7 @@ The loader-composition settle-reason assertions carry the recorded reasons and a Both plugin-manager record cases now observe the record while the run is in flight on a loaded runner, and a genuinely missing record still fails the same assertion. The bounded wait costs 2 s only when the operation never writes the record. -[The task-detail close note](2026-09-28-detail-close-waits-on-observed-state.md) applies the same rule to a web client case, where the sampled state lands in a passive effect one commit after the list the case already awaited. +The [archived task-detail close diagnosis](../../archived/testing/2026-09-28-detail-close-waits-on-observed-state.md) records the same rule in a Web client case, where the sampled state landed in a passive effect one commit after the list the case had awaited. ## Deferred diff --git a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.zh.md b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.zh.md index 6c38e1ae06..bf68cedd95 100644 --- a/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.zh.md +++ b/.agents/notes/implemented/testing/2026-09-27-observation-waits-on-observed-state.zh.md @@ -30,7 +30,7 @@ loader-composition 的结算层级断言把记录到的结算原因与一份逐 在负载较高的 runner 上,两个插件管理器记录用例现在都能在该 run 仍在进行时观测到记录,而真正缺失的记录仍会以同一条断言失败。只有在操作始终不写记录时,这段有界等待才会付出 2 秒。 -[任务详情关闭断言](2026-09-28-detail-close-waits-on-observed-state.zh.md) 把同一条规则用在 web 客户端用例上:被采样的状态落在该用例已经等待过的那个列表之后一个 commit 的被动 effect 里。 +[已归档的任务详情关闭诊断](../../archived/testing/2026-09-28-detail-close-waits-on-observed-state.md)记录了同一条规则在 Web 客户端用例中的应用:被采样的状态落在该用例等待的列表之后一个 commit 的被动 effect 里。 ## Deferred diff --git a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml b/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml deleted file mode 100644 index f50d88bce9..0000000000 --- a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-27-oxlint-contract-spawn-case-budgets.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md -/agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling: - en: 152f1e58efbf506e - zh: 889504ebfe5783ca -/agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling/problem: - en: 08d6430c0b4e868f - zh: b98c15541bcea4ee -/agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling/decision: - en: 34bd113b8c9228fb - zh: 99843bf33b703b5e -/agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling/alternatives-considered: - en: 2b317170890802f7 - zh: 2a5221dcfa8b9116 -/agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling/consequences: - en: e484a10680ca20b8 - zh: 8fd9fcf13e3dbcd7 diff --git a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml b/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml deleted file mode 100644 index f39a22bba4..0000000000 --- a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-27-plugin-install-interceptor-lifetime.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md -/agent-note-keep-plugin-install-interception-for-the-page-lifetime: - en: 2f732d10bfaf9f5b - zh: fff9f6cf8bcd86a7 -/agent-note-keep-plugin-install-interception-for-the-page-lifetime/problem: - en: 71b72bb5b13530bb - zh: b78170e3ac09cccf -/agent-note-keep-plugin-install-interception-for-the-page-lifetime/decision: - en: 45d689dced9dad9f - zh: cb89fbbb7937ca8e -/agent-note-keep-plugin-install-interception-for-the-page-lifetime/alternatives-considered: - en: 1027b59d76cd3ff7 - zh: c4c60fb406acb1db -/agent-note-keep-plugin-install-interception-for-the-page-lifetime/verification: - en: 0e7fbf7fbb47b07c - zh: 9b52991ad8b355ca -/agent-note-keep-plugin-install-interception-for-the-page-lifetime/consequences: - en: 3583e26c8ec33e03 - zh: 6080722751cf15e6 diff --git a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml b/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml deleted file mode 100644 index 96f659c8b0..0000000000 --- a/.agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-27-web-lane-assertions-name-their-input-state.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-27-web-lane-assertions-name-their-input-state.md -/agent-note-web-lane-assertions-name-their-input-state: - en: 6b04734ce8fbeb4a - zh: 73db3bd3ac5985dc -/agent-note-web-lane-assertions-name-their-input-state/problem: - en: 12391509553e0152 - zh: 86d02884ae9c1b09 -/agent-note-web-lane-assertions-name-their-input-state/decision: - en: c6a8e79707975429 - zh: 4fe1f6abd6fd4c1e -/agent-note-web-lane-assertions-name-their-input-state/alternatives-considered: - en: 6cd5940f55500c3d - zh: 4a2e13717e87b783 -/agent-note-web-lane-assertions-name-their-input-state/verification: - en: 9a35f9dbc4285674 - zh: 0995a8b137912b6b -/agent-note-web-lane-assertions-name-their-input-state/consequences: - en: ad3d8ec4b171b04a - zh: e254cc9515b33e0e diff --git a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml b/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml deleted file mode 100644 index 304ae39497..0000000000 --- a/.agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-28-detail-close-waits-on-observed-state.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-28-detail-close-waits-on-observed-state.md -/agent-note-the-task-detail-close-waits-on-the-observed-state: - en: ffb94d33eb1c162c - zh: 32845a4113ab27c1 -/agent-note-the-task-detail-close-waits-on-the-observed-state/problem: - en: 0a3fa08f242a0f7e - zh: fb8c39531405952b -/agent-note-the-task-detail-close-waits-on-the-observed-state/decision: - en: 3e4c20e32e3e9445 - zh: 157750a7e1f1fd59 -/agent-note-the-task-detail-close-waits-on-the-observed-state/alternatives-considered: - en: 8ee7951bdf933b45 - zh: 4736d42c1d52c9b0 -/agent-note-the-task-detail-close-waits-on-the-observed-state/consequences: - en: c0993f90f7af9c41 - zh: 6a877cb1287000e3 -/agent-note-the-task-detail-close-waits-on-the-observed-state/related: - en: f35c1fe1c2ff83b3 - zh: 56a3357ab5a67822 diff --git a/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.i18n.yaml b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.i18n.yaml new file mode 100644 index 0000000000..d0b8ebff98 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-07-memory-limited-benchmarks.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.md +/agent-note-memory-limited-benchmark-reruns: + en: 08979e022b49c675 + zh: b0c12b1f992ac681 +/agent-note-memory-limited-benchmark-reruns/problem: + en: b82519a7026a1a19 + zh: a72c4417426ec95f +/agent-note-memory-limited-benchmark-reruns/decision: + en: 3cf2db98939b3994 + zh: f80a1d3d6049381a +/agent-note-memory-limited-benchmark-reruns/decision/cases: + en: 9502c61025d27f23 + zh: f743a56404327f92 +/agent-note-memory-limited-benchmark-reruns/decision/limit-and-endpoint: + en: a314408967c5a450 + zh: 2b6ed3e1a96fe272 +/agent-note-memory-limited-benchmark-reruns/calibration-evidence: + en: ffe31b42aa199614 + zh: 29e4a3297ab55853 +/agent-note-memory-limited-benchmark-reruns/alternatives-considered: + en: 436516beabc2394f + zh: 7cabbecb5a05f77c +/agent-note-memory-limited-benchmark-reruns/consequences: + en: cb213ef0e7506c8f + zh: 3da65a3981c2518d diff --git a/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.md b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.md new file mode 100644 index 0000000000..79cc9d918a --- /dev/null +++ b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.md @@ -0,0 +1,42 @@ +# Agent Note: Memory-limited benchmark reruns + +Status: implemented + +English | [中文](2026-10-07-memory-limited-benchmarks.zh.md) + +## Problem + +Benchmarks run on hosted runners with 16 GB of memory and on developer machines with more. Neither shows how DSH behaves on a machine with 4 GB or 8 GB available, where the Host, Chromium, and page cache share one memory budget. + +## Decision + +[run-memory-pressure-bench.ts](../../../../scripts/run-memory-pressure-bench.ts) builds the benchmark artifacts unconfined, then reruns a fixed list of existing benchmark files inside a cgroup v2 whose `memory.max` is 4 or 8 GiB and whose `memory.swap.max` is zero. The files keep their own workloads and budgets; the scenario fails when a case misses its normal budget or the cgroup kills a process. + +### Cases + +The list holds the files with the largest measured resident memory or the long-history UI path: [session-corpus](../../../../benchmarks/session-corpus/README.md) (list of 3,000 Sessions, content search over 1,000, fork), its projection-list file, [session-open](2026-09-04-session-open-performance-gate.md) (127,400-event Session), and [long-session-browser](../../../../benchmarks/long-session-browser/README.md) (Chromium and its Host). Terminal, fold, reconnect, and continuation files measure small in-process workloads and stay out. + +### Limit and endpoint + +The cgroup contains Vitest, every benchmark worker, the scaffold Host, and Chromium. Node 24 sizes its default heap from the cgroup limit, and page cache from Session logs is charged to the same limit, so eviction and rereads appear in the timings. On Linux hosts a `systemd-run --user --scope` provides a fresh cgroup. In a container that already enforces the limit, `--scoped` uses the container cgroup. Before running, the launcher requires `memory.max`, `memory.swap.max`, and `process.constrainedMemory()` to match the machine. After the run it reports the cgroup's `memory.peak` and `oom_kill`; both cover the cgroup's lifetime. + +## Calibration evidence + +On 2026-10-07, Docker on Apple silicon (Linux VM, `--cpus=4`, Node 24.21) ran the list once per limit and once without a limit. All 38 cases passed their budgets under both limits, with no OOM kill. + +| Measurement | No limit | 8 GiB | 4 GiB | +|---|---:|---:|---:| +| cgroup peak (MB) | 3,538 | 3,649 | 3,895 | +| List 3,000 first / repeat (ms) | 1,235 / 1,023 | 1,365 / 1,072 | 2,613 / 1,994 | +| Search 1,000 first (ms) | 54,015 | 50,766 | 61,662 | +| Browser slowest page / input (ms) | 152 / 95 | 144 / 91 | 187 / 123 | + +## Alternatives considered + +- `--max-old-space-size` per worker: the list and search workers use about 100 MB of JavaScript heap while their resident memory reaches 0.7–1.2 GB, mostly native memory, so a heap flag does not model the machine. Node already derives the same 2 GB old space for 4 GB and 8 GB cgroups. +- `RLIMIT_AS`: V8 reserves address space far beyond its resident memory and fails under a virtual-memory limit. +- A pull-request CI job: the PR workflow keeps at most ten concurrent worker jobs, and each memory-limited run takes as long as the benchmark lane, so the scenario is manual. + +## Consequences + +macOS has no per-process-tree memory cap; developers there run `--scoped` inside a Linux container started with `--memory` and `--memory-swap` equal to the limit. A file added to the list must keep its budgets valid under both limits. diff --git a/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.zh.md b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.zh.md new file mode 100644 index 0000000000..f5a4db01a8 --- /dev/null +++ b/.agents/notes/implemented/testing/2026-10-07-memory-limited-benchmarks.zh.md @@ -0,0 +1,42 @@ +# Agent Note: 内存受限的基准重跑 + +Status: implemented + +[English](2026-10-07-memory-limited-benchmarks.md) | 中文 + +## 问题 + +基准运行在 16 GB 内存的托管 runner 和内存更大的开发机上。两者都无法显示 DSH 在可用内存为 4 GB 或 8 GB 的机器上的表现,而这类机器上 Host、Chromium 和页缓存共享同一份内存预算。 + +## 决定 + +[run-memory-pressure-bench.ts](../../../../scripts/run-memory-pressure-bench.ts) 先在无约束环境中构建基准产物,再在 `memory.max` 为 4 或 8 GiB、`memory.swap.max` 为零的 cgroup v2 中,重跑一份固定的现有基准文件列表。这些文件保留各自的负载和预算;某个用例未达到其常规预算或 cgroup 杀死进程时,该场景失败。 + +### 用例 + +列表包含常驻内存实测最大的文件和长历史 UI 路径:[session-corpus](../../../../benchmarks/session-corpus/README.zh.md)(3,000 个 Session 的列表、1,000 个 Session 的内容搜索、fork)、其 projection-list 文件、[session-open](2026-09-04-session-open-performance-gate.zh.md)(127,400 个事件的 Session)和 [long-session-browser](../../../../benchmarks/long-session-browser/README.zh.md)(Chromium 及其 Host)。终端、fold、重连和续写文件测量的是较小的进程内负载,不在列表中。 + +### 上限与端点 + +cgroup 包含 Vitest、每个基准 worker、脚手架 Host 和 Chromium。Node 24 按 cgroup 上限确定默认堆大小,Session 日志的页缓存也计入同一上限,因此淘汰和重读会体现在耗时中。在 Linux 主机上,`systemd-run --user --scope` 提供一个新的 cgroup。在已经执行该上限的容器中,`--scoped` 使用容器的 cgroup。运行前,启动脚本要求 `memory.max`、`memory.swap.max` 和 `process.constrainedMemory()` 与机器一致。运行后,它报告该 cgroup 的 `memory.peak` 和 `oom_kill`;两者覆盖 cgroup 的整个生命周期。 + +## 校准证据 + +2026-10-07,Apple silicon 上的 Docker(Linux VM,`--cpus=4`,Node 24.21)对每个上限各运行一次该列表,并在无上限时运行一次。两个上限下全部 38 个用例都通过了预算,没有 OOM kill。 + +| 测量项 | 无上限 | 8 GiB | 4 GiB | +|---|---:|---:|---:| +| cgroup 峰值(MB) | 3,538 | 3,649 | 3,895 | +| 列出 3,000 个:首次 / 重复(ms) | 1,235 / 1,023 | 1,365 / 1,072 | 2,613 / 1,994 | +| 搜索 1,000 个:首次(ms) | 54,015 | 50,766 | 61,662 | +| 浏览器最慢一页 / 输入(ms) | 152 / 95 | 144 / 91 | 187 / 123 | + +## 考虑过的替代方案 + +- 为每个 worker 设置 `--max-old-space-size`:列表和搜索 worker 使用约 100 MB JavaScript 堆,而常驻内存达到 0.7–1.2 GB,主要是原生内存,因此堆参数无法模拟机器。Node 已为 4 GB 和 8 GB cgroup 推导出相同的 2 GB old space。 +- `RLIMIT_AS`:V8 预留的地址空间远超其常驻内存,在虚拟内存上限下会失败。 +- PR CI 作业:PR 工作流最多保持十个并发 worker 作业,而每次内存受限运行与基准 lane 耗时相当,因此该场景为手动运行。 + +## 影响 + +macOS 没有按进程树的内存上限;那里的开发者在以等于上限的 `--memory` 和 `--memory-swap` 启动的 Linux 容器中运行 `--scoped`。加入列表的文件必须在两个上限下都保持预算有效。 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml index 714d3ca25a..380a20a91f 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml @@ -6,8 +6,8 @@ en: e1792a25ca5d684d zh: ddb20d72b56eebf8 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/problem: - en: 9a6abd0305f2e2bb - zh: 7c3f1132c0fa7b56 + en: 22a80335f54b17e9 + zh: 6c0d695a4dba7f0d /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal: en: 34e5bde1535a2bb1 zh: 559deb25b2ec50ba @@ -21,14 +21,14 @@ en: 6a26dd7ef357db74 zh: 951b31136e2415f9 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/dsh-domain-the-domain-data-form: - en: d51c71c793c26834 - zh: 3aab9f9695a0e225 + en: 2ee24f36cd93f7dd + zh: 45c83bd745de41f9 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/future-work-session-side-deletion-design-settled-not-implemented-this-phase: en: a3824326587b6539 zh: 9736e3060bd57651 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/dsh-workspace: - en: 71351e042512c03a - zh: 965622f09f135d37 + en: 13e68c6072cbfc98 + zh: 5f111dd13038b535 /agent-note-domain-kv-storage-capability-seam-and-the-workspace-entity/proposal/reuse-and-the-session-backend-migration-outlook: en: e586acdb16085b91 zh: 4fae53c5b9e4fd5b diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md index dc14648c70..950345b57f 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md @@ -13,7 +13,7 @@ The host's only persistence surface is the session event log (`packages/session/ Separately, Session deletion needs a `SessionPersistence` delete primitive and a `session.delete` endpoint. That gap's design is settled in this note, but its implementation remains future work. -The later [Workspace registration deletion decision](../../implemented/feature/2026-07-27-workspace-registration-deletion.md) supersedes only that coupling: deleting a Workspace registration preserves its Sessions and their logs, while Session deletion remains separate future work. The cascade design below is therefore not the Workspace GUI delete semantic. +The [Workspace reference](../../../../packages/workspace/workspace/README.md) defines non-destructive registration removal: deleting a Workspace registration preserves its Sessions and their logs, while Session deletion remains separate future work. The cascade design below is therefore not the Workspace GUI delete semantic. ## Proposal @@ -106,7 +106,7 @@ export function apply(ctx: Context, config: Config) { } ``` -(Facility unmount order: dispose each domain first (drain its write chain), then remove the name from the hub — in-flight writes still emit `domain/changed` during the drain, and the event-consistency invariant resolves domains back through the facility, so the name must stay resolvable at that point.) +(Facility unmount order: dispose each domain first (drain its write chain), then remove the name from the hub — in-flight writes still emit `domain/changed` during the drain, so the name must stay resolvable at that point.) Domain declarations (the spec object is defined and exported by the package that owns the domain — the single source of type and runtime truth; schemas use zod with `z.infer` deriving the types without re-declaration — the record model projects into RPC wire schemas next phase and the wire boundary is all zod; schemastery still owns plugin Config only): @@ -243,7 +243,7 @@ export class WorkspaceRegistry extends Service { - **Path canon**: the stored value = `fs.realpath(input)` (trailing slashes, `..`, and symlinks all resolved); uniqueness = string equality after normalization (a symlink resolving to the same directory counts as a collision). A missing directory makes create reject outright (realpath fails — a workspace must point at an existing directory; "Create new = make the directory" is upper-layer interaction: mkdir first, then create). The session cwd in attach checks follows the same canon. Single-valued cwd + unique path ⇒ one session structurally belongs to at most one workspace; double bookkeeping is impossible on the write side. - **Title**: a display name, defaults to `basename(path)`, mutable, duplicates allowed. Ownership is never derived from cwd as a fallback — cwd cannot express ordering, and ownership is a workspace-side fact; sessions started headless belong to no workspace. - Consumers see only the `Workspace` interface; `WorkspaceEntity` stays inside the package (a single implementation does not pre-split a seam). Entities are unique per id (registry cache); the record snapshot is swapped in place after each write, and the outside sees getters only. Every write funnels through the entity's internal `mutate(fn)` → `table.update`, with `updatedAt` refreshed inside mutate. Domain objects never cross RPC; next phase the wire layer projects records into zod wire schemas. -- **Session deletion remains future work.** The later [Workspace registration deletion decision](../../implemented/feature/2026-07-27-workspace-registration-deletion.md) ships `ctx.workspaceRegistry.delete(id)` as a metadata-only operation that preserves Sessions and logs. Recursive Session deletion, running checks, and crash-rerun convergence belong to a separate `session.delete` capability. +- **Session deletion remains future work.** The [Workspace registry](../../../../packages/workspace/workspace/README.md) provides `ctx.workspaceRegistry.delete(id)` as a metadata-only operation that preserves Sessions and logs. Recursive Session deletion, running checks, and crash-rerun convergence belong to a separate `session.delete` capability. Consistency doctrine (the ledger = the only ownership authority; the implementation and test baseline): diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md index 1cf7280919..a44086a0d6 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md @@ -13,7 +13,7 @@ host 侧唯一的持久化面是 session 事件日志(`packages/session/sessio 另外,Session 删除需要 `SessionPersistence` 删除原语和 `session.delete` 端点。该空白的设计随本 Note 定案,但实现仍属未来工作。 -后续的 [Workspace 注册记录删除决策](../../implemented/feature/2026-07-27-workspace-registration-deletion.zh.md)取代的仅是上述耦合关系:删除 Workspace 注册记录会保留相关 Session 及其日志,Session 删除仍是独立的未来工作。因此,下文的级联设计并不是 Workspace GUI 的删除语义。 +[Workspace 参考](../../../../packages/workspace/workspace/README.zh.md)定义非破坏性的注册记录移除:删除 Workspace 注册记录会保留相关 Session 及其日志,Session 删除仍是独立的未来工作。因此,下文的级联设计并不是 Workspace GUI 的删除语义。 ## 方案 @@ -106,7 +106,7 @@ export function apply(ctx: Context, config: Config) { } ``` -(facility 卸载顺序:先 dispose 各域(排空写链)再从枢纽摘名——排空期间在途写仍发 `domain/changed`,事件一致性 invariant 经 facility 反查域,要求此时域名仍可解析。) +(facility 卸载顺序:先 dispose 各域(排空写链)再从枢纽摘名——排空期间在途写仍发 `domain/changed`,因此此时域名仍须可解析。) 域声明(spec 对象由拥有该域的包定义导出,是类型与运行时的唯一真源;schema 用 zod,`z.infer` 推导类型不重复声明——记录模型下期要投影成 RPC wire schema,wire 边界全是 zod;schemastery 仍只管插件 Config): @@ -243,7 +243,7 @@ export class WorkspaceRegistry extends Service { - **path 规范**:落盘值 = `fs.realpath(输入)`(尾斜杠、`..`、符号链接全解析);唯一性 = 规范化后字符串相等(符号链接指向同一目录算撞)。目录不存在时 create 直接 reject(realpath 失败——workspace 必须指向存在目录;"Create new = 建目录"是上层交互,先 mkdir 再 create)。attach 校验的 session cwd 同口径。cwd 单值 + path 唯一 ⇒ 一个 session 结构上最多归属一个 workspace,双重记账写侧不可能。 - **title**:显示名,默认 `basename(path)`,可改,允许重复。归属不用 cwd 派生兜底——cwd 表达不了排序,归属是 workspace 侧事实;headless 直开的 session 不属于任何 workspace。 - 消费方只见 `Workspace` 接口,`WorkspaceEntity` 不出包(单实现不预拆 seam);实体按 id 唯一(注册表缓存),记录快照写后原地换新,外部只见 getter;所有写收敛到实体内 `mutate(fn)` → `table.update`,`updatedAt` 在 mutate 内统一刷。领域对象不过 RPC,下期 wire 层把记录投影成 zod wire schema。 -- **Session 删除仍属未来工作。** 后续的 [Workspace 注册记录删除决策](../../implemented/feature/2026-07-27-workspace-registration-deletion.zh.md)已将 `ctx.workspaceRegistry.delete(id)` 作为仅删除元数据、保留 Session 与日志的操作交付。递归删除 Session、运行中检查和崩溃重跑收敛属于独立的 `session.delete` 能力。 +- **Session 删除仍属未来工作。** [Workspace 注册表](../../../../packages/workspace/workspace/README.zh.md)将 `ctx.workspaceRegistry.delete(id)` 作为仅删除元数据、保留 Session 与日志的操作交付。递归删除 Session、运行中检查和崩溃重跑收敛属于独立的 `session.delete` 能力。 一致性口径(账 = 归属唯一依据;实现与测试基准): diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml index 7207445070..59a262d59e 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml @@ -15,8 +15,8 @@ en: e6c93d89a25f0d03 zh: 65e1f5842bc74008 /agent-note-session-projections-and-command-lifecycle-logging/proposal/host-projection-registry-dsh-session-projection-new-package: - en: 6068eb6aba54b102 - zh: 20e91466baf6ad11 + en: a0f86a588c9dff31 + zh: 421803bc8b0dddb5 /agent-note-session-projections-and-command-lifecycle-logging/proposal/shipped-consumer-the-subagent-identity-unit: en: 10093535660c3cae zh: a7f4ff9e71ee6751 @@ -33,8 +33,8 @@ en: 7b536bd8a51cc5dc zh: 1ead63f6358b3799 /agent-note-session-projections-and-command-lifecycle-logging/proposal/command-lifecycle-in-the-log: - en: a7e04451f3ed417c - zh: fb60c8729200b825 + en: 41c7f98634d4762f + zh: 0f3b85f4c6fa06a3 /agent-note-session-projections-and-command-lifecycle-logging/delivery-plan: en: 64ad8d08a8ab44e0 zh: fb0a0f7369d0bd23 @@ -45,5 +45,5 @@ en: 9dfcca30fffda225 zh: 0beddb81d32e0ca7 /agent-note-session-projections-and-command-lifecycle-logging/risks: - en: f0eb219b4119ca7d - zh: de870016c1623895 + en: c3740b6e12eb7295 + zh: b0b427b83c73dbba diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md index fb329a4117..4e2e02482e 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md @@ -59,7 +59,6 @@ declare module 'cordis' { - **State is always computed, never logged.** The log holds events only; the unit's state lives in the framework's per-session watermark cache (`{state, observedSeq}` per unit) and, in a later phase, in a **persisted projection cache** on the domain-KV storage seam: rows of `(sessionId, key, ver, seq, val)` (`ver` = the unit's `stateVersion`, `seq` = the watermark, `val` = the state JSON). A row is never wrong, only possibly stale — its `seq` says exactly how stale. The one read recipe, cold and live alike: take the cached state (or `init()`), forward-apply only the events past its watermark, `view` the result. Cold listings (every session's title across all workspaces) become an index read plus, at worst, a short tail replay; the session-persistence seam grows a read-from-seq primitive for that tail in the same later phase. Write policy: throttled (count/interval, configurable) plus two mandatory points — `turn/end` and detach (the live-to-cold moment). A crash between writes costs a longer tail replay, never a wrong value. - A domain's input event set is its own choice: todos folds `todo/write` alone; plan folds `plan/mode` plus its own `/plan` `command/run` records (see the plan section); goal folds `goal/change` metadata; session title folds its title events (retiring the bespoke `session/title` frame and the client's title-snapshot map — the fourth hand-rolled projection this seam absorbs). - Registration is an effect (disposer with the fiber): an unloaded plugin's key disappears from subsequent responses and the client reads it as capability absence — HMR semantics for free. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. -- The package owns `./invariant` (every served key has a live registration). ### Shipped consumer: the subagent identity unit @@ -128,7 +127,7 @@ Two log-only (non-surface, model-invisible) events, mirroring the `tool/call`/`t 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -The host command executor (`packages/interaction/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep their existing behavior (plan's narration, goal's inject) — that is a domain decision, unchanged. +The host command executor (`packages/interaction/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), and persistence drains them at ordinary checkpoints. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep their existing behavior (plan's narration, goal's inject) — that is a domain decision, unchanged. Because committed events broadcast on the mux stream, refresh persistence, multi-tab sync, and fork/resume recovery all come for free. The `command.execute` RPC degrades to admission — `{ matched, commandId? }`: whether the line resolved, and the minted pairing id when it did, so the issuing client can correlate its request with the flow node the lifecycle events produce. The one-shot notice channel (`runDetached` → `noticeFor`) is retired. @@ -184,7 +183,7 @@ Infrastructure first; the three in-flight PRs are left untouched and re-target a ## Risks - **Whole-value rule is load-bearing**: a future domain logging bare deltas cannot serve consumers from its latest event and complicates its own unit. Mitigation: the rule is stated here and in the projection package README; the unit contract makes the full state explicit at every transition. -- **Synchronous unit discipline**: `init`/`apply`/`view` that await would tear the consistency cut. The registry documents and the invariant companion asserts synchronicity as far as practical; review owns the rest. +- **Synchronous unit discipline**: `init`/`apply`/`view` that await would tear the consistency cut. The registry documents it; review enforces it. - **Live registry churn is not pushed**: loading or unloading a domain plugin mid-session changes the key set, but no session event fires and no frame is pushed; open clients hold the stale key until the next tail pull (reconnect, gap repair, open). Accepted as a dev-only (HMR) staleness window — a registry-change push can be added to the change feed later without contract impact. - **Eager drive costs on busy sessions**: every committed event passes every registered unit's `apply`. Units are cheap per-event by construction (whole-value rule), non-matching events return the same reference, and the count of registered domains is small; if a hot path ever shows, per-unit event-type prefilters can be added without contract change. - **Projection payload growth**: every tail page carries every registered key. Payloads are whole values of UI-scale state (a todo list, a goal snapshot); if a future domain's value is large, per-key opt-out or lazy keys can be added to the request without changing the model. diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md index eb359058a7..f226acfc83 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md @@ -59,7 +59,6 @@ declare module 'cordis' { - **状态永远靠计算得出,绝不入日志。** 日志只存事件;单元的状态住在框架的按会话水位线缓存里(每单元一份 `{state, observedSeq}`),并在后续阶段进入 domain-KV 存储 seam 上的**持久投影缓存(persisted projection cache)**:形如 `(sessionId, key, ver, seq, val)` 的行(`ver` = 单元的 `stateVersion`,`seq` = 水位线,`val` = 状态 JSON)。一行永远不会是错的,至多是陈旧的——其 `seq` 精确说明陈旧到哪。冷读与活读共用同一套读取配方:取缓存状态(或 `init()`),只对超出其水位线的事件做正向 `apply`,再对结果做 `view`。冷列表(跨全部 workspace 列出每个会话的标题)变成一次索引读,至多外加一小段尾部回放;session-persistence seam 在同一后续阶段为这段尾部补一个按 seq 起读的原语。写入策略:节流(次数/间隔,可配置)外加两个强制点——`turn/end` 与 detach(由活转冷的时刻)。两次写入之间崩溃的代价是尾部回放更长一些,绝不会是值出错。 - 领域的输入事件集由领域自己选择:todos 只折叠 `todo/write`;plan 折叠 `plan/mode` 外加它自己的 `/plan` `command/run` 记录(见 plan 一节);goal 折叠 `goal/change` 元数据;会话标题折叠其标题事件(顺带下线专设的 `session/title` 帧与客户端的标题快照表——这是该 seam 收编的第四个手工投影)。 - 注册是 effect(disposer 随 fiber 走):插件卸载后其 key 从后续响应中消失,客户端将其读作能力缺失——HMR(热模块替换)语义随之自动成立。key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。 -- 该包拥有 `./invariant`(每个被服务的 key 都有一条存活的注册)。 ### 已交付的消费方:subagent 身份单元 @@ -128,7 +127,7 @@ type UseProjection = { 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -host 侧命令执行器(`packages/interaction/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent(智能体)的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域保持既有行为(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 +host 侧命令执行器(`packages/interaction/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent(智能体)的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域保持既有行为(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 由于已提交事件会在 mux 流上广播,刷新后仍在、多标签页同步、fork/恢复后可还原这三件事随之全部自动获得。`command.execute` RPC 退化为准入判定——`{ matched, commandId? }`:该行是否匹配命中,以及命中时新铸的配对 id,发起命令的客户端据此把自己的请求与生命周期事件产出的 flow 节点关联起来。一次性通知通道(`runDetached` → `noticeFor`)就此下线。 @@ -184,7 +183,7 @@ host 侧命令执行器(`packages/interaction/commands`)在调用处理器 ## 风险 - **全量值规则是承重结构**:未来某个领域若只记裸增量,就无法凭其最新事件服务消费方,还会让自己的单元复杂化。缓解:该规则写明在本 Note 与投影包的 README 里;单元约定让完整状态在每次转移处都是显式的。 -- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,invariant 配套在可行范围内断言同步性;其余由评审把关。 +- **单元的同步纪律**:`init`/`apply`/`view` 一旦 await 就会撕裂一致性切面。注册表在文档中申明这条纪律,由评审把关。 - **注册表的实时增删不做推送**:会话中途加载或卸载领域插件会改变键集,但不会触发任何会话事件、也不会推任何帧;开着的客户端持有陈旧的 key 直到下次尾页拉取(重连、缺口修补、打开)。接受为仅开发期(HMR)的陈旧时窗——日后可以在变更流上加一个注册表变更推送,约定不受影响。 - **忙碌会话上的主动驱动开销**:每个已提交事件都要过每个已注册单元的 `apply`。按构造,单元的逐事件开销很低(全量值规则),不匹配的事件返回同一引用,且已注册领域的数量很小;若真出现热点路径,可以加按单元的事件类型预过滤,约定不变。 - **投影载荷膨胀**:每个尾页携带每个已注册的 key。载荷是 UI 量级状态的全量值(一张 todo 清单、一份 goal 快照);将来若某领域的值很大,可以在请求上加逐 key 的 opt-out 或惰性 key,模型本身不用改。 diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml index db04c28547..d8512e33ec 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml @@ -6,8 +6,8 @@ en: 9ebb460b39f06952 zh: 0fc0480869566600 /agent-note-storage-root-placement-and-derived-medium-recovery/problem: - en: e94be80df86b1da9 - zh: fe9b9dad3299562e + en: 82f3782b8c856da9 + zh: f3d7be6283a6a07d /agent-note-storage-root-placement-and-derived-medium-recovery/proposal: en: bd212f50ff01960a zh: de2c69ef9be74629 diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md index 3a4124e577..e840991cee 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md @@ -10,7 +10,7 @@ The persisted projection cache ([note](2026-07-27-session-projection-and-command **Where the files actually live (root mismatch closed; resolve-once residual still open).** The shared base defaults the session store to the global harness home (`$DSH_HOME/sessions`, default `~/.dsh/sessions`), while the shipped Web overlay used to give the json backend the relative root `./.storages`: `workspace.json` and `session_projcache.json` landed under `/.storages/` — two launches from different directories shared their sessions yet saw different workspace registries and different projection caches, and the cache exists precisely to serve the cross-session cold listing, which missed for every session last cached under another launch directory. That mismatch is now closed: the overlay anchors `storage-json.root` to `$DSH_HOME/storages` with the same `!!js` expression the session root uses (`apps/cli/config/web.cordis.yml`). The residual hazard: `JsonStorageBackend` still never resolves its root — each unit open joins the path against whatever `process.cwd()` is at that moment (packages/storage/storage-json/src/index.ts); the shipped overlay root is already absolute and unaffected, but any relative root (bare Loader boots, tests) still splits on a later cwd change — the exact hazard the JSONL session backend resolves-once to prevent ("later process.cwd() changes cannot split one backend across roots", packages/session/session-persistence-jsonl/src/index.ts). -**Recovery behavior.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which describes an aspiration, not the implementation. Partially superseded for the projection cache: [the per-session cache files note](../../archived/architecture/2026-08-19-projection-cache-per-session-files.md) removed the global `session_projcache` domain, so the cache half of this proposal (recovery on that domain) no longer applies; the `workspace.json` half remains current. The `invalid-record` class for the per-record projection cache is now also superseded: the shipped domain declares `invalidRecords: 'backup-and-skip'` ([cross-version read-compat note](../../implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md)), which backs the failing record up and skips it at open, so the reset/destroy proposal below stays relevant only for whole-medium damage on authoritative or single-document media. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change. +**Recovery behavior.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which describes an aspiration, not the implementation. Partially superseded for the projection cache: [the per-session cache files note](../../archived/architecture/2026-08-19-projection-cache-per-session-files.md) removed the global `session_projcache` domain, so the cache half of this proposal (recovery on that domain) no longer applies; the `workspace.json` half remains current. The `invalid-record` class for the per-record projection cache is now also superseded: the shipped domain declares `invalidRecords: 'backup-and-skip'` ([cross-version read-compat reference](../../../../packages/session/session-projection-cache/README.md)), which backs the failing record up and skips it at open, so the reset/destroy proposal below stays relevant only for whole-medium damage on authoritative or single-document media. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change. ## Proposal diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md index f8d91402ba..ea4399ab9a 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md +++ b/.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](../../archived/architecture/2026-08-19-projection-cache-per-session-files.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` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。 +**恢复行为。** 在健康介质内部,缓存按设计完全自愈:`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](../../archived/architecture/2026-08-19-projection-cache-per-session-files.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。per-record 投影缓存的 `invalid-record` 一类如今也已被取代:已发布的域声明了 `invalidRecords: 'backup-and-skip'`([跨版本读兼容 reference](../../../../packages/session/session-projection-cache/README.zh.md)),open 时把失败记录备份后跳过,因此下文 reset/destroy 提案仅对权威介质或单文档介质的整介质损坏仍然相关。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。 ## 提案 diff --git a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.i18n.yaml index bab183a3a2..4e59397821 100644 --- a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.i18n.yaml @@ -6,14 +6,14 @@ en: f64dc6aee10c755f zh: a28d8450c78b0c01 /agent-note-record-last-activity-in-the-session-index/problem: - en: 332469396acd98d4 - zh: c9e4d587628fc0e7 + en: 485ea0012ab3d9ed + zh: cc02544a320a22bb /agent-note-record-last-activity-in-the-session-index/proposal: en: 2260380e10b59eb6 zh: dcf5a7f2d6003021 /agent-note-record-last-activity-in-the-session-index/alternatives-considered: - en: 9034b95d80405f9c - zh: 573e1dcd10b62344 + en: 7a40315717074a92 + zh: f7f1e7bf79a85863 /agent-note-record-last-activity-in-the-session-index/acceptance-criteria: en: ec546e4feceb77a7 zh: a29c9268e1c189c7 @@ -21,5 +21,5 @@ en: 9aaf88097b46e9b6 zh: d16e5bbbe221b139 /agent-note-record-last-activity-in-the-session-index/related: - en: 3fde35c997f273f8 - zh: 2d063c34cc9fe0bb + en: 6baa1ed65ac7dd16 + zh: 2cb7b878fb233220 diff --git a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md index f007f01bf3..52b2a6b436 100644 --- a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md +++ b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md @@ -8,7 +8,7 @@ English | [中文](2026-07-29-durable-last-activity-index.zh.md) A cold (persisted, unattached) session has no authoritative stored answer to "when did the user last prompt here". `dsh-host-apiproxy` serves `updatedAt` from the optional projection cache's `lastPromptAt`, falling back to `createdAt`, and the Web client sorts its Session tree by that value. The cache is fail-soft and checkpointed asynchronously, so a missing or delayed row makes a recently prompted Session sort too old. -The gateway previously used JSONL artifact mtime when available. mtime answers a different question: when the artifact was last written. Every durable write refreshes it, including a truncate-repair of a torn tail, synthetic closers that balance an interrupted turn, and the [`session/end-seed` boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) appended during pickup. That approximation promoted a Session merely because it was opened. The [bounded cold blank verification](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md) removed mtime ordering and accepted the cache's conservative "too old" failure direction as an interim tradeoff. +The gateway previously used JSONL artifact mtime when available. mtime answers a different question: when the artifact was last written. Every durable write refreshes it, including a truncate-repair of a torn tail, synthetic closers that balance an interrupted turn, and the [`session/end-seed` boundary](../../../../packages/core/session/src/types.ts) appended during pickup. That approximation promoted a Session merely because it was opened. The [bounded cold blank verification](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md) removed mtime ordering and accepted the cache's conservative "too old" failure direction as an interim tradeoff. An attached summary can fold the live event log and select the latest human-authored `user/message`, but the cold path deliberately reads no logs: cold summaries come from the projection cache alone, so cold recency is only as fresh as the cache. @@ -34,7 +34,7 @@ Three questions must be answered before implementation, and none of them is sett **Keep mtime and exclude boundary writes from it.** Rejected as impossible rather than undesirable: mtime is the filesystem's, not the backend's. Nothing short of restoring the timestamp after every boundary write would preserve it, and that races any concurrent reader and lies about the artifact. -**Write the boundary only when repair occurred.** Would reduce the frequency, and the [boundary note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) already rejected it: the predicate must hold for an orderly restart too. Trading a correctness invariant for timestamp accuracy is the wrong direction. +**Write the boundary only when repair occurred.** Would reduce the frequency, and the [boundary reference](../../../../packages/core/session/src/types.ts) already rejected it: the predicate must hold for an orderly restart too. Trading a correctness invariant for timestamp accuracy is the wrong direction. **Derive activity from a projection cache.** This is the current interim implementation. `session-projection-cache` folds tails past a watermark without changing the persistence format, but it is optional and fail-soft. Its absence or checkpoint delay makes ordering depend on cache availability and freshness, so it cannot provide the authoritative value proposed here. @@ -59,6 +59,6 @@ Three questions must be answered before implementation, and none of them is sett ## Related - [Bounded cold blank verification](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md) — removes mtime ordering and defines the interim cache-only cold summary this proposal would make exact. -- [The end-seed log boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) — one of the non-prompt writes that made mtime unsuitable. -- [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md) — the append-only and never-rewrite invariants that rule out a mutable JSONL header field. +- [The end-seed log boundary](../../../../packages/core/session/src/types.ts) — one of the non-prompt writes that made mtime unsuitable. +- [Session persistence](../../../../packages/session/session-persistence/README.md) — the append-only and never-rewrite invariants that rule out a mutable JSONL header field. - [Handle-based session persistence](../../implemented/architecture/2026-08-27-handle-based-session-persistence.md) — the write-handle append path a stored field would hook into. diff --git a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.zh.md b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.zh.md index c3259f47bc..a8a1556b91 100644 --- a/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.zh.md @@ -8,7 +8,7 @@ Status: proposed 一个冷会话(已持久化、未附加)对「用户上次是什么时候在这里发出 prompt」没有权威的已存储答案。`dsh-host-apiproxy` 从可选 projection cache 的 `lastPromptAt` 提供 `updatedAt`,缺失时回退到 `createdAt`,Web 客户端按该值为 Session 树排序。cache 采用 fail-soft 并异步写入 checkpoint,因此缺失或延迟的记录会让最近收到 prompt 的 Session 排得过旧。 -网关以前会在可用时采用 JSONL 产物的 mtime。mtime 回答的是另一件事:这份产物上次是什么时候被写入。每一次持久写入都会刷新它,包括对撕裂尾部的截断修复、平衡中断轮次的合成 closer,以及拾起时追加的 [`session/end-seed` 边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md)。这套近似会让 Session 仅仅因为被打开就提升排序。[有界冷空白验证](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md)移除了 mtime 排序,并把 cache 保守的「过旧」错误方向作为现阶段取舍。 +网关以前会在可用时采用 JSONL 产物的 mtime。mtime 回答的是另一件事:这份产物上次是什么时候被写入。每一次持久写入都会刷新它,包括对撕裂尾部的截断修复、平衡中断轮次的合成 closer,以及拾起时追加的 [`session/end-seed` 边界](../../../../packages/core/session/src/types.ts)。这套近似会让 Session 仅仅因为被打开就提升排序。[有界冷空白验证](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md)移除了 mtime 排序,并把 cache 保守的「过旧」错误方向作为现阶段取舍。 已附加摘要可以折叠实时事件日志并选择最新的真人 `user/message`,但冷路径有意不读取任何日志:冷摘要只来自 projection cache,因此冷最近时间的新旧只取决于 cache。 @@ -34,7 +34,7 @@ Status: proposed **保留 mtime,但把边界的写入排除在它之外。** 否决的理由是做不到,而不是不合意:mtime 属于文件系统,不属于后端。除了在每次边界写入之后把时间戳复原,没有别的办法能保住它,而那样做会与任何并发读取方产生竞态,也会对这份产物撒谎。 -**仅在确实发生了修复时才写入边界。** 这能降低出现频率,而[边界 Agent Note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md)已经否决过它:谓词对有序重启同样必须成立。用一条正确性不变式去换时间戳的准确度,方向是错的。 +**仅在确实发生了修复时才写入边界。** 这能降低出现频率,而[边界 reference](../../../../packages/core/session/src/types.ts)已经否决过它:谓词对有序重启同样必须成立。用一条正确性不变式去换时间戳的准确度,方向是错的。 **从投影缓存派生活动时间。** 这是当前的过渡实现。`session-projection-cache` 会折叠水位线之后的尾部,无需改变持久格式,但它是可选且 fail-soft 的。缺失或 checkpoint 延迟会让排序取决于 cache 是否存在以及是否新鲜,因此无法提供本文所提议的权威值。 @@ -59,6 +59,6 @@ Status: proposed ## 相关 - [有界冷空白验证](../../archived/bug-fix/2026-08-13-bounded-cold-blank-verification.md)——移除 mtime 排序,并定义了本提案将使之精确的、仅依赖 cache 的过渡冷摘要。 -- [种子结束日志边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.zh.md)——让 mtime 不适用的非 prompt 写入之一。 -- [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.zh.md)——仅追加与绝不重写这两条不变式,正是它们排除了可变的 JSONL header 字段。 +- [种子结束日志边界](../../../../packages/core/session/src/types.ts)——让 mtime 不适用的非 prompt 写入之一。 +- [会话持久化](../../../../packages/session/session-persistence/README.zh.md)——仅追加与绝不重写这两条不变式,正是它们排除了可变的 JSONL header 字段。 - [基于句柄的会话持久化](../../implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)——一个已存储字段将挂入的那条写句柄追加路径。 diff --git a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.i18n.yaml b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.i18n.yaml index 311038880f..8db6097575 100644 --- a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.i18n.yaml @@ -9,8 +9,8 @@ en: 45193c3a6f3d29bf zh: 7f508649dbe65db5 /agent-note-semantic-phases-for-composer-chain-election/proposal: - en: 9dc3866376b4590b - zh: 93c2208fe9a43d91 + en: 2c71821b085dd337 + zh: 4da440bdbb0b12ab /agent-note-semantic-phases-for-composer-chain-election/alternatives-considered: en: 966a5082d8c2fe35 zh: 6a384edcdec8495e diff --git a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.md b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.md index 8dab5afcdd..ddabc91e0d 100644 --- a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.md +++ b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.md @@ -18,7 +18,7 @@ Question and approval register in `interaction`, retaining their current within- The phase vocabulary belongs to the declaring slot, not to the slot framework globally. `SlotMap` carries the exact phase tuple for compile-time registration, and the runtime `SlotSpec` repeats that tuple as the sorting authority. Other chains acquire no composer terminology and need no migration unless they deliberately declare phases. -This proposal extends the [Web subagent conversation](../../implemented/feature/2026-07-27-web-subagent-conversations.md), [Web permission and approval](../../archived/feature/2026-07-23-web-permission-and-approval.md), and [plan-review presentation](../../archived/feature/2026-07-30-plan-review-presentation-intent.md) contracts; it supersedes none of them. The [runtime-owned child guard record](../../archived/bug-fix/2026-08-01-ask-user-delegated-caller-guard.md) introduced the guard that prevents new child-owned human waits. No active Agent Note should be archived when this proposal lands. +This proposal extends the [Web subagent conversation](../../../../packages/client/ui-subagent/README.md), [Web permission and approval](../../archived/feature/2026-07-23-web-permission-and-approval.md), and [plan-review presentation](../../archived/feature/2026-07-30-plan-review-presentation-intent.md) contracts; it supersedes none of them. The [runtime-owned child guard record](../../archived/bug-fix/2026-08-01-ask-user-delegated-caller-guard.md) introduced the guard that prevents new child-owned human waits. No active Agent Note should be archived when this proposal lands. ## Alternatives considered diff --git a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.zh.md b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.zh.md index d00332b155..c5d38110df 100644 --- a/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.zh.md +++ b/.agents/notes/proposed/architecture/2026-08-08-semantic-composer-chain-phases.zh.md @@ -18,7 +18,7 @@ Status: proposed 阶段词汇归声明该 slot 的领域所有,而不属于全局 slot 框架。`SlotMap` 携带确切的阶段元组,用于编译期注册;运行时 `SlotSpec` 重复该元组,作为排序依据。其他链不会获得任何 composer 术语,也无需迁移,除非它们主动声明阶段。 -本提案扩展 [Web subagent 对话](../../implemented/feature/2026-07-27-web-subagent-conversations.zh.md)、[Web 权限与审批](../../archived/feature/2026-07-23-web-permission-and-approval.md)和[计划审阅呈现](../../archived/feature/2026-07-30-plan-review-presentation-intent.md)约定,但不取代其中任何一项。[运行时所有权子级守卫记录](../../archived/bug-fix/2026-08-01-ask-user-delegated-caller-guard.md)引入了防止子级新建自己负责的人类等待的守卫。本提案落地时,不应归档任何活跃 Agent Note。 +本提案扩展 [Web subagent 对话](../../../../packages/client/ui-subagent/README.zh.md)、[Web 权限与审批](../../archived/feature/2026-07-23-web-permission-and-approval.md)和[计划审阅呈现](../../archived/feature/2026-07-30-plan-review-presentation-intent.md)约定,但不取代其中任何一项。[运行时所有权子级守卫记录](../../archived/bug-fix/2026-08-01-ask-user-delegated-caller-guard.md)引入了防止子级新建自己负责的人类等待的守卫。本提案落地时,不应归档任何活跃 Agent Note。 ## 备选方案 diff --git a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.i18n.yaml b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.i18n.yaml index 8d5b683f9e..2cc9228795 100644 --- a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.i18n.yaml @@ -6,8 +6,8 @@ en: 28be5a60d0f8f70d zh: 2ade899f45347755 /agent-note-pre-tool-input-rewrite-a-consistent-design/problem: - en: 1e84d54d317750ed - zh: 5436c2f44569ab52 + en: edd34e93748ea618 + zh: 182f02dffa0d654f /agent-note-pre-tool-input-rewrite-a-consistent-design/the-problem-three-readers-of-pre-execution-arguments: en: bdc0958b87dbd888 zh: 0364cad8a5727306 diff --git a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md index a42b05ac7d..add55c305b 100644 --- a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md +++ b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md @@ -6,7 +6,7 @@ English | [中文](2026-06-30-pre-tool-input-rewrite.zh.md) ## Problem -The [interception extension-points Agent Note](../../implemented/feature/2026-06-30-interception-extension-points.md) defines `tools/pre-execute` as an allow/deny/ask gate over an execution whose identity is already protected and whose arguments are deeply frozen. Claude Code's `PreToolUse` hook also offers `updatedInput`, so a faithful bridge needs an explicit rewrite mechanism. A rewrite cannot be a mutation escape hatch on the existing execution object: it must keep the durable history, audit record, presentation, and executed value consistent. +The [interception extension-points reference](../../../../packages/core/tools/README.md) defines `tools/pre-execute` as an allow/deny/ask gate over an execution whose identity is already protected and whose arguments are deeply frozen. Claude Code's `PreToolUse` hook also offers `updatedInput`, so a faithful bridge needs an explicit rewrite mechanism. A rewrite cannot be a mutation escape hatch on the existing execution object: it must keep the durable history, audit record, presentation, and executed value consistent. ## The problem: three readers of pre-execution arguments diff --git a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md index 8ef07ea7b6..4eaa7ba0ba 100644 --- a/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md +++ b/.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md @@ -6,7 +6,7 @@ Status: proposed ## 问题 -[拦截扩展点 Agent Note](../../implemented/feature/2026-06-30-interception-extension-points.zh.md) 将 `tools/pre-execute` 定义为一道针对执行的允许/拒绝/询问门禁,此时执行的身份标识已受保护、参数已被深度冻结。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的重写机制。重写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。 +[拦截扩展点 参考](../../../../packages/core/tools/README.zh.md) 将 `tools/pre-execute` 定义为一道针对执行的允许/拒绝/询问门禁,此时执行的身份标识已受保护、参数已被深度冻结。Claude Code 的 `PreToolUse` 钩子还提供了 `updatedInput`,因此忠实的桥接需要一个显式的重写机制。重写不能是对现有执行对象的可变逃逸口:它必须保持持久化历史、审计记录、展示层与实际执行值之间的一致性。 ## 问题本质:执行前参数的三个读取方 diff --git a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.i18n.yaml b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.i18n.yaml index a142187273..30e665ca0b 100644 --- a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.i18n.yaml @@ -6,8 +6,8 @@ en: a200e33a65a4f530 zh: 47d02d3736310892 /agent-note-interactive-side-sessions-and-merge-back/problem: - en: 181d0078a702fdf2 - zh: ad1bff4f82fafdf3 + en: 212258ada8f174eb + zh: 9a02b100533118fe /agent-note-interactive-side-sessions-and-merge-back/proposal: en: 5fa7d2000db6b265 zh: a892b74299e0f984 @@ -18,5 +18,5 @@ en: 17d0937a1f28b1ac zh: 9710ab0af3268da1 /agent-note-interactive-side-sessions-and-merge-back/risks: - en: 86f12d62580d8d98 - zh: 8c39be21a1e3958f + en: 2435d8e2aba0d1c1 + zh: 3854c476bea061d4 diff --git a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md index 53b21497c9..fd1b79a820 100644 --- a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md +++ b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md @@ -6,7 +6,7 @@ English | [中文](2026-07-08-interactive-side-sessions.zh.md) ## Problem -A user may want to explore a question from a live session without changing its main context. Existing primitives do not expose that product shape: [session-store fork](../../archived/feature/2026-06-30-session-store-fork-api.md) creates an unattached session, while [fork subagents](../../implemented/feature/2026-06-21-subagent-capability-seam.md) are model-driven tasks whose transcript collapses into one tool result. Neither gives the user a separate conversation, and neither records a conclusion in the parent together with the side session that produced it. +A user may want to explore a question from a live session without changing its main context. Existing primitives do not expose that product shape: [session-store fork](../../archived/feature/2026-06-30-session-store-fork-api.md) creates an unattached session, while [fork subagents](../../../../packages/subagent/subagent-fork-in-process/README.md) are model-driven tasks whose transcript collapses into one tool result. Neither gives the user a separate conversation, and neither records a conclusion in the parent together with the side session that produced it. ## Proposal @@ -36,6 +36,6 @@ Rewind productization, session-tree views, a model-facing side-session tool, and ## Risks -- Read-only behavior is advisory until a `tools/pre-execute` deny gate enforces it; [the interception point](../../implemented/feature/2026-06-30-interception-extension-points.md) can add that gate without changing these mechanics. +- Read-only behavior is advisory until a `tools/pre-execute` deny gate enforces it; [the interception point](../../../../docs/tool-execution-pipeline.md) can add that gate without changing these mechanics. - A compacted source forks its compacted view, so a bound UI should disclose that the child inherits summaries rather than replaced turns. - Repeated handbacks consume parent context. The per-merge length cap bounds each note; later consolidation belongs to compaction. diff --git a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.zh.md b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.zh.md index 439d9c4d1a..5837897709 100644 --- a/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.zh.md +++ b/.agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.zh.md @@ -6,7 +6,7 @@ Status: proposed ## 问题 -用户可能希望在不改变当前会话主上下文的前提下,探索一个来自活跃会话的问题。现有原语无法提供这种产品形态:[会话存储 fork](../../archived/feature/2026-06-30-session-store-fork-api.md) 创建的是一个未绑定的会话,而 [fork subagent](../../implemented/feature/2026-06-21-subagent-capability-seam.zh.md) 是模型驱动的任务,其 transcript(文本记录)会折叠为一条工具结果。两者都不能给用户一个独立的对话,也都不能在父会话中同时记录结论和产生该结论的侧会话。 +用户可能希望在不改变当前会话主上下文的前提下,探索一个来自活跃会话的问题。现有原语无法提供这种产品形态:[会话存储 fork](../../archived/feature/2026-06-30-session-store-fork-api.md) 创建的是一个未绑定的会话,而 [fork subagent](../../../../packages/subagent/subagent-fork-in-process/README.zh.md) 是模型驱动的任务,其 transcript(文本记录)会折叠为一条工具结果。两者都不能给用户一个独立的对话,也都不能在父会话中同时记录结论和产生该结论的侧会话。 ## 提案 @@ -36,6 +36,6 @@ Status: proposed ## 风险 -- 只读行为在 `tools/pre-execute` 拒绝门禁强制执行之前仅为建议性质;[拦截点](../../implemented/feature/2026-06-30-interception-extension-points.zh.md)可在不改变本机制的前提下添加该门禁。 +- 只读行为在 `tools/pre-execute` 拒绝门禁强制执行之前仅为建议性质;[拦截点](../../../../docs/tool-execution-pipeline.zh.md)可在不改变本机制的前提下添加该门禁。 - 经过压缩(compaction)的源会话 fork 出的是其压缩视图,因此绑定的界面应当告知用户子会话继承的是摘要而非被替换的轮次。 - 反复的 handback 会消耗父会话上下文。每次合并的长度上限约束了单条笔记的大小;后续的合并整理属于上下文压缩的职责。 diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml b/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml index faa5a8965e..fbb5dea139 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.i18n.yaml @@ -15,8 +15,8 @@ en: 9650ef8f65049b7d zh: c0925b701a6b681c /agent-note-task-surface-for-structured-session-interaction/tool-and-presentation-contract: - en: 836a14533a918d95 - zh: 0522d458992c5355 + en: 13f923b5958b5f6d + zh: c8aa92cac29769fd /agent-note-task-surface-for-structured-session-interaction/submission-contract: en: 480c434bc0825063 zh: bbcace61f8d82188 @@ -33,8 +33,8 @@ en: 121ceb5920763980 zh: 7d7c1576faf4f331 /agent-note-task-surface-for-structured-session-interaction/acceptance-criteria: - en: e5db6c885617dd5d - zh: 0a54a00d2a195bfb + en: 2b278bc21a87232a + zh: 49d0556ec77b542f /agent-note-task-surface-for-structured-session-interaction/risks: en: 84a297fcdef9aaad zh: a9ce38547d650e55 diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.md index 749ce9e40e..bc8b0e9ae7 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.md @@ -81,9 +81,9 @@ Limits are schema-backed configuration on the Task Surface service. The initial `show_task_surface` accepts `{ model: TaskSurfaceModelV1 }`. The Host parses and normalizes the complete model, rejects the call when that Session already has an open Task Surface, mints `surfaceId`, and returns canonical `{ surfaceId, model }` with the normalized model. `presentationMeta` persists `value.model`, so the projector and executor cannot disagree about normalization. The Native result names the Surface and explains that an ordinary message bypasses it when the client cannot render the panel. The tool then calls `exec.concludeTurn()` so the agent does not continue past the requested human checkpoint. -The tool definition omits `isConcurrencySafe`. Under the existing tool-registry contract, omission classifies every call as an exclusive ordering barrier; no new `ToolDefinition` field is introduced. The tool is composed only in Web profiles that mount both the Host service and Web renderer. Version 1 supports `native` and `both` tool modes; a `ptc`-only profile does not advertise it because PTC mode dispatch is nested and cannot carry its presentation metadata to the outer result. +The tool definition omits `isConcurrencySafe`. Under the existing tool-registry contract, omission classifies every call as an exclusive ordering barrier; no new `ToolDefinition` field is introduced. The tool is composed only in Web profiles that mount both the Host service and Web renderer. Version 1 supports `native` tool mode; a `ptc`-only profile does not advertise it because PTC mode dispatch is nested and cannot carry its presentation metadata to the outer result. -The browser-safe domain package imports the type-only `Branded` primitive from `@deepseek-ai/dsh-brand` and owns all three Task Surface IDs. The canonical value is execution-local under the [canonical tool output contract](../../implemented/architecture/2026-07-20-canonical-tool-output-contract.md). Replay therefore uses `output.presentationMeta(args, value)` to persist this tagged payload with `tool/result.meta`: +The browser-safe domain package imports the type-only `Branded` primitive from `@deepseek-ai/dsh-brand` and owns all three Task Surface IDs. The canonical value is execution-local under the [canonical tool output contract](../../../../packages/core/tools/README.md). Replay therefore uses `output.presentationMeta(args, value)` to persist this tagged payload with `tool/result.meta`: ```ts import type { Branded } from '@deepseek-ai/dsh-brand' @@ -248,7 +248,7 @@ The implementation depends on the existing message log, canonical tool output, t ## Acceptance criteria -- A real model in `native` or `both` mode can call one stable `show_task_surface` schema, the call ends its turn, and a capable Web client renders the same normalized model live and after replay; `ptc`-only mode does not advertise it. +- A real model in `native` mode can call one stable `show_task_surface` schema, the call ends its turn, and a capable Web client renders the same normalized model live and after replay; `ptc`-only mode does not advertise it. - The static `TaskSurfaceDock` is the only editor and remains actionable for an active result outside the loaded history window; the keyed toolview remains a read-only transcript summary and replay. A composer takeover hides the still-mounted Dock, preserves its draft, and reveals the same owner after release. - Submitting produces exactly one visible user message per `submissionId`, starts the next turn through normal queue admission, and retains exact branded occurrence correlation while keeping `source.kind: 'user'`; dismissing records one log event and starts no turn. - The queued client row retains the correlated message source. `getActive` exposes `queued` or `claiming` across same-process reconnect; commit closes the projection, while explicit discard clears pending state and leaves the Surface open. Queue-row disappearance alone changes no UI state. Edit and steer are rejected, and remove succeeds only before claim. diff --git a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md index 4a63e671ff..3bb982f032 100644 --- a/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md +++ b/.agents/notes/proposed/feature/2026-08-04-task-surface.zh.md @@ -81,9 +81,9 @@ Task Surface 服务通过受 schema 校验的配置定义限制。初始默认 `show_task_surface` 接收 `{ model: TaskSurfaceModelV1 }`。Host 解析并规范化完整模型;若该会话已有一个打开的 Task Surface,则拒绝调用;否则生成 `surfaceId`,并返回带规范化模型的规范值 `{ surfaceId, model }`。`presentationMeta` 持久化 `value.model`,使投影器和执行器不会对规范化结果产生分歧。Native 结果会指明该 Surface,并说明客户端无法渲染面板时,可以通过普通消息绕过它。随后工具调用 `exec.concludeTurn()`,防止 agent 越过所要求的人工检查点继续执行。 -工具定义省略 `isConcurrencySafe`。根据现有工具注册表约定,省略该字段会将每次调用归类为独占排序屏障,无需新增 `ToolDefinition` 字段。该工具只会组装到同时挂载 Host 服务和 Web 渲染器的 Web profile 中。版本 1 支持 `native` 和 `both` 工具模式;仅支持 `ptc` 的 profile 不会向模型公布该工具,因为 PTC mode 分发属于嵌套调用,无法把呈现元数据传到外层结果。 +工具定义省略 `isConcurrencySafe`。根据现有工具注册表约定,省略该字段会将每次调用归类为独占排序屏障,无需新增 `ToolDefinition` 字段。该工具只会组装到同时挂载 Host 服务和 Web 渲染器的 Web profile 中。版本 1 支持 `native` 工具模式;仅支持 `ptc` 的 profile 不会向模型公布该工具,因为 PTC mode 分发属于嵌套调用,无法把呈现元数据传到外层结果。 -浏览器安全的领域包从 `@deepseek-ai/dsh-brand` 以仅类型方式导入 `Branded` 原语,并拥有全部三个 Task Surface ID。根据[规范工具输出约定](../../implemented/architecture/2026-07-20-canonical-tool-output-contract.zh.md),规范值仅存在于本次执行中。因此,回放通过 `output.presentationMeta(args, value)` 将以下带标签的载荷随 `tool/result.meta` 一并持久化: +浏览器安全的领域包从 `@deepseek-ai/dsh-brand` 以仅类型方式导入 `Branded` 原语,并拥有全部三个 Task Surface ID。根据[规范工具输出约定](../../../../packages/core/tools/README.zh.md),规范值仅存在于本次执行中。因此,回放通过 `output.presentationMeta(args, value)` 将以下带标签的载荷随 `tool/result.meta` 一并持久化: ```ts import type { Branded } from '@deepseek-ai/dsh-brand' @@ -248,7 +248,7 @@ Web 插件将未提交值保存在一个有界、按会话持久化的 slot stor ## 验收标准 -- 在 `native` 或 `both` 工具模式下,真实模型可以调用一个稳定的 `show_task_surface` schema;调用结束当前轮次;具备相应能力的 Web 客户端在实时运行和回放后都能渲染同一份规范化模型;仅支持 `ptc` 的模式不会向模型公布该工具。 +- 在 `native` 工具模式下,真实模型可以调用一个稳定的 `show_task_surface` schema;调用结束当前轮次;具备相应能力的 Web 客户端在实时运行和回放后都能渲染同一份规范化模型;仅支持 `ptc` 的模式不会向模型公布该工具。 - 静态 `TaskSurfaceDock` 是唯一的编辑器,即使活动结果位于已加载历史窗口之外也仍可操作;带 key 的 toolview 始终是 transcript 的只读摘要和回放。composer 接管会隐藏仍处于挂载状态的 Dock、保留其草稿,并在接管释放后重新显示同一个所有者。 - 每个 `submissionId` 的提交操作恰好生成一条可见用户消息,通过普通队列接纳开始下一轮,并在保留 `source.kind: 'user'` 的同时维持带品牌类型的确切调用实例关联;关闭操作记录一条日志事件,且不启动轮次。 - 客户端排队行保留已关联的消息来源。`getActive` 可在同一进程的重新连接前后公开 `queued` 或 `claiming`;持久化完成后会关闭投影,显式丢弃则会清除待处理状态并让 Surface 保持打开。队列行消失本身不会改变任何 UI 状态。系统会拒绝编辑和 steering,且移除操作只能在认领前成功。 diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.i18n.yaml b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.i18n.yaml index 540d081a58..1e0038bd15 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.i18n.yaml @@ -12,8 +12,8 @@ en: 264c54ffe91f3193 zh: 005512e1c498ccc1 /agent-note-desktop-mandatory-update-api/proposal/endpoint-and-access: - en: 6c82820724930bb5 - zh: 5f6a0bbfd6fb6b16 + en: c9f53773a42e779e + zh: a50a6e98eb92c479 /agent-note-desktop-mandatory-update-api/proposal/request: en: fb66b137a600f3a6 zh: 83f5c74a1716d57c diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.md b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.md index 429547c85b..1e86503c43 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.md +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.md @@ -14,7 +14,7 @@ Use a guest-accessible policy endpoint. This document owns its Desktop request a ### Endpoint and access -The [client decision](../../implemented/feature/2026-09-11-desktop-mandatory-update-client.md) owns the implemented polling and blocking UI. This API proposal remains active for backend deployment and live integration; local fixtures do not certify the service. +The [client reference](../../../../apps/desktop/README.md) owns the implemented polling and blocking UI. This API proposal remains active for backend deployment and live integration; local fixtures do not certify the service. ```http GET /api/v0/check_client_update diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.zh.md b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.zh.md index e2b992f746..ef2fa52f65 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.zh.md +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-mandatory-update-api.zh.md @@ -14,7 +14,7 @@ Desktop 在业务 server 为本地进程、用户尚未登录时也需要获取 ### 接口与访问 -[客户端决策](../../implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md)负责已实现的轮询和阻塞界面。本接口提案仍负责后端部署与真实联调;本地 fixture(测试前置数据)不能认证服务。 +[客户端参考](../../../../apps/desktop/README.zh.md)负责已实现的轮询和阻塞界面。本接口提案仍负责后端部署与真实联调;本地 fixture(测试前置数据)不能认证服务。 ```http GET /api/v0/check_client_update diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.i18n.yaml b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.i18n.yaml index 11052cfad3..0d88040d32 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.i18n.yaml +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.i18n.yaml @@ -9,8 +9,8 @@ en: 96e7c7a206e06f15 zh: 189133653543a98c /agent-note-desktop-update-policy-and-installation/proposal: - en: 73b06c4b868250d5 - zh: 947419d367f6c7e9 + en: 68c7ebc3e29eb4d8 + zh: c98d151b6ca44ecb /agent-note-desktop-update-policy-and-installation/proposal/release-and-backend-qualification: en: 03612a65e4a65939 zh: 4d45467930e9f364 diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.md b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.md index fa88a7dd79..97db1cebef 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.md +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.md @@ -10,7 +10,7 @@ Desktop usually runs a local dsh server, so remote business errors cannot reliab ## Proposal -This proposal records outstanding release and backend work; the [implemented client decision](../../implemented/feature/2026-09-11-desktop-mandatory-update-client.md) and [Desktop README](../../../../apps/desktop/README.md) own current behavior. +This proposal records outstanding release and backend work; the [Desktop README](../../../../apps/desktop/README.md) owns current client behavior. | Document | Owns | |---|---| diff --git a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.zh.md b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.zh.md index d9dc74f66a..0c7195dd63 100644 --- a/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.zh.md +++ b/.agents/notes/proposed/feature/2026-09-08-desktop-update-policy-and-installation.zh.md @@ -10,7 +10,7 @@ Desktop 通常运行本地 dsh server,无法依靠远程业务错误可靠触 ## 提案 -本文记录尚未完成的发布与后端工作;[已实施的客户端决策](../../implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md)和[桌面 README](../../../../apps/desktop/README.zh.md)负责当前行为。 +本文记录尚未完成的发布与后端工作;[桌面 README](../../../../apps/desktop/README.zh.md)负责当前客户端行为。 | 文档 | 负责内容 | |---|---| diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.i18n.yaml b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.i18n.yaml new file mode 100644 index 0000000000..6a09cae1de --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.i18n.yaml @@ -0,0 +1,43 @@ +# Bilingual-pair consistency record for 2026-10-03-pi-extensions-bridge.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.md +/agent-note-pi-extensions-bridge: + en: cd9f68956ba34672 + zh: 1fc649c5eb0d3cf4 +/agent-note-pi-extensions-bridge/problem: + en: eb4d3f10c3feedb4 + zh: a1a1a75a724c9a8b +/agent-note-pi-extensions-bridge/proposal: + en: 56b36193ca6f829d + zh: a31e226794c1ca90 +/agent-note-pi-extensions-bridge/proposal/packages-and-composition: + en: 2ab10852b93ad41b + zh: 3c14d410235fcfac +/agent-note-pi-extensions-bridge/proposal/loading-and-execution: + en: d95ece799bc6bab7 + zh: 1e19d3b49473a57e +/agent-note-pi-extensions-bridge/proposal/serving-the-api: + en: b1365f16d76fe27a + zh: 105b88f80eb8964d +/agent-note-pi-extensions-bridge/proposal/built-in-tool-calls-as-pi-sees-them: + en: c74e4662ad0a8ee7 + zh: 653729b41e983b13 +/agent-note-pi-extensions-bridge/proposal/unserved-features: + en: 70c74cf93526cfad + zh: cab3efc2c109ad5a +/agent-note-pi-extensions-bridge/proposal/extension-state: + en: be58f498b8d28548 + zh: 4cc057f794c288bf +/agent-note-pi-extensions-bridge/proposal/core-mechanisms-narrower-than-pi-s: + en: 9fa8297436332064 + zh: 9a000e157940b0f8 +/agent-note-pi-extensions-bridge/alternatives-considered: + en: a3769b960278c17f + zh: e7d86ff1aff243b8 +/agent-note-pi-extensions-bridge/acceptance-criteria: + en: 60894db423a54ec3 + zh: 13b435271f6a1371 +/agent-note-pi-extensions-bridge/risks: + en: 2b9a626b13c604f6 + zh: f67cb80d2b699989 diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.md b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.md new file mode 100644 index 0000000000..28faf5911a --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.md @@ -0,0 +1,142 @@ +# Agent Note: Pi extensions bridge + +Status: proposed + +English | [中文](2026-10-03-pi-extensions-bridge.zh.md) + +## Problem + +Pi 1.0.0 extensions are in-process TypeScript factories `(pi: ExtensionAPI) => void`. A factory subscribes to about forty lifecycle, context, tool, and provider events, registers model tools, slash commands, model providers, and MCP servers, and talks to the user through `ctx.ui` ([extensions guide](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/docs/extensions.md), [API types](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/core/extensions/types.ts)). The API is published and widely used: about 11,000 npm packages carried the `pi-package` keyword in October 2026. + +DSH has bridges for two other ecosystems: shell hooks written for Claude Code and Codex ([hooks group](../../../../packages/experimental/README.md)) and Claude Code mods ([compatibility page](../../../../docs/subsystems/claude-code-mods.md)). Pi's extension API is broader than either. It lets one in-process module rewrite the messages of a single request, edit tool arguments, append its own durable records, and continue a finished run. Serving that API from DSH extension points answers two questions: how much of an extension written for another harness runs unchanged, and which DSH core mechanisms are narrower than Pi's. + +## Proposal + +Add an experimental bridge that loads unmodified Pi extension sources and serves Pi's `ExtensionAPI` from existing DSH extension points. The bridge is a capability demonstration: it runs extensions as written where an existing DSH mechanism carries the behavior, leaves the rest unserved, and lists every difference on a compatibility page. Apart from the restricted plugin records that extension state needs (row 9 of the gap table), each core gap is closed by a separate change. + +### Packages and composition + +- `@deepseek-ai/dsh-experimental-pi-extensions` is the Host package and provides `ctx.piExtensions`. `@deepseek-ai/dsh-experimental-client-ui-pi-extensions` is the Web package. Neither is an optional bundle; an opt-in overlay composes them, as for the Claude Code mods bridge. +- One cordis.yml row names one extension source: a file, a directory with an `index.ts` or `index.js`, or a Pi package directory whose `package.json` lists `pi.extensions`. The row also carries the extension's flag values. Row order is Pi's load order, which is also its handler dispatch order. +- The bridge does not scan `~/.pi` or `.pi`, install packages, or decide project trust. A Pi package's skills, prompt templates, and themes are not loaded. + +### Loading and execution + +- The bridge has its own loader and event dispatcher. `@earendil-works/pi-coding-agent` is not a dependency. +- `jiti` imports the source, so TypeScript loads in a built installation. The loader resolves the imports Pi supplies to extensions: `typebox` and `@earendil-works/pi-ai` to the bridge's own dependencies, `@earendil-works/pi-coding-agent` to a module of the bridge that implements the helpers that need no Pi runtime (`defineTool`, the tool-event type guards, the truncation helpers, `withFileMutationQueue`, `getAgentDir`), and `@earendil-works/pi-tui` to the optional peer dependency when the deployment installed it. An extension that imports a module the loader cannot resolve fails to load with an error naming the module. +- Extension code runs in the Host process with the process's full authority, as mods do. +- Every Session with an in-process Agent gets its own runtime: the module is evaluated once per row, and its factory runs once per Session, as Pi re-runs factories for each Session. State an extension keeps at module scope is shared by all Sessions of the Host process; Pi also keeps one module instance across the Sessions of a process, but runs one Session at a time. A subagent's Session gets a separate runtime in Pi's `print` mode with `hasUI: false`, so a tool call a subagent makes passes the same extension handlers as the root's. + +### Serving the API + +| Pi API | DSH mechanism | +|---|---| +| `session_start`, `session_shutdown` | `agent/created` (awaited before the first turn), `agent/disposed` | +| `input` (after inbox admission; see row 7), `before_agent_start` | `system-prompt/assemble` and `agent/pre-step`: claimed user messages are transformed or dropped, a returned `systemPrompt` replaces the assembled prompt for that turn, and returned messages join the step | +| `tool_call` | `tools/pre-execute`: `block` becomes a denial; a handler that throws denies the call | +| `tool_result` | `tools/post-execute`: replaced content is installed as the result's content | +| `agent_before_settle` | `agent/turn-stopping`: returned custom entries are recorded, and a returned message with `continue` is steered into the turn | +| `agent_start`, `agent_end`, `agent_settled`, `turn_start`, `turn_end`, `message_start`, `message_end`, `tool_execution_start`, `tool_execution_end`, `model_select`, `session_info_changed`, `session_compact` | Session events and `agent/status`, delivered in order as notifications | +| `registerTool`, `ctx.executeTool` | Agent-scoped `ctx.tools` registrations; nested calls run the full tool pipeline; built-in names follow the translation below | +| `registerCommand`, `getCommands` | Agent-scoped `ctx.commands` | +| `sendUserMessage`, `sendMessage` | `agent.followup()`, `agent.steer()`, `agent.inject()` | +| `appendEntry`, `setLabel`, tool `details`, `ctx.sessionManager` | Ignorable Session records and `tool/result` metadata, read back as Pi entries | +| `getActiveTools`, `setActiveTools`, `getAllTools` | `ctx.tools.schemas()`; `setActiveTools` applies a scoped `ctx.tools.restrict()` to global tools and adds or removes the bridge's own Agent-scoped registrations, because a restriction does not hide scoped tools | +| `setModel`, `getThinkingLevel`, `setThinkingLevel` | An `agent/request` override | +| `exec` | `ctx.subprocess`, argv without a shell | +| `ctx.ui.select`, `confirm`, `input`, `editor` | `ctx.userQuestions` | +| `ctx.ui.notify`, `setStatus`, `setWidget`, `setTitle`, `setEditorText` | A Remote stream the Web package renders | +| `registerFlag`, `getFlag`, `events` | Row configuration; an in-process event bus per Session runtime | + +All Pi handlers for one Session run through one ordered queue. A notification never overtakes an earlier one, and an awaited hook such as `tool_call` runs after every notification queued before it, which keeps the order Pi's awaited dispatch gives handlers. + +The Web and Desktop GUI appear to extensions as Pi's `rpc` mode with `hasUI: true`; headless, SDK, and ACP Sessions appear as `print` mode. Extensions already handle both: terminal-only calls such as `ctx.ui.custom()` return what Pi's `rpc` mode returns. + +### Built-in tool calls as Pi sees them + +DSH and Pi share the names `bash`, `read`, `edit`, `write`, and `grep`, but not their arguments, and Pi's `find` is DSH's `glob`. A Pi safety extension reads `event.input.path` or `event.input.command`. The bridge therefore presents each call of these six tools to handlers with Pi's name and Pi's argument names, translated from the DSH call. Other tools pass through unchanged. A test loads the registered DSH tool schemas and fails when a mapped parameter is renamed or removed, or when a tool gains a parameter the table has not classified. + +The opposite direction uses the same table. `getActiveTools()` and `getAllTools()` report Pi names for the six tools and DSH names for every other tool, and `setActiveTools()` accepts the same names. `ctx.executeTool()` given a Pi name translates the arguments into the DSH call; an `edit` call with more than one entry in `edits` is refused, because one DSH `edit` call makes one replacement. An extension tool registered under a Pi built-in name replaces the corresponding DSH tool for that Agent, as it replaces Pi's own tool (`find` replaces `glob`). + +### Unserved features + +- An event DSH cannot raise is accepted at registration, never raised, and named in a warning when the extension loads. +- A `pi` or `ctx` member DSH cannot serve throws an error naming the member. +- Where Pi fails closed, the bridge does too. A `tool_call` handler that edits `event.input` gets the call denied with a reason, because the logged call would otherwise differ from the executed one. + +### Extension state + +Pi extensions rebuild their state on `session_start` from what they wrote earlier. The bridge keeps that state in the Session log so that it follows resume and fork: + +- `appendEntry` and `setLabel` write ignorable records whose type starts with `plugin:`. A restricted core operation, callable only from experimental packages, appends them. They are not `SessionEventMap` members, so the persistence catalog and its type history are unchanged. A build without the bridge retains and skips them when it reads the current format, as the [ignorable events decision](../../implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md) requires. A later format edge carries them on a best-effort basis: it keeps them under their names where it can and otherwise states which it drops, while the alpha historical edges refuse every unknown event ([decision](../../implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.md)). +- A tool's `details` is stored as the `tool/result` metadata the tool's output declaration projects. +- `sendMessage` content is logged as an ordinary user-sourced message with the extension's content unchanged. A companion ignorable record carries `customType`, `display`, and `details`. + +### Core mechanisms narrower than Pi's + +Ranked by how much of Pi's API each blocks, with the GitHub issue that tracks each. Rows 1 and 3 follow from DSH rules: a request is derived from the Session log, and the log is linear. The other rows are missing extension points. + +| # | Mechanism | Pi API blocked | DSH today | Issue | +|---|---|---|---|---| +| 1 | Per-request rewrite of model input | `context`, `context_with_system`, `before_provider_request`, `prepareLoadout` hidden declarations | The loop derives each request from the log and freezes it; `llm/stream` listeners read it. A rewrite needs an awaited hook and a core event that every log reader applies to that request. | #5661 | +| 2 | Tool-argument rewrite | `tool_call` editing `event.input` | `PreToolDecision` excludes input rewriting ([proposal](2026-06-30-pre-tool-input-rewrite.md)). | #5662 | +| 3 | Session tree and assistant-message replacement | `navigateTree`, `session_before_tree`, `session_tree`, `message_end` replacement | The log is linear, a fork creates a new Session, and an `assistant/message` cannot replace another. | #5663 | +| 4 | Awaited step-end and pre-commit hooks | `turn_end` entries and `continue`, `message_end` | `step/end` and `assistant/message` are committed events; a Session-event listener cannot append. | #5664 | +| 5 | Tool progress and result replacement | `onUpdate`, `tool_execution_update`, `tool_result` turning an error into a success, tool `usage` | `ToolRunContext` has no progress channel, and `tools/post-execute` cannot replace the value of a failed result. | #5665 | +| 6 | Per-tool exposure | `codemode`, `deferred`, and `hidden` exposure | Presentation is chosen per Agent, and a tool the model cannot see refuses execution. | #5666 | +| 7 | Input admission | `input` before the prompt is stored | A prompt is durable in the inbox before `agent/pre-step`; a swallowed prompt leaves a turn without a step. | #5667 | +| 8 | Logged model call by a plugin | `ctx.modelRegistry.complete`, `stream`, `streamSimple` | No Session event records a plugin's own model request, which also blocks `$.model.complete` for mods. | #5668 | +| 9 | Plugin-owned durable records | `appendEntry` | `Session.append()` accepts declared event types only. The restricted `plugin:` records close this for experimental packages. | #5669 | +| 10 | Compaction customization | `session_before_compact` | One `CompactionEngine` owns summarization, and `/compact` takes no instructions. | #5670 | +| 11 | Lifecycle reasons | `session_start` reasons `new`, `fork`, and `reload`; `session_shutdown` reasons | `agent/created` reports `startup` or `resume`; `SessionStartSource` also declares `clear` and `compact`, which nothing emits yet; `agent/disposed` carries no reason. | #5671 | +| 12 | Host-plugin display in the GUI | `ctx.ui.notify`, `setStatus`, `setWidget` | No shared mechanism; each bridge ships its own Client package. | #5672 | +| 13 | Command capabilities | Argument completion; `newSession`, `fork`, and `switchSession` moving the user to another Session | `ctx.commands` takes unstructured text and has no Client-side effects. | #5673 | +| 14 | Provider request hooks | `before_provider_headers`, `after_provider_response`, `provider_stream_event` | Headers, responses, and raw stream events stay inside each adapter. | #5674 | +| 15 | Fault isolation | An extension bug stops one Pi process | An uncaught exception in the Host disposes the application for every Session. | #5675 | + +Terminal rendering (`ctx.ui.custom`, custom editors, renderers, shortcuts) is outside this list: DSH's GUI is not a terminal, and Pi's own `rpc` mode does not serve these either. + +## Alternatives considered + +**Depend on `@earendil-works/pi-coding-agent` and reuse its loader and runner.** It would reproduce Pi's handler ordering and error rules exactly and supply every helper extensions import. It was rejected because the exercise is to carry Pi's API on DSH mechanisms, not to embed Pi's harness: the runner is constructed over Pi's `SessionManager` and `ModelRegistry`, the package brings Pi's whole dependency closure, and its built-in tool factories would run Pi's own file and shell implementations outside DSH's filesystem and sandbox policy. + +**A wrapper module per extension, like `defineMod`.** A Pi extension is already a default-exported factory with no manifest to replace, so a wrapper adds a step for every extension and prevents loading a published Pi package directory. + +**Discover Pi's own locations automatically.** Loading `~/.pi/agent/extensions` and project `.pi/extensions` executes code a DSH user did not compose, which needs a project-trust decision DSH does not have. The mods bridge removed its directory loader for the same composition reason. Discovery can be a later opt-in layer. + +**Run extensions in a separate process per Session.** An uncaught extension error would then reach the Host as an event. Every awaited hook would become an inter-process round trip, and Pi's edit-in-place event objects would have to be reproduced by sending changes back. Deferred until real extensions show the isolation is needed. + +**Evaluate each extension module once per Session.** Concurrent Sessions would then not share module-scope state. Rejected because Pi keeps one module instance across the Sessions of a process, so an extension may rely on module state surviving a Session switch, and per-Session evaluation repeats the transpilation for every Session and subagent. + +**Run extensions for root Sessions only.** It needs an explicit filter, since `agent/created` fires for every Agent, and it lets a subagent's tool calls bypass a Pi safety extension. + +**Pass DSH tool names and arguments to handlers unchanged.** Pi's own `protected-paths` example reads `event.input.path`; with DSH's `file_path` it throws, and a handler written slightly differently would allow the write. + +**Declare `pi/*` Session events and a `pi-extension` message source.** Correct attribution, but each is a required-on-read persistence type: the catalog and type history change, and a build without the bridge refuses the Session. + +**Keep extension state in a storage domain outside the log.** State would not follow a fork, and `ctx.sessionManager.getBranch()` would not show what the extension wrote. + +**Raise `context` and `before_provider_request` without logging their effect.** The model would see input that the Session log cannot reconstruct. + +**Refuse to load an extension that subscribes to an unserved event.** Most extensions use several features; the mods bridge already warns and keeps the rest working. + +**Build a shared GUI notification mechanism first.** It would also serve the mods bridge, but it is a core addition; the Web package of this bridge renders its own stream until that exists. + +## Acceptance criteria + +- Pi's MIT-licensed example extensions are committed unchanged as fixtures, and only those a test exercises. Through a real agent loop with a mock model: a permission extension denies a tool call and the model sees the reason; an extension tool is called by the model and its `details` survive a resumed Session; an extension command runs; an extension replaces the system prompt for a turn; `appendEntry` state is rebuilt on `session_start` after resume. +- A subagent's tool call reaches the handlers of its own runtime. +- The tool translation test fails when a mapped DSH tool parameter changes. +- A Loader composition test boots the bridge from a cordis.yml, and a disposal test shows every registration removed when the bridge unloads. +- A recorded-Session snapshot covers an extension-denied tool call and an extension tool call. +- `docs/subsystems/pi-extensions.md` lists every event and member with its status and every behavioral difference. +- The new packages' `src/` have 100% coverage, and `doc-sync` passes. + +## Risks + +- Extension code has the Host's full authority, and one uncaught error stops every Session. Mount only extensions you would run as a plugin. +- Fidelity rests on the bridge's reading of Pi 1.0.0 dispatch rules, checked against Pi's examples but not executed on Pi's code. A later Pi release can change an event or helper the bridge serves. +- An extension that imports a helper the bridge does not implement fails to load, even if it would have used that helper on one code path only. +- A future Session format edge that cannot carry plugin records loses extension state for Sessions written before it. +- Extensions that keep per-Session state at module scope mix the state of concurrent Sessions, including subagents. +- A message an extension sends is attributed to the user everywhere outside the Web package that reads the companion record. diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.zh.md b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.zh.md new file mode 100644 index 0000000000..855f7b160c --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-extensions-bridge.zh.md @@ -0,0 +1,142 @@ +# Agent Note: Pi 扩展桥接 + +Status: proposed + +[English](2026-10-03-pi-extensions-bridge.md) | 中文 + +## 问题 + +Pi 1.0.0 的扩展是进程内的 TypeScript 工厂函数 `(pi: ExtensionAPI) => void`。工厂函数订阅约四十个生命周期、上下文、工具与提供方事件,注册模型工具、斜杠命令、模型提供商与 MCP server,并通过 `ctx.ui` 与用户交互([扩展指南](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/docs/extensions.md)、[API 类型](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/core/extensions/types.ts))。该 API 已发布并被广泛使用:截至 2026 年 10 月,npm 上约有 11,000 个包带有 `pi-package` 关键字。 + +DSH 已为另外两个生态提供桥接:为 Claude Code 与 Codex 编写的 shell 钩子([hooks 组](../../../../packages/experimental/README.zh.md)),以及 Claude Code mods([兼容性页面](../../../../docs/subsystems/claude-code-mods.zh.md))。Pi 的扩展 API 比两者都宽。它允许一个进程内模块改写单次请求的消息、编辑工具参数、追加自己的持久记录,并让已经结束的运行继续。用 DSH 扩展点提供这套 API 可以回答两个问题:为另一个 harness 编写的扩展有多少能原样运行,以及 DSH 的哪些核心机制比 Pi 窄。 + +## 提案 + +新增一个实验性桥接,加载未修改的 Pi 扩展源码,并用现有的 DSH 扩展点提供 Pi 的 `ExtensionAPI`。该桥接是一次能力演示:凡现有 DSH 机制能承载的行为,就让扩展原样运行;其余部分不提供,并在兼容性页面列出每一处差异。除扩展状态所需的受限插件记录(缺口表第 9 行)外,每项核心缺口都由单独的变更弥补。 + +### 包与组合 + +- `@deepseek-ai/dsh-experimental-pi-extensions` 是 Host 包,提供 `ctx.piExtensions`。`@deepseek-ai/dsh-experimental-client-ui-pi-extensions` 是 Web 包。两者都不是可选 bundle;与 Claude Code mods 桥接一样,由一个按需启用的 overlay 组合它们。 +- cordis.yml 的一行指定一个扩展来源:一个文件、一个含 `index.ts` 或 `index.js` 的目录,或一个在 `package.json` 中列出 `pi.extensions` 的 Pi 包目录。该行同时携带扩展的 flag 值。行的顺序就是 Pi 的加载顺序,也是其 handler 的分发顺序。 +- 桥接不扫描 `~/.pi` 或 `.pi`,不安装包,也不判定项目信任。Pi 包中的 skill、提示词模板与主题不会被加载。 + +### 加载与执行 + +- 桥接有自己的 loader 与事件分发器。`@earendil-works/pi-coding-agent` 不是依赖。 +- 源码由 `jiti` 导入,因此 TypeScript 在已构建的安装中也能加载。loader 解析 Pi 提供给扩展的那些导入:`typebox` 与 `@earendil-works/pi-ai` 解析到桥接自己的依赖;`@earendil-works/pi-coding-agent` 解析到桥接的一个模块,它实现了不需要 Pi 运行时的辅助函数(`defineTool`、工具事件类型守卫、截断辅助函数、`withFileMutationQueue`、`getAgentDir`);`@earendil-works/pi-tui` 在部署安装了该可选对等依赖(peer dependency)时解析到它。扩展导入了 loader 无法解析的模块时加载失败,错误信息会指出该模块。 +- 与 mods 一样,扩展代码在 Host 进程中以进程的全部权限运行。 +- 每个拥有进程内 Agent 的 Session 都有自己的运行时:每行的模块只求值一次,其工厂函数对每个 Session 运行一次,正如 Pi 为每个 Session 重新运行工厂函数。扩展保存在模块作用域的状态由 Host 进程中的所有 Session 共享;Pi 同样在一个进程的多个 Session 之间保留同一个模块实例,但同一时间只运行一个 Session。subagent 的 Session 有独立的运行时,处于 Pi 的 `print` 模式且 `hasUI: false`,因此 subagent 发起的工具调用与根 Agent 的调用经过同样的扩展 handler。 + +### 提供 API + +| Pi API | DSH 机制 | +|---|---| +| `session_start`、`session_shutdown` | `agent/created`(在首个轮次之前等待完成)、`agent/disposed` | +| `input`(在 inbox 准入之后,见第 7 行)、`before_agent_start` | `system-prompt/assemble` 与 `agent/pre-step`:被认领的用户消息可被改写或丢弃,返回的 `systemPrompt` 替换该轮次已组装的提示词,返回的消息加入该步骤 | +| `tool_call` | `tools/pre-execute`:`block` 成为拒绝;handler 抛出异常时拒绝该调用 | +| `tool_result` | `tools/post-execute`:被替换的内容安装为结果内容 | +| `agent_before_settle` | `agent/turn-stopping`:返回的自定义条目被记录,带 `continue` 返回的消息被 steer 进该轮次 | +| `agent_start`、`agent_end`、`agent_settled`、`turn_start`、`turn_end`、`message_start`、`message_end`、`tool_execution_start`、`tool_execution_end`、`model_select`、`session_info_changed`、`session_compact` | 会话事件与 `agent/status`,作为通知按顺序送达 | +| `registerTool`、`ctx.executeTool` | Agent 作用域的 `ctx.tools` 注册;嵌套调用走完整的工具管线;内置名称遵循下文的转换 | +| `registerCommand`、`getCommands` | Agent 作用域的 `ctx.commands` | +| `sendUserMessage`、`sendMessage` | `agent.followup()`、`agent.steer()`、`agent.inject()` | +| `appendEntry`、`setLabel`、工具 `details`、`ctx.sessionManager` | ignorable Session 记录与 `tool/result` 元数据,读回时还原为 Pi 条目 | +| `getActiveTools`、`setActiveTools`、`getAllTools` | `ctx.tools.schemas()`;`setActiveTools` 对全局工具使用作用域内的 `ctx.tools.restrict()`,并增删桥接自己在 Agent 作用域的注册,因为限制不会隐藏作用域内的工具 | +| `setModel`、`getThinkingLevel`、`setThinkingLevel` | `agent/request` 覆盖 | +| `exec` | `ctx.subprocess`,不经 shell 的 argv | +| `ctx.ui.select`、`confirm`、`input`、`editor` | `ctx.userQuestions` | +| `ctx.ui.notify`、`setStatus`、`setWidget`、`setTitle`、`setEditorText` | 由 Web 包渲染的 Remote 流 | +| `registerFlag`、`getFlag`、`events` | 行配置;每个 Session 运行时一个进程内事件总线 | + +同一个 Session 的所有 Pi handler 都经过一个有序队列。通知不会越过更早的通知;`tool_call` 这类需要等待的钩子,在排在它之前的所有通知之后运行。这样就保持了 Pi 的等待式分发给 handler 的顺序。 + +Web 与 Desktop GUI 对扩展呈现为 Pi 的 `rpc` 模式且 `hasUI: true`;headless、SDK 与 ACP Session 呈现为 `print` 模式。扩展本来就处理这两种模式:`ctx.ui.custom()` 这类仅限终端的调用返回 Pi 的 `rpc` 模式所返回的值。 + +### Pi 视角下的内置工具调用 + +DSH 与 Pi 共有 `bash`、`read`、`edit`、`write` 与 `grep` 这些名称,但参数不同,而 Pi 的 `find` 是 DSH 的 `glob`。Pi 的安全类扩展会读取 `event.input.path` 或 `event.input.command`。因此,桥接把这六个工具的每次调用以 Pi 的名称和 Pi 的参数名呈现给 handler,由 DSH 调用转换而来。其他工具原样传递。有一项测试加载已注册的 DSH 工具 schema,在被映射的参数改名或被移除时失败,在工具新增了映射表尚未归类的参数时也失败。 + +反方向使用同一张映射表。`getActiveTools()` 与 `getAllTools()` 对这六个工具报告 Pi 名称,对其他工具报告 DSH 名称;`setActiveTools()` 接受同样的名称。`ctx.executeTool()` 收到 Pi 名称时,把参数转换为 DSH 调用;`edits` 中多于一项的 `edit` 调用会被拒绝,因为一次 DSH `edit` 调用只做一处替换。以 Pi 内置名称注册的扩展工具会替换该 Agent 中对应的 DSH 工具,正如它在 Pi 中替换 Pi 自己的工具(`find` 替换 `glob`)。 + +### 不提供的功能 + +- DSH 无法触发的事件:注册时接受,永不触发,并在扩展加载时的警告中列出名称。 +- DSH 无法提供的 `pi` 或 `ctx` 成员:抛出指明该成员的错误。 +- Pi 在哪里失败关闭,桥接也在哪里失败关闭。编辑 `event.input` 的 `tool_call` handler 会使该调用被拒绝并附带原因,否则已记录的调用会与实际执行的调用不一致。 + +### 扩展状态 + +Pi 扩展在 `session_start` 时根据自己此前写入的内容重建状态。桥接把这些状态保存在 Session 日志中,使其跟随 resume 与 fork: + +- `appendEntry` 与 `setLabel` 写入类型以 `plugin:` 开头的 ignorable 记录。追加它们的是一项受限的核心操作,只能由实验性包调用。它们不是 `SessionEventMap` 成员,因此持久化目录及其类型历史不变。不带该桥接的构建在读取当前格式时保留并跳过它们,这是 [ignorable 事件决策](../../implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md)的要求。之后的格式迁移边以尽力而为的方式携带它们:能保留时以原名称保留,否则说明丢弃哪些;Alpha 历史格式迁移边则拒绝所有未知事件([决策](../../implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.zh.md))。 +- 工具的 `details` 保存为 `tool/result` 元数据,由工具的输出声明投影得出。 +- `sendMessage` 的内容记录为普通的用户来源消息,扩展的内容保持不变。一条伴随的 ignorable 记录携带 `customType`、`display` 与 `details`。 + +### 比 Pi 窄的核心机制 + +按各自阻塞的 Pi API 数量排序,并列出跟踪每一项的 GitHub issue。第 1、3 行源自 DSH 的规则:请求由 Session 日志派生,而日志是线性的。其余各行是缺失的扩展点。 + +| # | 机制 | 被阻塞的 Pi API | DSH 现状 | Issue | +|---|---|---|---|---| +| 1 | 按请求改写模型输入 | `context`、`context_with_system`、`before_provider_request`、`prepareLoadout` 隐藏声明 | 循环从日志派生每个请求并将其冻结;`llm/stream` 监听器只读取它。改写需要一个可等待的钩子,以及一个每个日志读取方都会应用到该请求上的核心事件。 | #5661 | +| 2 | 改写工具参数 | 编辑 `event.input` 的 `tool_call` | `PreToolDecision` 不包含输入改写([提案](2026-06-30-pre-tool-input-rewrite.zh.md))。 | #5662 | +| 3 | Session 树与 assistant 消息替换 | `navigateTree`、`session_before_tree`、`session_tree`、`message_end` 替换 | 日志是线性的,fork 会创建新的 Session,`assistant/message` 不能替换另一条。 | #5663 | +| 4 | 可等待的步骤结束钩子与提交前钩子 | `turn_end` 的条目与 `continue`、`message_end` | `step/end` 与 `assistant/message` 是已提交的事件;会话事件监听器不能追加事件。 | #5664 | +| 5 | 工具进度与结果替换 | `onUpdate`、`tool_execution_update`、把错误变为成功的 `tool_result`、工具 `usage` | `ToolRunContext` 没有进度通道,`tools/post-execute` 不能替换失败结果的值。 | #5665 | +| 6 | 按工具设置可见性 | `codemode`、`deferred` 与 `hidden` exposure | 呈现方式按 Agent 选择,模型看不到的工具拒绝执行。 | #5666 | +| 7 | 输入准入 | 在提示词被保存之前的 `input` | 提示词在 `agent/pre-step` 之前就已持久保存在 inbox 中;被吞掉的提示词会留下一个没有步骤的轮次。 | #5667 | +| 8 | 插件发起且被记录的模型调用 | `ctx.modelRegistry.complete`、`stream`、`streamSimple` | 没有会话事件记录插件自己的模型请求,这同样阻塞了 mods 的 `$.model.complete`。 | #5668 | +| 9 | 插件拥有的持久记录 | `appendEntry` | `Session.append()` 只接受已声明的事件类型。受限的 `plugin:` 记录为实验性包补上了这一点。 | #5669 | +| 10 | 定制压缩 | `session_before_compact` | 摘要由唯一的 `CompactionEngine` 负责,`/compact` 不接受指令。 | #5670 | +| 11 | 生命周期原因 | `session_start` 的 `new`、`fork` 与 `reload` 原因;`session_shutdown` 的原因 | `agent/created` 报告 `startup` 或 `resume`;`SessionStartSource` 还声明了尚无生产者的 `clear` 与 `compact`;`agent/disposed` 不带原因。 | #5671 | +| 12 | Host 插件在 GUI 中的显示 | `ctx.ui.notify`、`setStatus`、`setWidget` | 没有共享机制;每个桥接各自附带 Client 包。 | #5672 | +| 13 | 命令能力 | 参数补全;把用户带到另一个 Session 的 `newSession`、`fork` 与 `switchSession` | `ctx.commands` 接受非结构化文本,没有 Client 侧效果。 | #5673 | +| 14 | 提供方请求钩子 | `before_provider_headers`、`after_provider_response`、`provider_stream_event` | 请求头、响应与原始流事件都留在各适配器内部。 | #5674 | +| 15 | 故障隔离 | 扩展的缺陷只中止一个 Pi 进程 | Host 中未捕获的异常会为所有 Session 释放整个应用。 | #5675 | + +终端渲染(`ctx.ui.custom`、自定义编辑器、渲染器、快捷键)不在此列:DSH 的 GUI 不是终端,Pi 自己的 `rpc` 模式也不提供这些。 + +## 考虑过的替代方案 + +**依赖 `@earendil-works/pi-coding-agent` 并复用其 loader 与 runner。** 这样可以精确复现 Pi 的 handler 顺序与错误规则,并提供扩展导入的全部辅助函数。否决的原因是:这项工作要用 DSH 的机制承载 Pi 的 API,而不是嵌入 Pi 的 harness。该 runner 构造于 Pi 的 `SessionManager` 与 `ModelRegistry` 之上,这个包会带入 Pi 的全部依赖闭包,而且它的内置工具工厂会在 DSH 的文件系统与沙箱策略之外运行 Pi 自己的文件与 shell 实现。 + +**像 `defineMod` 那样为每个扩展写一个包装模块。** Pi 扩展本身就是默认导出的工厂函数,没有需要替代的 manifest,因此包装只会给每个扩展多加一步,并且无法直接加载已发布的 Pi 包目录。 + +**自动发现 Pi 自己的位置。** 加载 `~/.pi/agent/extensions` 与项目的 `.pi/extensions` 会执行 DSH 用户并未组合进来的代码,这需要 DSH 尚不具备的项目信任判定。mods 桥接出于同样的组合原因移除了它的目录 loader。发现机制可以作为之后按需启用的一层。 + +**为每个 Session 在独立进程中运行扩展。** 这样未捕获的扩展错误会以事件的形式到达 Host。但每个需要等待的钩子都会变成一次跨进程往返,Pi 的就地编辑事件对象也必须通过回传变更来复现。待真实扩展表明确实需要这种隔离时再做。 + +**为每个 Session 各求值一次扩展模块。** 这样并发的 Session 不会共享模块作用域的状态。否决的原因是:Pi 在一个进程的多个 Session 之间保留同一个模块实例,扩展可能依赖模块状态在切换 Session 后仍然存在;而且按 Session 求值会为每个 Session 与 subagent 重复转译。 + +**只为根 Session 运行扩展。** 这需要显式过滤,因为 `agent/created` 对每个 Agent 都会触发;它还会让 subagent 的工具调用绕过 Pi 的安全类扩展。 + +**把 DSH 的工具名称与参数原样传给 handler。** Pi 自己的 `protected-paths` 示例读取 `event.input.path`;遇到 DSH 的 `file_path` 时它会抛出异常,而写法稍有不同的 handler 会放行这次写入。 + +**声明 `pi/*` 会话事件与 `pi-extension` 消息来源。** 归属是正确的,但两者都是读取时必需的持久化类型:目录与类型历史会改变,没有该桥接的构建会拒绝这个 Session。 + +**把扩展状态保存在日志之外的 storage domain 中。** 状态不会跟随 fork,`ctx.sessionManager.getBranch()` 也看不到扩展写入的内容。 + +**触发 `context` 与 `before_provider_request` 但不记录其效果。** 模型会看到 Session 日志无法重建的输入。 + +**拒绝加载订阅了不提供的事件的扩展。** 多数扩展同时使用多项功能;mods 桥接已经采用“发出警告并让其余部分继续工作”的做法。 + +**先构建共享的 GUI 通知机制。** 它也能服务 mods 桥接,但属于核心新增;在它出现之前,本桥接的 Web 包渲染自己的流。 + +## 验收标准 + +- Pi 以 MIT 许可发布的示例扩展原样提交为 fixture,且只提交有测试覆盖的那些。在使用 mock 模型的真实 agent loop 中:权限扩展拒绝一次工具调用,模型看到原因;模型调用扩展工具,其 `details` 在恢复的 Session 中仍然存在;扩展命令可以运行;扩展替换某个轮次的系统提示词;resume 之后,`appendEntry` 的状态在 `session_start` 时被重建。 +- subagent 的工具调用到达它自己运行时的 handler。 +- 被映射的 DSH 工具参数变化时,工具转换测试失败。 +- Loader 组合测试从 cordis.yml 启动桥接,释放测试表明桥接卸载时每项注册都被移除。 +- 一个录制的 Session 快照覆盖一次被扩展拒绝的工具调用和一次扩展工具调用。 +- `docs/subsystems/pi-extensions.md` 列出每个事件与成员及其状态,以及每一处行为差异。 +- 新包的 `src/` 覆盖率为 100%,`doc-sync` 通过。 + +## 风险 + +- 扩展代码拥有 Host 的全部权限,一个未捕获的错误会中止所有 Session。只挂载你愿意作为插件运行的扩展。 +- 保真度取决于桥接对 Pi 1.0.0 分发规则的理解;它经过 Pi 示例的检验,但没有在 Pi 的代码上执行。之后的 Pi 版本可能改变桥接所提供的事件或辅助函数。 +- 扩展导入了桥接未实现的辅助函数时会加载失败,即使它只在某一条代码路径上用到该函数。 +- 无法携带插件记录的未来 Session 格式迁移边,会使迁移之前写入的 Session 丢失扩展状态。 +- 把按 Session 区分的状态放在模块作用域的扩展,会混用并发 Session(包括 subagent)的状态。 +- 扩展发送的消息,在读取伴随记录的 Web 包之外的所有地方,都归属于用户。 diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.i18n.yaml b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.i18n.yaml new file mode 100644 index 0000000000..18c0105d65 --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.i18n.yaml @@ -0,0 +1,46 @@ +# Bilingual-pair consistency record for 2026-10-03-pi-plugins-compatibility.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.md +/agent-note-pi-plugins-compatibility-levels-of-support: + en: 68e63fd54584ecbc + zh: e1cedbf64975bc53 +/agent-note-pi-plugins-compatibility-levels-of-support/problem: + en: f98ddabdc0ea831b + zh: e695439507286576 +/agent-note-pi-plugins-compatibility-levels-of-support/proposal: + en: 37a27883d679bb6a + zh: 62c60e0cd06bf8fb +/agent-note-pi-plugins-compatibility-levels-of-support/what-a-pi-plugin-is: + en: fd37c3aa5d6c092b + zh: e39a84ef9aabaa48 +/agent-note-pi-plugins-compatibility-levels-of-support/level-1-session-facets: + en: 5e08e808af0a741a + zh: eae79e9c8f1fc301 +/agent-note-pi-plugins-compatibility-levels-of-support/level-2-presentation-facets-on-the-host: + en: 6a4b96426808d653 + zh: c341dd2ba594ed45 +/agent-note-pi-plugins-compatibility-levels-of-support/level-3-transcript-and-stored-selection: + en: defc273e0759bb9f + zh: 59a4cf0dd0b46faa +/agent-note-pi-plugins-compatibility-levels-of-support/level-4-presentation-facets-in-the-browser: + en: 5b2fcfdb38eb6121 + zh: 1d583d9a6486d2dd +/agent-note-pi-plugins-compatibility-levels-of-support/level-5-parity: + en: 657aa240a06f663f + zh: 74ca5260a5a3d215 +/agent-note-pi-plugins-compatibility-levels-of-support/runtime-differences-the-plugin-model-exposes: + en: 114b54fae1ff94cd + zh: 0d0dde9f0563b27b +/agent-note-pi-plugins-compatibility-levels-of-support/chord-mechanisms-to-evaluate-for-dsh: + en: e44ed4dcd7fc9eb9 + zh: 825a41daa69931f9 +/agent-note-pi-plugins-compatibility-levels-of-support/alternatives-considered: + en: 390883362b220cfb + zh: 1c072dd19744a92e +/agent-note-pi-plugins-compatibility-levels-of-support/acceptance-criteria: + en: 386ae81c30ec7cfc + zh: 6f8bc4257fa8898c +/agent-note-pi-plugins-compatibility-levels-of-support/risks: + en: b6410c36a55acc47 + zh: e7de4e26be7a3c58 diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.md b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.md new file mode 100644 index 0000000000..352a9b3f40 --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.md @@ -0,0 +1,140 @@ +# Agent Note: Pi plugins compatibility — levels of support + +Status: proposed + +English | [中文](2026-10-03-pi-plugins-compatibility.zh.md) + +## Problem + +Pi 1.0.0 has two separate mechanisms for running customization code. Extensions are in-process TypeScript factories with a published API; the [Pi extensions bridge note](2026-10-03-pi-extensions-bridge.md) covers them. Plugins are a newer mechanism built on Pi's Chord composition runtime: a plugin package contributes facets, and each facet runs in the process its name selects. This note covers plugins. + +A compatibility layer for Pi plugins is not worth building today, for two reasons the upstream sources state themselves. + +- **The API is unpublished and unstable.** The plugin stack runs only with `PI_EXPERIMENTAL=1`. The `./experimental/plugin` export of `@earendil-works/pi-coding-agent` has only a `source` condition, and the npm tarball excludes its build output. Chord's plan says it "is not a stable public API contract yet" ([PLANNING.md](https://github.com/earendil-works/pi/blob/v1.0.0/packages/chord/PLANNING.md)), and pi-durable, which the Session worker runs on, says its "API changes without notice between releases" ([README](https://github.com/earendil-works/pi/blob/v1.0.0/packages/durable/README.md)). The [services README](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/services/README.md) lists `AgentController.navigate()`, `nextRun()`, and `resume()` as dropped. +- **A plugin can do less than an extension.** The Session worker builds its tool, hook, and prompt registry itself and does not expose it as a service, so a facet cannot register a model tool, intercept a tool call, or change the prompt. Pi ships one example plugin. + +Deferring the work without a record loses two results of the investigation: the mapping from Pi's plugin services onto DSH services, which decides what each increment of support costs, and the DSH runtime properties that differ from Chord's, which matter to DSH independently of Pi. + +## Proposal + +Treat Pi-plugin compatibility as six levels. Each level names what a plugin author gets, the upstream change that makes it worth building, and the DSH work it needs. DSH stays at level 0 until the trigger of a higher level occurs. A level includes the levels below it, except that level 4 replaces the Host-side presentation of level 2. + +| Level | Support | Upstream trigger | DSH work | +|---|---|---|---| +| 0 | None. This note is the only artifact. | — | Re-read the upstream status when Pi releases. | +| 1 | `session` facets run in one Chord host per DSH Session. `AgentController`, `Models`, and `SessionPlugins` are served over DSH services. | Pi ships the plugin API in its npm package, or removes the `PI_EXPERIMENTAL` requirement. | One experimental Host package. No core change. | +| 2 | `tui` facets run on the Host. Their slash commands become DSH commands, `select` becomes a user question, and `showStatus` a status line. Pi's example plugin runs unmodified. | Same as level 1. | Adapters, plus the Web status display of the Pi extensions bridge. | +| 3 | `Transcript` is served, and a Session's plugin selection is stored with the Session. | A plugin that consumes `Transcript` exists, or pi-durable declares `ConversationView` stable. | A projection from the Session log to `ConversationView`, and a per-Session selection record. | +| 4 | `tui` facets run in the browser and reach Session services through a generic Chord bridge. | Chord commits to browser support, and Pi defines a presentation facet that is not terminal-specific. | One generic JSON Remote endpoint, a browser facet loader, and argument text for Client commands. | +| 5 | Parity: one worker per Session, and plugins register model tools and hooks. | Pi exposes its tool and hook registry to facets. | Per-Session process isolation, and the tool and hook mappings of the Pi extensions bridge. | + +## What a Pi plugin is + +A plugin is a package directory. Its `package.json` may carry `chord.facets`, which maps a facet name to an entry file or disables it; without the field, Pi uses `src/session.ts` and `src/tui.ts` when they exist. An entry default-exports `{ id, setup(env) }`. `setup` is synchronous and only declares: `env.use` and `env.observe` acquire services, `env.provide` and `env.provideMany` publish them, `env.replicatedState` creates shared state, and `env.own`, `env.onActivate`, and `env.onDeactivate` attach lifecycle work. The Chord host validates the complete dependency graph, activates providers before consumers, and can replace a facet whose declared services are unchanged while consumers keep their service handles. + +The server builds each entry with Chord's esbuild bundler into a content-addressed CommonJS file listed in `chord-facets.json`. The Session worker loads the `session` entry with `node:vm` after checking its SHA-256 digest, and `require` resolves only the externals the entry declared. The `tui` entry reaches each client as a JSON artifact in a service response and loads the same way. The loader is not a sandbox: plugin code runs with the process's full authority. + +`pi server -e ` sets the server's default plugins, and `pi client -e ` selects plugins for one Session. Pi stores a Session's selection and refuses a different selection while that Session's worker is running. + +Pi serves these to facets: + +| Scope | Service | Members | +|---|---|---| +| server | `SessionDirectory`, `SessionManagement`, `PresentationPlugins` | Session list state; create, remove, attach, detach; build and reload of presentation artifacts | +| session | `AgentController` | `prompt`, `steer`, `followUp`, `cancelQueued`, `abort`, `compact`, `waitForPrompt` | +| session | `Models` | replicated catalog and selection state; `select`, `selectThinking`, `cycleThinking`, `getThinkingLevels`, `refresh` | +| session | `Transcript` | the root conversation's pi-durable `ConversationView` as replicated state | +| session | `SessionPlugins` | `reload` | +| presentation, process-local | `SlashCommands` | `register`, `replace`, `list`, `subscribe`; a command has a name, description, argument hint, optional argument completions, and `run(args, context)` | +| presentation, process-local | `PresentationUI` | `select(title, items, selected, context)`, `showStatus(message, context)` | + +A service that is not process-local may contain only replicated JSON state and methods whose arguments and result are JSON and whose last parameter is a Chord `Context`. + +## Level 1: session facets + +The package depends on `@earendil-works/chord` at one exact version. It builds each selected package with `bundleFacetPackage` into a cache under the Harness home and loads the `session` entry with `createFacetBundleLoader`. Chord neither installs dependencies nor runs lifecycle scripts, so a package arrives with its dependencies installed. + +The loader resolves two externals itself. `@earendil-works/chord` resolves to the package's own Chord instance, because Chord identifies replicated state through a registry held by the module instance. `@earendil-works/pi-coding-agent/experimental/plugin` resolves to a module that defines service tokens with Pi's IDs (`pi.agent-controller`, `pi.local.presentation-ui`, `pi.local.slash-commands`); Chord matches services by ID string, so a token defined outside Pi binds to a facet compiled against Pi's. + +The package creates one Chord host per Agent in `agent/created` and disposes it with the Agent's context. Built-in facets serve Pi's Session services: + +| Pi member | DSH mechanism | Difference | +|---|---|---| +| `AgentController.prompt` | `agent.followup()` | Refusal with `busy` is derived from `agent.status`. | +| `steer`, `followUp` | `agent.steer()`, `agent.followup()` | None. | +| `cancelQueued` | `agent.inbox.remove()` | Telling `already_consumed` from `not_found` reads the Session log. | +| `abort` | `agent.cancel()` | None. | +| `compact` | `ctx.compaction.compactNow()` | Non-null `customInstructions` is refused; DSH compaction takes none. | +| `waitForPrompt` | A fold over Session events from the queued message to its `turn/end` | DSH does not link a prompt to its answer; the fold does. | +| `Models` | The model catalog and model selection of `session-controller` | Reasoning-effort identifiers stand in for Pi's thinking levels. | +| `SessionPlugins.reload` | Rebuild, `FacetHost.reload()`, then dispose the retired generation | None. | + +`Transcript` is not provided at this level. Chord rejects a host whose facet requires a service nobody provides, so a plugin that uses `Transcript` fails to load with an error naming it. + +## Level 2: presentation facets on the Host + +Pi's `tui` facets use no terminal API. They consume `SlashCommands`, `PresentationUI`, and Session services, so they can run in a second Chord host per Agent on the DSH Host, bound in-process to the Session host. + +A registered slash command becomes an Agent-scoped `ctx.commands` registration whose handler passes the raw input as `args`. A command run on the Host is logged as `command/run` and `command/done`; Pi does not log presentation commands. `getArgumentCompletions` has no DSH counterpart and is not called. `PresentationUI.select` maps to `ctx.userQuestions.ask()` and needs a connected Web Client; `showStatus` maps to the status display the Pi extensions bridge adds to the Web Client. + +## Level 3: transcript and stored selection + +`Transcript` requires a projection from DSH Session events to pi-durable's `ConversationView`: entries in pi-durable's entry kinds plus its live, inbox, agent, and usage documents. The projection is lossy where DSH records events Pi has no entry for, and it binds DSH to a type pi-durable declares unstable. + +A Session's plugin selection is stored as a `plugin:`-prefixed ignorable Session record, the mechanism the Pi extensions bridge uses for extension state, and a live Agent's selection is fixed, as in Pi. + +## Level 4: presentation facets in the browser + +Running `tui` facets in the page matches Pi's placement of presentation code. It needs three additions. A browser loader evaluates the CommonJS artifact, because Chord's artifact loader uses the filesystem and `node:vm`. One generic Remote endpoint carries Chord's service calls, snapshots, and updates as JSON between the page and the Host, because DSH generates Remote methods at build time and the Client does not discover Host services at runtime. Client commands accept argument text, which `ui-commands` actions do not receive today. + +## Level 5: parity + +Pi isolates each Session in a worker process and plans to give plugins tools and hooks through its registry. Matching that needs a DSH Session to run in its own process or worker thread with Chord's service protocol across the process boundary, and, once Pi exposes the registry, the tool, hook, and prompt mappings that the Pi extensions bridge already defines. + +## Runtime differences the plugin model exposes + +| Property | Chord and Pi | DSH | Levels affected | +|---|---|---|---| +| Provider replacement | A consumer holds a handle that resolves the current provider on each access; a reload swaps the implementation behind it. | A fiber whose injected service changes is unloaded and re-run. | 1: the Chord host keeps Chord's behavior for services between facets; a DSH service mapped into Chord still restarts its adapter. | +| Fault isolation | One worker process per Session. | All Sessions share the Host process, and an uncaught exception or unhandled rejection disposes the application ([app-boot](../../../../packages/boot/app-boot/README.md)). | 1–4 run plugin code in the Host; 5 removes the difference. | +| Remote declaration | A host publishes a service's remote members from the object it was given at runtime. | Remote methods are generated at build time, and the Client does not discover Host services at runtime ([remotes](../../../../packages/api/remotes/README.md)). | 4 | +| Shared state | Replicated JSON state with operation batches, hydration, and gap recovery, read through one API locally and remotely. | Each domain defines its own snapshot and delta types, or publishes complete projection values. | 3, 4 | +| Runtime loading | One bundle per facet, loaded outside the module cache after an integrity check. | Packages are built ahead of time; a built installation does not transpile TypeScript. | 1 adds Chord's bundler as a dependency of the plugin package. | + +## Chord mechanisms to evaluate for DSH + +Four Chord mechanisms address limits DSH has independently of Pi. Each needs its own proposal; this note only records them. + +- **Stable service handles.** A consumer keeps its handle while a provider is replaced, so a plugin reload does not restart its dependents and lose their state. +- **Declaration before activation.** Setup declares every dependency synchronously, and the host rejects a missing provider, a duplicate provider, or a cycle before any facet activates. Cordis resolves dependencies as services appear, so a fiber with a missing provider stays pending until an audit after startup reports it. +- **Replicated JSON state.** One primitive gives local and remote consumers complete immutable values, with operation batches on the wire and a full reset when a subscriber falls behind. +- **Verified plugin artifacts.** A content-addressed bundle with a manifest digest lets a host load a plugin generation outside the module cache and release it when retired. + +## Alternatives considered + +**Build levels 1 and 2 now as a prototype.** The API is unpublished, has one example plugin, and its services README already lists three `AgentController` methods as dropped. A prototype would need maintenance against each Pi release without a user. The service mapping above keeps the design result without that cost. + +**Skip the record and decide when Pi stabilizes.** The service mapping and the runtime comparison took a full reading of Chord and Pi's experimental server, worker, and client. The runtime differences also apply to DSH work that has nothing to do with Pi. + +**Implement Pi's plugin services on Cordis without Chord.** A facet calls Chord's API directly: `env.use`, `env.provide`, and `replicatedState`. Serving that API without Chord means reimplementing graph validation, stable handles, keyed services, and replicated state. Chord is MIT-licensed, has one runtime dependency, and is the runtime plugins are compiled against. + +**Add plugins to the Pi extensions bridge package.** The two mechanisms share no API and have different stability. Separate packages let each be enabled, versioned, and removed without the other. + +**Run presentation facets only in the browser.** Level 2 reuses Pi's Node artifacts unchanged and needs no browser loader. Browser support is an open decision in Chord's plan, and Pi has no presentation facet that is not terminal-specific. + +## Acceptance criteria + +- **Level 0:** this note is merged, and the Pi extensions compatibility page links to it for the plugin mechanism. +- **Level 1:** the `session` facet of Pi's example plugin loads from its unmodified package directory. A test calls its greeting service and every served `AgentController` member against a real agent loop with a mock model. `SessionPlugins.reload` replaces the facet generation without recreating the Chord host, and disposing the Agent disposes the host. +- **Level 2:** `/hello ` from Pi's example plugin runs through `ctx.commands`, shows its status text in the Web Client, and submits its prompt. +- **Level 3:** a facet that subscribes to `Transcript` receives the root conversation after hydration and after each committed step, and a resumed Session loads the plugins it was created with. +- **Level 4:** the `tui` facet of Pi's example plugin runs in the browser, and its service calls reach the Host through the generic endpoint. +- **Every level:** a compatibility page lists each difference from Pi, and the package's README states that plugin code runs in the Host with its full authority. + +## Risks + +- The upstream API can change or be withdrawn before a level is built. The service mapping describes Pi 1.0.0 and needs a re-read against the release that triggers the work. +- Levels 1 to 4 run plugin code in the Host process, where one uncaught error stops every Session. +- Level 3 depends on a pi-durable type that upstream declares unstable. +- Level 4 adds a Remote endpoint whose payloads are validated by Chord's wire parsers instead of generated per-method codecs. +- The plugin package brings esbuild as a runtime dependency through Chord's bundler. diff --git a/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.zh.md b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.zh.md new file mode 100644 index 0000000000..b7078a9eba --- /dev/null +++ b/.agents/notes/proposed/feature/2026-10-03-pi-plugins-compatibility.zh.md @@ -0,0 +1,140 @@ +# Agent Note: Pi 插件兼容——分级支持 + +Status: proposed + +[English](2026-10-03-pi-plugins-compatibility.md) | 中文 + +## 问题 + +Pi 1.0.0 有两套相互独立、用于运行定制代码的机制。扩展是进程内的 TypeScript 工厂函数,API 已发布,由 [Pi 扩展桥接 Agent Note](2026-10-03-pi-extensions-bridge.zh.md) 负责。插件是较新的机制,构建在 Pi 的 Chord 组合运行时之上:一个插件包贡献若干 facet,每个 facet 运行在其名称选定的进程里。本 Agent Note 只讨论插件。 + +现在还不值得为 Pi 插件构建兼容层,上游资料自己给出了两条理由。 + +- **API 未发布且不稳定。** 插件栈只在 `PI_EXPERIMENTAL=1` 下运行。`@earendil-works/pi-coding-agent` 的 `./experimental/plugin` 导出只有 `source` 条件,npm tarball 不包含它的构建产物。Chord 的规划文档称它“is not a stable public API contract yet”([PLANNING.md](https://github.com/earendil-works/pi/blob/v1.0.0/packages/chord/PLANNING.md));Session worker 所基于的 pi-durable 称其“API changes without notice between releases”([README](https://github.com/earendil-works/pi/blob/v1.0.0/packages/durable/README.md))。[services README](https://github.com/earendil-works/pi/blob/v1.0.0/packages/coding-agent/src/experimental/services/README.md) 把 `AgentController.navigate()`、`nextRun()` 与 `resume()` 列为已移除。 +- **插件能做的事比扩展少。** Session worker 自行构建工具、钩子与提示词注册表,不把它暴露为服务,因此 facet 无法注册模型工具、拦截工具调用或修改提示词。Pi 只附带一个示例插件。 + +如果不留记录就搁置这项工作,会丢失调研得到的两项结果:Pi 插件服务到 DSH 服务的映射,它决定每一级支持的成本;以及 DSH 运行时与 Chord 的差异,这些差异与 Pi 无关,对 DSH 自身同样重要。 + +## 提案 + +把 Pi 插件兼容划分为六个级别。每一级说明插件作者能得到什么、哪项上游变化使它值得构建,以及需要哪些 DSH 工作。在更高级别的触发条件出现之前,DSH 停留在 0 级。每一级包含其下各级,唯一例外是 4 级取代 2 级在 Host 侧的 presentation 实现。 + +| 级别 | 支持内容 | 上游触发条件 | DSH 工作 | +|---|---|---|---| +| 0 | 无。本 Agent Note 是唯一产物。 | — | Pi 发版时重新核对上游状态。 | +| 1 | `session` facet 运行在每个 DSH Session 各自的 Chord host 中。`AgentController`、`Models` 与 `SessionPlugins` 由 DSH 服务提供。 | Pi 在 npm 包中发布插件 API,或取消 `PI_EXPERIMENTAL` 要求。 | 一个实验性 Host 包。不改核心。 | +| 2 | `tui` facet 运行在 Host 上。它们的斜杠命令成为 DSH 命令,`select` 成为用户提问,`showStatus` 成为状态行。Pi 的示例插件原样运行。 | 同 1 级。 | 适配器,以及 Pi 扩展桥接的 Web 状态显示。 | +| 3 | 提供 `Transcript`,并把 Session 的插件选择随 Session 保存。 | 出现消费 `Transcript` 的插件,或 pi-durable 宣布 `ConversationView` 稳定。 | 从 Session 日志到 `ConversationView` 的投影,以及按 Session 保存的选择记录。 | +| 4 | `tui` facet 运行在浏览器中,经通用 Chord 桥访问 Session 服务。 | Chord 承诺支持浏览器,且 Pi 定义了不依赖终端的 presentation facet。 | 一个通用 JSON Remote 端点、一个浏览器 facet loader,以及 Client 命令的参数文本。 | +| 5 | 对等:每个 Session 一个 worker,插件可注册模型工具与钩子。 | Pi 把工具与钩子注册表暴露给 facet。 | 按 Session 的进程隔离,以及 Pi 扩展桥接已有的工具与钩子映射。 | + +## Pi 插件是什么 + +插件是一个包目录。它的 `package.json` 可以带 `chord.facets`,把 facet 名称映射到入口文件或将其禁用;没有该字段时,Pi 在 `src/session.ts` 与 `src/tui.ts` 存在的情况下使用它们。入口默认导出 `{ id, setup(env) }`。`setup` 是同步的,只做声明:`env.use` 与 `env.observe` 获取服务,`env.provide` 与 `env.provideMany` 发布服务,`env.replicatedState` 创建共享状态,`env.own`、`env.onActivate` 与 `env.onDeactivate` 挂接生命周期工作。Chord host 校验完整的依赖图,先激活提供方再激活消费方,并且能在消费方保留服务句柄的同时替换所声明服务不变的 facet。 + +服务器用 Chord 的 esbuild 打包器把每个入口构建为内容寻址的 CommonJS 文件,并登记在 `chord-facets.json` 中。Session worker 校验 SHA-256 摘要后用 `node:vm` 加载 `session` 入口,`require` 只解析该入口声明过的外部依赖。`tui` 入口作为 JSON 制品随服务响应到达各客户端,并以同样方式加载。该 loader 不是沙箱:插件代码以进程的全部权限运行。 + +`pi server -e ` 设置服务器的默认插件,`pi client -e ` 为单个 Session 选择插件。Pi 保存 Session 的选择,并在该 Session 的 worker 运行期间拒绝不同的选择。 + +Pi 向 facet 提供以下服务: + +| 作用域 | 服务 | 成员 | +|---|---|---| +| server | `SessionDirectory`、`SessionManagement`、`PresentationPlugins` | Session 列表状态;创建、删除、attach、detach;presentation 制品的构建与重载 | +| session | `AgentController` | `prompt`、`steer`、`followUp`、`cancelQueued`、`abort`、`compact`、`waitForPrompt` | +| session | `Models` | 可复制的目录与选择状态;`select`、`selectThinking`、`cycleThinking`、`getThinkingLevels`、`refresh` | +| session | `Transcript` | 根对话的 pi-durable `ConversationView`,以可复制状态提供 | +| session | `SessionPlugins` | `reload` | +| presentation,进程内 | `SlashCommands` | `register`、`replace`、`list`、`subscribe`;命令包含名称、描述、参数提示、可选的参数补全,以及 `run(args, context)` | +| presentation,进程内 | `PresentationUI` | `select(title, items, selected, context)`、`showStatus(message, context)` | + +非进程内的服务只能包含可复制的 JSON 状态,以及参数与结果均为 JSON、最后一个参数为 Chord `Context` 的方法。 + +## 1 级:session facet + +该包依赖某一精确版本的 `@earendil-works/chord`。它用 `bundleFacetPackage` 把每个选中的包构建到 Harness 主目录下的缓存,并用 `createFacetBundleLoader` 加载 `session` 入口。Chord 既不安装依赖也不运行生命周期脚本,因此包到达时必须已装好依赖。 + +loader 自行解析两个外部依赖。`@earendil-works/chord` 解析为该包自己的 Chord 实例,因为 Chord 通过模块实例持有的注册表识别可复制状态。`@earendil-works/pi-coding-agent/experimental/plugin` 解析为一个模块,它用 Pi 的 ID(`pi.agent-controller`、`pi.local.presentation-ui`、`pi.local.slash-commands`)定义服务 token;Chord 按 ID 字符串匹配服务,因此在 Pi 之外定义的 token 可以绑定到按 Pi 的 token 编译的 facet。 + +该包在 `agent/created` 中为每个 Agent 创建一个 Chord host,并随 Agent 的上下文一同释放。内置 facet 提供 Pi 的 Session 服务: + +| Pi 成员 | DSH 机制 | 差异 | +|---|---|---| +| `AgentController.prompt` | `agent.followup()` | 以 `busy` 拒绝的行为由 `agent.status` 推导。 | +| `steer`、`followUp` | `agent.steer()`、`agent.followup()` | 无。 | +| `cancelQueued` | `agent.inbox.remove()` | 区分 `already_consumed` 与 `not_found` 需要读取 Session 日志。 | +| `abort` | `agent.cancel()` | 无。 | +| `compact` | `ctx.compaction.compactNow()` | 拒绝非空的 `customInstructions`;DSH 压缩不接受指令。 | +| `waitForPrompt` | 对 Session 事件做 fold,从入队消息到它的 `turn/end` | DSH 不关联提示词与其回答;由该 fold 完成关联。 | +| `Models` | `session-controller` 的模型目录与模型选择 | 以推理强度标识符代替 Pi 的 thinking level。 | +| `SessionPlugins.reload` | 重新构建、调用 `FacetHost.reload()`,再释放退役的 generation | 无。 | + +本级不提供 `Transcript`。Chord 会拒绝含有“所需服务无人提供”的 facet 的 host,因此使用 `Transcript` 的插件加载失败,错误信息会指出该服务。 + +## 2 级:Host 上的 presentation facet + +Pi 的 `tui` facet 不使用任何终端 API。它们消费 `SlashCommands`、`PresentationUI` 与 Session 服务,因此可以运行在 DSH Host 上每个 Agent 的第二个 Chord host 中,并在进程内绑定到 Session host。 + +注册的斜杠命令成为 Agent 作用域的 `ctx.commands` 注册,其 handler 把原始输入作为 `args` 传入。在 Host 上运行的命令会记录为 `command/run` 与 `command/done`;Pi 不记录 presentation 命令。`getArgumentCompletions` 在 DSH 中没有对应物,不会被调用。`PresentationUI.select` 映射到 `ctx.userQuestions.ask()`,需要已连接的 Web Client;`showStatus` 映射到 Pi 扩展桥接为 Web Client 增加的状态显示。 + +## 3 级:transcript 与保存的选择 + +`Transcript` 需要从 DSH Session 事件到 pi-durable `ConversationView` 的投影:按 pi-durable 条目类型组织的条目,加上它的 live、inbox、agent 与 usage 文档。DSH 记录了 Pi 没有对应条目的事件,在这些地方投影是有损的;它还把 DSH 绑定到一个被 pi-durable 声明为不稳定的类型上。 + +Session 的插件选择保存为带 `plugin:` 前缀的 ignorable Session 记录,即 Pi 扩展桥接保存扩展状态所用的机制;与 Pi 一样,存活 Agent 的选择固定不变。 + +## 4 级:浏览器中的 presentation facet + +在页面中运行 `tui` facet 与 Pi 对 presentation 代码的放置一致。它需要三项新增。浏览器 loader 负责求值 CommonJS 制品,因为 Chord 的制品 loader 使用文件系统与 `node:vm`。一个通用 Remote 端点以 JSON 在页面与 Host 之间承载 Chord 的服务调用、快照与更新,因为 DSH 在构建时生成 Remote 方法,Client 不在运行时发现 Host 服务。Client 命令需要接受参数文本,目前 `ui-commands` 的 action 收不到它。 + +## 5 级:对等 + +Pi 把每个 Session 隔离在一个 worker 进程中,并计划通过其注册表向插件提供工具与钩子。要达到这一点,DSH Session 需要运行在自己的进程或 worker 线程中,并让 Chord 的服务协议跨越进程边界;在 Pi 暴露注册表之后,还需要 Pi 扩展桥接已经定义的工具、钩子与提示词映射。 + +## 插件模型暴露出的运行时差异 + +| 属性 | Chord 与 Pi | DSH | 受影响的级别 | +|---|---|---|---| +| 提供方替换 | 消费方持有句柄,每次访问时解析当前提供方;重载在句柄背后替换实现。 | 所注入服务发生变化的 fiber 会被卸载并重新运行。 | 1:Chord host 对 facet 之间的服务保持 Chord 的行为;映射进 Chord 的 DSH 服务仍会重启其适配器。 | +| 故障隔离 | 每个 Session 一个 worker 进程。 | 所有 Session 共享 Host 进程,未捕获的异常或未处理的 rejection 会释放整个应用([app-boot](../../../../packages/boot/app-boot/README.zh.md))。 | 1–4 在 Host 中运行插件代码;5 消除这一差异。 | +| Remote 声明 | host 根据运行时拿到的对象发布服务的远程成员。 | Remote 方法在构建时生成,Client 不在运行时发现 Host 服务([remotes](../../../../packages/api/remotes/README.zh.md))。 | 4 | +| 共享状态 | 可复制的 JSON 状态,带操作批次、hydration 与缺口恢复,本地与远程通过同一 API 读取。 | 每个领域定义自己的快照与增量类型,或发布完整的投影值。 | 3、4 | +| 运行时加载 | 每个 facet 一个 bundle,经完整性校验后在模块缓存之外加载。 | 包提前构建;已构建的安装不转译 TypeScript。 | 1 把 Chord 的打包器加为插件包的依赖。 | + +## 值得为 DSH 评估的 Chord 机制 + +有四项 Chord 机制针对的是 DSH 自身存在、与 Pi 无关的限制。每一项都需要单独的提案;本 Agent Note 只做记录。 + +- **稳定的服务句柄。** 提供方被替换时消费方保留句柄,因此插件重载不会重启其依赖方并丢失它们的状态。 +- **先声明后激活。** setup 同步声明全部依赖,host 在任何 facet 激活之前拒绝缺失的提供方、重复的提供方或循环依赖。Cordis 随服务出现逐步解析依赖,因此缺少提供方的 fiber 会一直挂起,直到启动后的审计报告它。 +- **可复制的 JSON 状态。** 一个原语为本地与远程消费方提供完整的不可变值,线上传输操作批次,订阅方落后时做一次完整重置。 +- **经过校验的插件制品。** 带 manifest 摘要的内容寻址 bundle 让 host 在模块缓存之外加载某一代插件,并在其退役后释放。 + +## 考虑过的替代方案 + +**现在就把 1、2 级做成原型。** 该 API 未发布,只有一个示例插件,其 services README 已把三个 `AgentController` 方法列为已移除。原型需要跟随 Pi 的每次发版维护,却没有用户。上文的服务映射保留了设计结论,而不必付出这项成本。 + +**不留记录,等 Pi 稳定后再决定。** 服务映射与运行时对比来自对 Chord 以及 Pi 实验性 server、worker 与 client 的完整阅读。这些运行时差异同样适用于与 Pi 无关的 DSH 工作。 + +**不用 Chord,直接在 Cordis 上实现 Pi 的插件服务。** facet 直接调用 Chord 的 API:`env.use`、`env.provide` 与 `replicatedState`。不用 Chord 而提供这套 API,意味着重新实现依赖图校验、稳定句柄、keyed 服务与可复制状态。Chord 采用 MIT 许可,只有一个运行时依赖,而且插件正是针对它编译的。 + +**把插件并入 Pi 扩展桥接包。** 两套机制没有共同的 API,稳定性也不同。分成独立的包,才能各自启用、定版与移除。 + +**只在浏览器中运行 presentation facet。** 2 级原样复用 Pi 的 Node 制品,不需要浏览器 loader。浏览器支持在 Chord 的规划中仍是未决事项,Pi 也没有不依赖终端的 presentation facet。 + +## 验收标准 + +- **0 级:** 本 Agent Note 已合并,Pi 扩展兼容性页面在插件机制处链接到它。 +- **1 级:** Pi 示例插件的 `session` facet 从未修改的包目录加载。测试在使用 mock 模型的真实 agent loop 上调用它的问候服务以及每个已提供的 `AgentController` 成员。`SessionPlugins.reload` 替换 facet generation 而不重建 Chord host,释放 Agent 时 host 随之释放。 +- **2 级:** Pi 示例插件的 `/hello ` 经 `ctx.commands` 运行,在 Web Client 中显示其状态文本,并提交其提示词。 +- **3 级:** 订阅 `Transcript` 的 facet 在 hydration 之后以及每个已提交的步骤之后收到根对话,恢复的 Session 加载其创建时所用的插件。 +- **4 级:** Pi 示例插件的 `tui` facet 在浏览器中运行,其服务调用经通用端点到达 Host。 +- **每一级:** 兼容性页面列出与 Pi 的每一处差异,包的 README 说明插件代码以 Host 的全部权限运行。 + +## 风险 + +- 在任何一级构建之前,上游 API 都可能变化或被撤回。服务映射描述的是 Pi 1.0.0,需要对照触发这项工作的发行版重新核对。 +- 1 至 4 级在 Host 进程中运行插件代码,一个未捕获的错误会中止所有 Session。 +- 3 级依赖一个被上游声明为不稳定的 pi-durable 类型。 +- 4 级增加的 Remote 端点由 Chord 的 wire 解析器校验载荷,而不是由按方法生成的编解码器校验。 +- 插件包经由 Chord 的打包器引入 esbuild 作为运行时依赖。 diff --git a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml b/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml deleted file mode 100644 index 38917672a7..0000000000 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-27-port-tool-owned-render.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md -/agent-note-port-tool-owned-render-into-current-dsh-apis: - en: a13180eec1968793 - zh: e47a83ddb329de88 -/agent-note-port-tool-owned-render-into-current-dsh-apis/problem: - en: 3a3ceea534efabd1 - zh: f13f8531d1819b8a -/agent-note-port-tool-owned-render-into-current-dsh-apis/proposal: - en: a51c0e6f23d8d4d4 - zh: 1eecd0ed06685c40 -/agent-note-port-tool-owned-render-into-current-dsh-apis/alternatives-considered: - en: a2364e3fca0a06e5 - zh: a76370082e218154 -/agent-note-port-tool-owned-render-into-current-dsh-apis/acceptance-criteria: - en: 5e2815d18df000f9 - zh: 97845472646b1881 -/agent-note-port-tool-owned-render-into-current-dsh-apis/risks: - en: 4fa51a62af0a3fc5 - zh: d6f555a8a5d9120c diff --git a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md b/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md deleted file mode 100644 index 4ab27142fd..0000000000 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: Port tool-owned render into current DSH APIs - -Status: proposed - -English | [中文](2026-08-27-port-tool-owned-render.zh.md) - -## Problem - -The `dsh-tool-owned-render` prototype (`Chinesezjc/dsh-tool-owned-render`) ships tool-owned render registrants for `read`, `bash`, `write`/`edit`, `grep`/`glob`, and `web_search`/`web_fetch`, written against an older API where `ToolCallBlock` exposed `callView` / `resultView` and the client received host `presentResult` output. Current master derives client cards from raw `block.call` / `block.content` / `block.meta`, and `ctx.slots` requires the `@deepseek-ai/dsh-client-ui-renderer/client` module augmentation. A direct merge of the prototype does not typecheck, so its registrants cannot ship without a port. - -## Proposal - -- Add `packages/client/tool-owned-render` as a workspace package. -- Port the `read`, `bash`, `write`/`edit`, `grep`/`glob`, and `web_search`/`web_fetch` registrants to derive from current `ToolCallBlock` fields. -- Add a `read_image` registrant using the same ToolCard/Segment primitives. -- Wire `ctx.slots` type augmentation through `dsh-client-ui-renderer`. -- Keep PR #2828 mergeable while this port proceeds separately. - -## Alternatives considered - -- **Merge the prototype and fix its type errors in place** — rejected: every registrant would have to be re-derived from the current `ToolCallBlock` fields anyway, so the port is the same work with the obsolete `callView` / `resultView` contract already gone. -- **Fold the port into PR #2828** — rejected: the image card is one feature with a defined scope, and a second package plus five more registrants would enlarge the review surface of an already large PR. - -## Acceptance criteria - -- `packages/client/tool-owned-render` exists as a workspace package. -- The ported registrants derive card state from current `ToolCallBlock` fields and typecheck on master. -- A `read_image` registrant renders through the same primitives as `read`. -- The `ctx.slots` type augmentation resolves through `dsh-client-ui-renderer`. -- PR #2828 merges independently of this port. - -## Risks - -- The port may not reproduce the prototype's exact visual output, because the current card primitives differ from the old `callView` / `resultView` contract. -- API drift while the port proceeds can stale this proposal; the acceptance criteria are re-checked against master at port time. diff --git a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md b/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md deleted file mode 100644 index fe8a018235..0000000000 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md +++ /dev/null @@ -1,35 +0,0 @@ -# Agent Note: 把 tool-owned render 移植到当前 DSH API - -Status: proposed - -[English](2026-08-27-port-tool-owned-render.md) | 中文 - -## 问题 - -`dsh-tool-owned-render` 原型(`Chinesezjc/dsh-tool-owned-render`)带有 `read`、`bash`、`write`/`edit`、`grep`/`glob`、`web_search`/`web_fetch` 的 tool-owned render 注册项,基于旧 API 编写:`ToolCallBlock` 暴露 `callView` / `resultView`,客户端能拿到 host `presentResult` 输出。当前 master 从原始 `block.call` / `block.content` / `block.meta` 推导客户端卡片,`ctx.slots` 也需要 `@deepseek-ai/dsh-client-ui-renderer/client` 模块增强。直接合并原型不能通过类型检查,因此这些注册项不经移植无法发布。 - -## 提案 - -- 新增 `packages/client/tool-owned-render` workspace 包。 -- 把 `read`、`bash`、`write`/`edit`、`grep`/`glob`、`web_search`/`web_fetch` 注册项移植到从当前 `ToolCallBlock` 字段推导。 -- 增加 `read_image` 注册项,使用同一套 ToolCard/Segment 原语。 -- 通过 `dsh-client-ui-renderer` 接通 `ctx.slots` 类型增强。 -- 移植单独推进,保持 PR #2828 可合并。 - -## 已考虑的替代方案 - -- **直接合并原型并就地修复类型错误** — 否决:每个注册项反正都要按当前 `ToolCallBlock` 字段重新推导,移植就是同一份工作,只是旧的 `callView` / `resultView` 契约已不存在。 -- **把移植并入 PR #2828** — 否决:image 卡片是一个范围明确的单一功能,再加一个新包和五个注册项会扩大本已很大的 PR 的审查面。 - -## 验收标准 - -- `packages/client/tool-owned-render` 作为 workspace 包存在。 -- 移植后的注册项从当前 `ToolCallBlock` 字段推导卡片状态,并在 master 上通过类型检查。 -- `read_image` 注册项与 `read` 使用同一套原语渲染。 -- `ctx.slots` 类型增强通过 `dsh-client-ui-renderer` 解析。 -- PR #2828 独立于本移植合并。 - -## 风险 - -- 移植可能无法复现原型的精确视觉输出,因为当前卡片原语与旧的 `callView` / `resultView` 契约不同。 -- 移植推进期间 API 继续漂移会使本提案过时;验收标准在移植时按当时的 master 重新核对。 diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml deleted file mode 100644 index 30d37e8252..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-09-19-omit-fs-payload-invariant.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md -/agent-note-omit-the-filesystem-payload-only-invariant-companion: - en: 6385aa079529c4f4 - zh: 602b99cea351125c -/agent-note-omit-the-filesystem-payload-only-invariant-companion/problem: - en: 4568d53b3ef69254 - zh: 66c9aea2ae3645e7 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/proposal: - en: 2bfceaa480b6645d - zh: 3ad1060bf91ded74 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/alternatives-considered: - en: d12fd1577a308af3 - zh: 56271794646f5a87 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/acceptance-criteria: - en: 253009b93ce34c5b - zh: 2268e0303bd025d6 -/agent-note-omit-the-filesystem-payload-only-invariant-companion/risks: - en: 70c52f193d72d02d - zh: f30844e865ac4c27 diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md deleted file mode 100644 index e91fa1784a..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: Omit the filesystem payload-only invariant companion - -Status: proposed - -English | [中文](2026-09-19-omit-fs-payload-invariant.zh.md) - -## Problem - -The [filesystem invariant](../../../../packages/fs/fs/src/invariant.ts) checks non-empty target strings, a non-empty present version, and an observation discriminant. It reads one dispatch payload, retains no history, and compares no provider, policy, filesystem, or independently mutable state. Its [tests](../../../../packages/fs/fs/tests/invariant.spec.ts) construct malformed payloads directly. Publishing this check costs a companion module, export/build wiring, invariant dependencies, compiler references, and dedicated tests. - -Current guidance needs an explicit decision: the [independent-observation rule](../../implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md) requires observations that can independently diverge, while the [runtime-contract examples](../../implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md) specifically endorse filesystem target/version validity. The later rule retains the earlier note's semantic authority, so chronology alone does not resolve this conflict. - -## Proposal - -Apply the independent-observation criterion to this concrete companion and remove it. The event producers are independent plugins, but checking the format of each single payload does not compare their observations. Keep typed filesystem events and the real [observation-policy consumer](../../../../packages/fs/fs-observation-policy/src/index.ts), along with parser, wire, read, edit, and write validation at their existing owners. - -Remove the 48-line companion, its 55-line dedicated test, `./invariant` export/publication, invariant-only dependencies and compiler references. Add the package-specific omission reason to both fs READMEs. Amend the earlier representative-check row when implementing this decision, retaining its other semantic examples and the shared invariant registration machinery. This proposal leaves those current owners unchanged until acceptance. - -## Alternatives considered - -**Keep payload validation as an explicit policy exception.** It can detect malformed third-party emissions early, but this would deliberately broaden the stated independent-observation criterion. The proposal prefers input validation at its owner and reserves companions for relational checks. - -**Add a second state source to justify the companion.** Rejected without a concrete missing invariant. Duplicating policy state or exposing new API solely to retain this diagnostic increases the maintenance burden. - -## Acceptance criteria - -- Resolve the named policy conflict in the active owning notes; do not claim this is already an uncontested mechanical omission. -- No companion source, test, export, artifact entry, invariant-only dependency, or compiler reference remains; package topology checks accept the explained omission. -- Real file-tool and observation-policy tests retain stale-version, absent-target, alias, actor-routing, and world-state assertions. The events and consumers remain. -- Run focused fs and observation-policy tests, relevant file-tool integration checks, source/built invariant-topology checks, build/hygiene, doc-sync, and lint. Do not replace the module with equivalent generic payload-validation machinery. - -## Risks - -Malformed trusted-plugin emissions lose this opt-in early diagnostic. The check's removal does not justify weakening validation of external data, nor removing other companions that compare lifecycle, durable history, or independently assembled values. If payload-format diagnostics are intentionally part of the companion policy, retain this module and clarify that exception instead. diff --git a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md b/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md deleted file mode 100644 index ba81baed5f..0000000000 --- a/.agents/notes/proposed/simplification/2026-09-19-omit-fs-payload-invariant.zh.md +++ /dev/null @@ -1,34 +0,0 @@ -# Agent Note: 省略仅校验载荷的文件系统 invariant 配套模块 - -Status: proposed - -[English](2026-09-19-omit-fs-payload-invariant.md) | 中文 - -## 问题 - -[文件系统 invariant](../../../../packages/fs/fs/src/invariant.ts) 检查目标字符串非空、present 版本非空及观察值的判别标签。它只读取一次分发的载荷,不保留历史,也不与提供方、策略、文件系统或独立可变状态比较。其[测试](../../../../packages/fs/fs/tests/invariant.spec.ts) 直接构造格式错误的载荷。发布该检查需要配套模块、导出/构建配置、invariant 依赖、编译器引用和专用测试。 - -当前指导需要显式决策:[独立观察规则](../../implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md) 要求能够独立分歧的观察值,而[运行时约定示例](../../implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md) 明确认可文件系统目标/版本有效性检查。后者的语义权威仍被较新的规则保留,因此不能仅凭时间先后解决冲突。 - -## 提案 - -将独立观察标准应用于这个具体配套模块,并删除它。事件生产方来自独立插件,但逐一检查单个载荷的格式,并没有比较它们的观察值。保留类型化文件系统事件和实际的[观察策略消费方](../../../../packages/fs/fs-observation-policy/src/index.ts),以及现有归属处的解析器、wire、读取、编辑和写入校验。 - -删除 48 行配套模块、55 行专用测试、`./invariant` 导出/发布项、仅供 invariant 使用的依赖和编译器引用。在 fs 的两个 README 中补充包特有的省略理由。实现本决策时修改较早的代表性检查行,保留其他语义示例及共享 invariant 注册机制。本提案获接受前不修改这些当前归属文档。 - -## 考虑过的替代方案 - -**将载荷校验保留为显式策略例外。** 它能提前发现第三方格式错误的事件,但这会有意扩大已声明的独立观察标准。提案倾向于在输入归属处校验,并将配套模块留给关系检查。 - -**添加第二个状态源以证明配套模块有必要。** 在没有具体缺失 invariant 的情况下否决。仅为保留此诊断而复制策略状态或暴露新 API,会增加维护负担。 - -## 验收标准 - -- 在当前归属 Agent Notes 中解决已指出的策略冲突;不将其宣称为已有共识的机械省略。 -- 不再保留配套源码、测试、导出、产物条目、仅供 invariant 使用的依赖或编译器引用;包拓扑检查接受有说明的省略。 -- 真实文件工具和观察策略测试保留过期版本、目标缺失、别名、执行方路由及外部状态断言。事件及其消费方保留。 -- 运行聚焦的 fs 与观察策略测试、相关文件工具集成检查、源码/构建产物 invariant 拓扑检查、build/hygiene、doc-sync(文档同步门禁)和 lint。不用等价的通用载荷校验机制替换该模块。 - -## 风险 - -来自受信任插件的格式错误事件会失去这项可选的早期诊断。删除该检查不构成削弱外部数据校验的理由,也不意味着可以删除比较生命周期、持久化历史或独立组装值的其他配套模块。如果载荷格式诊断本来就是配套模块策略的一部分,应保留模块并澄清该例外。 diff --git a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.i18n.yaml index 47b5014e29..232069c543 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.i18n.yaml @@ -9,8 +9,8 @@ en: 5426489eed9b447e zh: 97132891bfff4b41 /agent-note-read-instruction-files-only-through-a-filesystem-provider/proposal: - en: 1f6d8ce78f5ab682 - zh: c71c97c0268ac165 + en: 0096f75858b4c565 + zh: 7c2c6a18fb111c08 /agent-note-read-instruction-files-only-through-a-filesystem-provider/alternatives-considered: en: c942573554ef1d63 zh: 7af89ae9434f7521 diff --git a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.md b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.md index 26d004c136..c3951a6510 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.md +++ b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.md @@ -18,7 +18,7 @@ Keep the plugin's missing-provider no-op and the pure `renderAgentInstructions` Migrate direct helper tests to explicitly owned local or controlled providers. The [test suite](../../../../packages/context/agent-instructions/tests/agent-instructions.spec.ts) already mounts both. Preserve unique assertions and consolidate duplicate Node-mocking cases into provider coverage. Expected deletion is roughly 50–65 production lines before signature/documentation updates, plus redundant test machinery; measure the actual reduction during implementation. -The [symlink decision](../../implemented/feature/2026-07-21-follow-instruction-symlinks.md) remains active because its accepted behavior survives. At implementation, update its references to the two implementations and the [package README](../../../../packages/context/agent-instructions/README.md), including bilingual counterparts. The [archived workspace-context note](../../archived/feature/2026-06-24-workspace-context.md) remains frozen; no existing note changes accompany this proposal. +The [agent-instructions reference](../../../../packages/context/agent-instructions/README.md) owns the retained symlink-loading behavior. At implementation, update its provider-read descriptions and Chinese counterpart. The [archived workspace-context note](../../archived/feature/2026-06-24-workspace-context.md) remains frozen; this proposal requires no change to that historical record. ## Alternatives considered diff --git a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.zh.md b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.zh.md index 82f3451f96..b678ea69be 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.zh.md +++ b/.agents/notes/proposed/simplification/2026-09-19-provider-only-instruction-reads.zh.md @@ -18,7 +18,7 @@ Status: proposed 让直接调用辅助函数的测试改用显式管理生命周期的本地或受控提供方。[测试套件](../../../../packages/context/agent-instructions/tests/agent-instructions.spec.ts)已经挂载了这两类实现。保留独有断言,把重复的 Node mock 场景合并到提供方覆盖中。预计在更新签名和文档之前删除约 50–65 行生产源码,另加冗余测试机制;实施时测量实际减少量。 -[符号链接决策](../../implemented/feature/2026-07-21-follow-instruction-symlinks.zh.md)所接受的行为继续存在,因此该说明保持现行状态。实施时,更新其中对两种实现的引用和[包 README](../../../../packages/context/agent-instructions/README.zh.md),包括双语对侧。[已归档的工作区上下文说明](../../archived/feature/2026-06-24-workspace-context.md)保持冻结;本提案不附带任何既有说明变更。 +[agent-instructions 参考](../../../../packages/context/agent-instructions/README.zh.md)负责保留的符号链接加载行为。实施时更新其中对提供方读取的描述及英文对侧。[已归档的工作区上下文说明](../../archived/feature/2026-06-24-workspace-context.md)保持冻结;本提案无需修改该历史记录。 ## 备选方案 diff --git a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.i18n.yaml index 51801755e1..d8ee6e246b 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.i18n.yaml @@ -6,11 +6,11 @@ en: 5d82ce82cac8a687 zh: 4cfc68fceb215e35 /agent-note-prune-unused-stream-visibility-helpers/problem: - en: da5bf344b0d704c1 - zh: 6826b064d8b66373 + en: 0665c67fb7460f1f + zh: 28215a7a02e9262b /agent-note-prune-unused-stream-visibility-helpers/proposal: - en: b6cec09bd23a2f5a - zh: ac348bbe47ea7cc9 + en: b3f72cf66541614f + zh: cc214a5dc75ae6b7 /agent-note-prune-unused-stream-visibility-helpers/alternatives-considered: en: 1a190cf86a9f7ec9 zh: 2a30aa235c69bdc7 diff --git a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.md b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.md index e44716fe15..8c64dba545 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.md +++ b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.md @@ -6,7 +6,7 @@ English | [中文](2026-09-19-prune-stream-visibility-helpers.zh.md) ## Problem -The [compact Assistant stream implementation](../../../../packages/llm/llm/src/assistant-stream.ts) exports five visibility queries: `isVisibleChunk`, `chunkHasVisibleText`, `runFirstVisibleTime`, `assistantStreamHasVisibleContent`, and `assistantStreamHasVisibleText`. Repository searches find no production caller outside that family. Its other consumers are the [unit tests](../../../../packages/llm/llm/tests/assistant-stream.spec.ts), package documentation, and the [record-reader decision](../../implemented/architecture/2026-09-06-embedded-stream-record-readers.md). The package root and published `./assistant-stream` entry expose the functions to external callers, whose usage this search cannot establish. +The [compact Assistant stream implementation](../../../../packages/llm/llm/src/assistant-stream.ts) exports five visibility queries: `isVisibleChunk`, `chunkHasVisibleText`, `runFirstVisibleTime`, `assistantStreamHasVisibleContent`, and `assistantStreamHasVisibleText`. Repository searches find no production caller outside that family. Its other consumers are the [unit tests](../../../../packages/llm/llm/tests/assistant-stream.spec.ts), package documentation, and the [record-reader reference](../../../../packages/llm/llm/README.md). The package root and published `./assistant-stream` entry expose the functions to external callers, whose usage this search cannot establish. The retained first-token reader has current consumers in [Session Stats](../../../../packages/session/session-stats/src/projection.ts) and [Trajectory](../../../../packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts). Their timing requirements do not require the separate whitespace-sensitive visibility classification. @@ -16,7 +16,7 @@ Remove the five exported visibility queries and their exclusive private helpers, Remove dedicated visibility tests and only the obsolete assertions inside mixed reader tests. Keep the mixed fixtures and assertions that verify first-token timing, early exit, raw chunks, text joining, and assembly equivalence. -The record-reader decision is only partially superseded: its allocation rationale and used readers remain current. When implementing this proposal, update that active note, the [package README](../../../../packages/llm/llm/README.md), and the [LLM subsystem page](../../../../docs/subsystems/llm-streaming.md) with their bilingual counterparts. Do not delete the owning note or alter archived records while adding this proposal. +The [package README](../../../../packages/llm/llm/README.md) owns compact-reader behavior: retained readers avoid materializing per-member expansions, and durable input still uses validating expansion. Preserve those constraints and the used readers. When implementing this proposal, update that README and the [LLM subsystem page](../../../../docs/subsystems/llm-streaming.md) with their bilingual counterparts; archived records remain unchanged. ## Alternatives considered diff --git a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.zh.md b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.zh.md index 60d010f260..406b21847b 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.zh.md +++ b/.agents/notes/proposed/simplification/2026-09-19-prune-stream-visibility-helpers.zh.md @@ -6,7 +6,7 @@ Status: proposed ## 问题 -[紧凑 Assistant 流实现](../../../../packages/llm/llm/src/assistant-stream.ts)导出五个可见性查询:`isVisibleChunk`、`chunkHasVisibleText`、`runFirstVisibleTime`、`assistantStreamHasVisibleContent` 和 `assistantStreamHasVisibleText`。仓库搜索未找到这组函数之外的生产调用方。其余消费方是[单元测试](../../../../packages/llm/llm/tests/assistant-stream.spec.ts)、包文档和[记录读取器决策](../../implemented/architecture/2026-09-06-embedded-stream-record-readers.zh.md)。包根入口和发布的 `./assistant-stream` 入口向外部调用方暴露这些函数;本次搜索无法确定外部使用情况。 +[紧凑 Assistant 流实现](../../../../packages/llm/llm/src/assistant-stream.ts)导出五个可见性查询:`isVisibleChunk`、`chunkHasVisibleText`、`runFirstVisibleTime`、`assistantStreamHasVisibleContent` 和 `assistantStreamHasVisibleText`。仓库搜索未找到这组函数之外的生产调用方。其余消费方是[单元测试](../../../../packages/llm/llm/tests/assistant-stream.spec.ts)、包文档和[记录读取器参考](../../../../packages/llm/llm/README.zh.md)。包根入口和发布的 `./assistant-stream` 入口向外部调用方暴露这些函数;本次搜索无法确定外部使用情况。 保留的首 token 读取器已有 [Session Stats](../../../../packages/session/session-stats/src/projection.ts) 和 [Trajectory](../../../../packages/client/ui-trajectory/src/client/trajectory-assistant-definition.ts) 消费方。它们的计时要求不依赖单独区分空白文本的可见性分类。 @@ -16,7 +16,7 @@ Status: proposed 移除专门的可见性测试;混合读取器测试只删除过时断言。保留验证首 token 计时、提前退出、原始分片、文本拼接和组装等价性的混合 fixture(测试前置数据)与断言。 -记录读取器决策只被部分替代:其分配开销方面的理由和仍被使用的读取器继续有效。实施本提案时,更新该现行说明、[包 README](../../../../packages/llm/llm/README.zh.md) 和 [LLM 子系统页面](../../../../docs/subsystems/llm-streaming.zh.md)及其双语对侧。添加本提案时不删除所属说明,也不修改归档记录。 +[包 README](../../../../packages/llm/llm/README.zh.md)负责紧凑读取器的行为:保留的读取器避免将逐成员展开结果实体化,持久化输入仍使用带校验的展开操作。保留这些约束和仍被使用的读取器。实施本提案时,更新该 README、[LLM 子系统页面](../../../../docs/subsystems/llm-streaming.zh.md)及其双语对侧;归档记录保持不变。 ## 备选方案 diff --git a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.i18n.yaml index df72997d92..beb6fb007b 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.i18n.yaml @@ -6,8 +6,8 @@ en: 29c6b53f7f81bfd8 zh: 94139164373cda91 /agent-note-remove-the-python-sdk-s-unused-reverse-rpc-direction/problem: - en: e8779fc70adce0e4 - zh: 288565d427fe65ae + en: 839a904fc25dc18e + zh: 5f7b3cac4fac2755 /agent-note-remove-the-python-sdk-s-unused-reverse-rpc-direction/proposal: en: 508671a9a29a8f2a zh: 750587c118eb7450 diff --git a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.md b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.md index 287126d65c..439a479ac6 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.md +++ b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.md @@ -8,7 +8,7 @@ English | [中文](2026-09-19-python-sdk-directional-client.zh.md) The [Python client](../../../../python/sdk/src/deepseek_harness/client.py) keeps an incoming-request queue, close-time wake-up, and `next_request`, `respond`, `respond_error`, and `notify` methods. The [SDK server](../../../../packages/sdk/server/src/server.ts) receives requests and emits notifications; it declares no server-originated request or client-notification handler. Searches across Python sources, examples, and runtime profiles found the reverse direction only in client implementation and [synthetic client tests](../../../../python/sdk/tests/test_client.py). The exported `IncomingRequest` model consequently has no supported runtime producer. -This is a narrower continuation of the [directional JSON-RPC proposal](../../rejected/simplification/2026-07-19-make-jsonrpc-directional.md), whose rejection explicitly permits transport narrowing separately. Its completion redesign remains rejected. Its claim about the shared TypeScript transport is now inapplicable: the [Codex wire adapter](../../../../packages/subagent/subagent-codex/src/wire.ts) uses requests and notifications in both directions. +This proposal narrows only the Python client. The [SDK server](../../../../packages/sdk/server/README.md) retains its durable enqueue receipt and streamed Session events; prompt responses do not represent turn completion. The shared TypeScript transport remains bidirectional because the [Codex wire adapter](../../../../packages/subagent/subagent-codex/src/wire.ts) uses requests and notifications in both directions. ## Proposal diff --git a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.zh.md b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.zh.md index caf9b7c73a..e6279dbb80 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.zh.md +++ b/.agents/notes/proposed/simplification/2026-09-19-python-sdk-directional-client.zh.md @@ -8,7 +8,7 @@ Status: proposed [Python 客户端](../../../../python/sdk/src/deepseek_harness/client.py) 维护入站请求队列、关闭时的唤醒,以及 `next_request`、`respond`、`respond_error` 和 `notify` 方法。[SDK 服务端](../../../../packages/sdk/server/src/server.ts) 接收请求并发送通知,没有定义服务端主动发起的请求或客户端通知处理器。搜索 Python 源码、示例和运行时 profile 后,仅在客户端实现和[合成客户端测试](../../../../python/sdk/tests/test_client.py) 中发现反向通信。因此,导出的 `IncomingRequest` 模型没有受支持运行时的生产方。 -这是[定向 JSON-RPC 提案](../../rejected/simplification/2026-07-19-make-jsonrpc-directional.zh.md) 的缩小范围续案;该提案的否决原因明确允许单独重提传输收缩。其完成语义重设计仍被否决。它对共享 TypeScript 传输的判断已不适用:[Codex wire 适配器](../../../../packages/subagent/subagent-codex/src/wire.ts) 在两个方向上都使用请求和通知。 +本提案只收缩 Python 客户端。[SDK 服务端](../../../../packages/sdk/server/README.zh.md) 保留持久化入队回执和 Session 事件流;prompt 响应不表示轮次完成。共享 TypeScript 传输保留双向能力,因为 [Codex wire 适配器](../../../../packages/subagent/subagent-codex/src/wire.ts) 在两个方向上都使用请求和通知。 ## 提案 diff --git a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.i18n.yaml index 1f094f2d6f..80524894f7 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.i18n.yaml @@ -6,8 +6,8 @@ en: 081a6a1f8f256c6e zh: c49dd9c0e7e42f24 /agent-note-retire-the-prompt-registry-change-event/problem: - en: 22259f002f329483 - zh: d5a9c78a80af5187 + en: 53a2f8957e78dc15 + zh: 75fa547166f1d45a /agent-note-retire-the-prompt-registry-change-event/proposal: en: dba84cfd3aa5f719 zh: d3f6eece6f2f8148 diff --git a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.md b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.md index 1103675525..80cee5fbad 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.md +++ b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.md @@ -8,7 +8,7 @@ English | [中文](2026-09-19-retire-prompt-registry-change-event.zh.md) [`SystemPrompt`](../../../../packages/core/system-prompt/src/index.ts) emits `system-prompt/change` when providers register or dispose. Repository searches find no executing product listener, but generated Host discovery advertises the event. Tests maintain notification counts and rollback after listener exceptions. This is a public extension with maintenance obligations, even without a first-party subscriber. -The active [Remote event delivery decision](../../implemented/architecture/2026-08-10-remote-event-delivery.md) explicitly retains this extension despite having no shipped consumer. Per-step assembly predates that promise and serves a different purpose: rebuilding model input does not replace push observation of registry changes. +The active [Remote event delivery reference](../../../../packages/api/remotes/README.md) explicitly retains this extension despite having no shipped consumer. Per-step assembly predates that promise and serves a different purpose: rebuilding model input does not replace push observation of registry changes. ## Proposal diff --git a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.zh.md b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.zh.md index a49df2eeba..41563c0e9b 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.zh.md +++ b/.agents/notes/proposed/simplification/2026-09-19-retire-prompt-registry-change-event.zh.md @@ -8,7 +8,7 @@ Status: proposed [`SystemPrompt`](../../../../packages/core/system-prompt/src/index.ts) 在提供方注册或 dispose(资源释放)时发出 `system-prompt/change`。仓库搜索未找到执行的产品监听者,但生成的 Host 发现目录会展示该事件。测试维护通知次数及监听者抛出异常后的回滚行为。即使没有第一方订阅者,它仍是具有维护义务的公开扩展。 -活动的 [Remote 事件投递决策](../../implemented/architecture/2026-08-10-remote-event-delivery.zh.md) 明确保留了这一扩展,尽管没有随产品提供的消费方。逐步骤组装早于该承诺,且用途不同:重新构建模型输入不能替代注册表变更的推送观测。 +活动的 [Remote 事件投递参考](../../../../packages/api/remotes/README.zh.md) 明确保留了这一扩展,尽管没有随产品提供的消费方。逐步骤组装早于该承诺,且用途不同:重新构建模型输入不能替代注册表变更的推送观测。 ## 提案 diff --git a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.i18n.yaml b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.i18n.yaml index 06790c6cff..be77cf402a 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.i18n.yaml @@ -9,8 +9,8 @@ en: ff308f45fc6f5242 zh: 025b58883a3e175b /agent-note-keep-shell-environment-declarations-focused-on-key-ownership/proposal: - en: ffd78167917dfa4b - zh: dbcb1004009ea9f5 + en: 38511c6da912406d + zh: 2ee9dcf7a611c57f /agent-note-keep-shell-environment-declarations-focused-on-key-ownership/alternatives-considered: en: 476de18c44f2ad86 zh: a0a15da6c5e7fbf7 diff --git a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.md b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.md index 7b74e55e6b..8487a6253d 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.md +++ b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.md @@ -14,7 +14,7 @@ The method is nevertheless discoverable through the [Cordis inspection provider] Represent a contributor's declaration as an explicit readonly collection of keys. Remove description objects, `BashEnvVariable`, `BashEnvVariableInfo`, `list()`, description-only validation, and the exhaustive-catalog TODO. Update the Web contributor, README pair, and generated service/type declarations together. -Keep `register`, `collect`, reserved keys, ownership conflicts, undeclared-output refusal, deterministic environment output, and effect disposal. The [PowerShell parity decision](../../implemented/feature/2026-08-02-pwsh-tool-bash-parity.md) continues to own the shared registry and both shell consumers. The proposal removes one public method, two metadata types, and their validation/enumeration tests; roughly 32 identified source lines disappear before the small key-membership adaptation. +Keep `register`, `collect`, reserved keys, ownership conflicts, undeclared-output refusal, deterministic environment output, and effect disposal. The [PowerShell parity reference](../../../../packages/shell/shell-env/README.md) continues to own the shared registry and both shell consumers. The proposal removes one public method, two metadata types, and their validation/enumeration tests; roughly 32 identified source lines disappear before the small key-membership adaptation. ## Alternatives considered diff --git a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.zh.md b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.zh.md index 03d7ed9fde..ebc0524f58 100644 --- a/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.zh.md +++ b/.agents/notes/proposed/simplification/2026-09-19-shell-env-key-declarations.zh.md @@ -14,7 +14,7 @@ Status: proposed 将贡献方声明表示为显式只读键集合。删除描述对象、`BashEnvVariable`、`BashEnvVariableInfo`、`list()`、仅针对描述的校验,以及完整目录 TODO。同时更新 Web 贡献方、README 配对与生成的服务/类型声明。 -保留 `register`、`collect`、保留键、所有权冲突、拒绝未声明输出、确定性的环境输出和 effect 释放。[PowerShell 对齐决策](../../implemented/feature/2026-08-02-pwsh-tool-bash-parity.zh.md) 继续拥有共享注册表及两个 shell 消费方。提案删除一个公共方法、两个元数据类型及其校验/枚举测试;在少量键成员判定适配前,已定位可删除约 32 行源码。 +保留 `register`、`collect`、保留键、所有权冲突、拒绝未声明输出、确定性的环境输出和 effect 释放。[PowerShell 对齐参考](../../../../packages/shell/shell-env/README.zh.md) 继续拥有共享注册表及两个 shell 消费方。提案删除一个公共方法、两个元数据类型及其校验/枚举测试;在少量键成员判定适配前,已定位可删除约 32 行源码。 ## 考虑过的替代方案 diff --git a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.i18n.yaml b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.i18n.yaml index c1c40758be..1937f884f0 100644 --- a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.i18n.yaml +++ b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.i18n.yaml @@ -6,8 +6,8 @@ en: 521fac2a2df2498e zh: 9a508486144b2e74 /agent-note-mutation-testing-as-the-coverage-counterweight/problem: - en: f67a5e8beb51c898 - zh: 9f997eaf27491ea4 + en: bb51340cca98a1be + zh: ee8e9297edf623bb /agent-note-mutation-testing-as-the-coverage-counterweight/proposal: en: 4aa172d4d1c7e419 zh: dae8a42d5693e509 diff --git a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.md b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.md index 591d901264..1e996158ff 100644 --- a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.md +++ b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.md @@ -6,7 +6,7 @@ English | [中文](2026-06-11-mutation-testing.zh.md) ## Problem -The per-file 100% coverage gate ([the quality-gates decision](../../implemented/process/2026-06-11-quality-gates.md)) proves every line *executes* under test — not that any assertion would notice if the line were wrong. Under agent-written tests, coverage pressure can produce execution-without-assertion. Mutation testing measures what coverage cannot: whether the suite *kills* deliberately injected bugs. +The per-file 100% coverage gate ([the quality-gates reference](../../../../docs/testing.md)) proves every line *executes* under test — not that any assertion would notice if the line were wrong. Under agent-written tests, coverage pressure can produce execution-without-assertion. Mutation testing measures what coverage cannot: whether the suite *kills* deliberately injected bugs. ## Proposal diff --git a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.zh.md b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.zh.md index a8e786ab65..cd80dac8a5 100644 --- a/.agents/notes/proposed/testing/2026-06-11-mutation-testing.zh.md +++ b/.agents/notes/proposed/testing/2026-06-11-mutation-testing.zh.md @@ -6,7 +6,7 @@ Status: proposed ## 问题 -逐文件 100% 覆盖率门禁([质量门禁决策](../../implemented/process/2026-06-11-quality-gates.zh.md))证明每一行代码在测试中都被*执行*了,但不能证明如果该行出错,任何断言会注意到。在 agent(智能体)编写测试的场景下,覆盖率压力可能产出「执行但不断言」的测试。变异测试衡量的正是覆盖率无法衡量的:测试套件是否能*杀死*被刻意注入的缺陷。 +逐文件 100% 覆盖率门禁([质量门禁说明](../../../../docs/testing.zh.md))证明每一行代码在测试中都被*执行*了,但不能证明如果该行出错,任何断言会注意到。在 agent(智能体)编写测试的场景下,覆盖率压力可能产出「执行但不断言」的测试。变异测试衡量的正是覆盖率无法衡量的:测试套件是否能*杀死*被刻意注入的缺陷。 ## 提案 diff --git a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.i18n.yaml b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.i18n.yaml index a1a2f350fc..139a14628d 100644 --- a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.i18n.yaml +++ b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.i18n.yaml @@ -6,8 +6,8 @@ en: 63685a0c0afc3f07 zh: 8f7e06562771ee1d /agent-note-runtime-schemas-for-the-event-vocabulary-zod-vs-the-merge-extensible-map-pattern/problem: - en: 6bea9eb883f324d2 - zh: 2bef9b9fa5233d09 + en: 8b2780f7c75a81a9 + zh: 62525da8b9750f84 /agent-note-runtime-schemas-for-the-event-vocabulary-zod-vs-the-merge-extensible-map-pattern/why-this-is-not-a-persistence-change: en: 42d48e48d694d3de zh: f556f4624b55a480 @@ -36,5 +36,5 @@ en: 79e720cfffe60257 zh: b4aea0d52f3ee072 /agent-note-runtime-schemas-for-the-event-vocabulary-zod-vs-the-merge-extensible-map-pattern/open-questions: - en: afc8275e1598eb34 - zh: f3684ee3aba33cf1 + en: 2c9095d05efbd00f + zh: c18fca57a4600284 diff --git a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.md b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.md index 5912c39016..d43a6d809d 100644 --- a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.md +++ b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.md @@ -8,7 +8,7 @@ English | [中文](2026-06-16-typed-event-schemas.zh.md) The harness models its core vocabulary — content blocks, message sources, finish reasons, turn triggers, turn-end reasons, and session events — as **merge-extensible maps**: a TypeScript `interface` (e.g. `SessionEventMap`, `ContentBlockMap`) that plugins augment via declaration merging, with the public union derived as `Map[keyof Map]`. This is the repo's universal extension pattern, documented in [docs/architecture.md](../../../../docs/architecture.md) ("The same merge-extensible-map pattern is used for `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`") and relied on by the `defineTool` `InferArgs` DSL and the `assertNever` exhaustiveness convention. -The pattern is **compile-time only**. The types vanish at runtime: there is no schema object to validate an incoming value against, parse untrusted input with, or enumerate at runtime. The [session-persistence contract](../../implemented/architecture/2026-06-14-session-persistence.md) exposes two consequences: +The pattern is **compile-time only**. The types vanish at runtime: there is no schema object to validate an incoming value against, parse untrusted input with, or enumerate at runtime. The [session-persistence contract](../../../../packages/session/session-persistence/README.md) exposes two consequences: 1. **Persistence treats `event.data` as opaque JSON.** The JSONL provider `JSON.stringify`s and `JSON.parse`s each event verbatim; the only runtime guard is `isJsonValue` (round-trip serializability — rejects BigInt, functions, cycles, non-finite numbers, …), not structural validation. A corrupted-but-still-JSON event datum (wrong field types, missing fields) round-trips silently and is only caught later, if at all, by a consumer's `switch`. 2. **No runtime contract for plugin-added variants.** A plugin that declaration-merges a new `SessionEventMap` key gets compile-time typing for its own code, but nothing validates that the values it produces match the shape it declared — at the producer, at the persistence boundary, or on reload. @@ -72,4 +72,4 @@ Defer. If runtime validation is wanted at the durable boundary, **Option B** (sc - If a registry is adopted, is the library **schemastery** (already in the tree, already the config schema lib) or **Zod** (richer ecosystem, currently only transitive)? Adopting two schema libraries is a cost in itself. - Can a hybrid keep compile-time inference (so `defineTool` and plugin DX survive) while adding an *optional* runtime schema per variant, validated only at the persistence/wire boundary rather than on every in-process append? -- Does the `ctx.invariants` service already cover enough of the runtime-shape gap when enabled that boundary validation is only needed for genuinely untrusted input (reload of an externally-modified log)? +- Did the `ctx.invariants` service, since removed, already cover enough of the runtime-shape gap when enabled that boundary validation is only needed for genuinely untrusted input (reload of an externally-modified log)? diff --git a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.zh.md b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.zh.md index 67d0c1be31..a30fd7b8ce 100644 --- a/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.zh.md +++ b/.agents/notes/rejected/architecture/2026-06-16-typed-event-schemas.zh.md @@ -8,7 +8,7 @@ Status: rejected — runtime event-schema registry declined; event maps stay com harness 将其核心词汇——内容块、消息来源、结束原因、轮次触发器、轮次结束原因与会话事件——建模为 **merge-extensible map**:一个 TypeScript `interface`(如 `SessionEventMap`、`ContentBlockMap`),插件通过声明合并对其扩展,公开联合类型则以 `Map[keyof Map]` 派生。这是本仓库的通用扩展模式,记录在 [docs/architecture.md](../../../../docs/architecture.zh.md) 中(「The same merge-extensible-map pattern is used for `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`」),`defineTool` 的 `InferArgs` DSL 和 `assertNever` 穷举约定都依赖于它。 -该模式**仅存在于编译期**。类型在运行时消失:没有 schema 对象可供校验传入值、解析不可信输入或在运行时枚举变体。[会话持久化约定](../../implemented/architecture/2026-06-14-session-persistence.zh.md)暴露了两个后果: +该模式**仅存在于编译期**。类型在运行时消失:没有 schema 对象可供校验传入值、解析不可信输入或在运行时枚举变体。[会话持久化约定](../../../../packages/session/session-persistence/README.zh.md)暴露了两个后果: 1. **持久化将 `event.data` 视为不透明 JSON。** JSONL provider 对每个事件原样执行 `JSON.stringify`/`JSON.parse`;唯一的运行时守卫是 `isJsonValue`(往返可序列化性检查:拒绝 BigInt、函数、循环引用、非有限数等),而非结构校验。一个损坏但仍为合法 JSON 的事件数据(字段类型错误、字段缺失)会静默往返,只有在后续消费方的 `switch` 中才可能被捕获。 2. **插件新增变体没有运行时约定。** 一个通过声明合并添加新 `SessionEventMap` 键的插件,在自身代码中获得了编译期类型,但没有任何机制校验它产出的值是否符合它所声明的形状——无论是在生产者处、持久化边界处还是重新加载时。 @@ -72,4 +72,4 @@ harness 将其核心词汇——内容块、消息来源、结束原因、轮次 - 如果采用注册表,库选 **schemastery**(已在仓库中,已作为配置 schema 库)还是 **Zod**(生态更丰富,目前仅为传递依赖)?同时维护两个 schema 库本身就是一种成本。 - 能否采用混合方案:保留编译期推断(使 `defineTool` 和插件开发体验不受影响),同时为每个变体添加*可选*的运行时 schema,仅在持久化/协议边界校验,而非每次进程内 append 都校验? -- `ctx.invariants` 服务启用后是否已覆盖了足够多的运行时形状缺口,使得边界校验仅在面对真正不可信输入(重新加载外部修改过的日志)时才有必要? +- 已移除的 `ctx.invariants` 服务启用后是否曾覆盖足够多的运行时形状缺口,使得边界校验仅在面对真正不可信输入(重新加载外部修改过的日志)时才有必要? diff --git a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.i18n.yaml b/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.i18n.yaml deleted file mode 100644 index 0d568431e3..0000000000 --- a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.i18n.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-25-client-settings-locale-theme.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.md -/agent-note-client-settings-locale-and-theme-layering: - en: ebeba4110da742d5 - zh: 3cfc57b1eb553185 -/agent-note-client-settings-locale-and-theme-layering/problem: - en: 4cec7e053c53607b - zh: 3796b79366e7ea58 -/agent-note-client-settings-locale-and-theme-layering/proposal: - en: 3dd489ca0eab19bd - zh: a57d5dd4d2496f04 -/agent-note-client-settings-locale-and-theme-layering/proposal/first-phase-registration-surfaces: - en: 7a4890dffd207a66 - zh: 4353be1cf48bf68d -/agent-note-client-settings-locale-and-theme-layering/proposal/slot-topology: - en: bc2aba013c52fca5 - zh: 734e5ef05792bdf9 -/agent-note-client-settings-locale-and-theme-layering/proposal/slot-declarations-are-first-class-injectable-waits: - en: 9df71cdd33cd691d - zh: 9ac3eba3d89192d9 -/agent-note-client-settings-locale-and-theme-layering/proposal/service-contracts: - en: 30c49423999492e5 - zh: bce71328fdd946c5 -/agent-note-client-settings-locale-and-theme-layering/alternatives-considered: - en: 258859f30f0b9784 - zh: 70d48285033c3d8f -/agent-note-client-settings-locale-and-theme-layering/acceptance-criteria: - en: 9ff6ce8ea22dab22 - zh: c8dd20442b50df21 -/agent-note-client-settings-locale-and-theme-layering/risks: - en: fb649d6c5a86bc09 - zh: cb5520268d365ca9 diff --git a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.md b/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.md deleted file mode 100644 index e7f4b64ab0..0000000000 --- a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.md +++ /dev/null @@ -1,130 +0,0 @@ -# Agent Note: Client Settings, Locale, and Theme layering - -Status: rejected — closed as a proposal: the shipped ui-settings, locale, and ui-theme packages and their READMEs own the design - -English | [中文](2026-07-25-client-settings-locale-theme.zh.md) - -## Problem - -The browser client's existing Settings is written directly inside the Sidebar, and language and theme are applied by component-local state mutating the DOM directly. As a result Settings cannot be extended by independent plugins, preference state has no stable cross-plugin service contract, and the theme registry carries both state and presentation responsibilities. - -## Proposal - -**Collaboration doctrine (how every later module joins Settings): feature owners self-register.** The Settings shell is a pure composition surface: it only declares slots and renders the chrome structure — zero copy, no locale dependency, and neither importing nor enumerating any feature; for a feature to appear in Settings, its own plugin registers into the corresponding slot — locale registers the Language row, ui-theme registers the Appearance row, ui-settings-models registers the Models top-level panel. No separate `ui-settings-*` package is created for "a feature's settings page": the settings surface belongs to the feature package itself (shipping the Theme feature means Theme's settings choices ship with ui-theme). Content that belongs to no single feature (the trigger/title/close chrome copy, the General directory with its skeleton rows, the `settings` dictionary) is owned by `ui-settings-general` — the owner of the ownerless copy, not a feature satellite package. - -The Sidebar declares the `sidebar.settings` single slot; `ui-settings` occupies it and declares four slots: `settings.trigger` / `settings.header` / `settings.close` (chrome content seats, single) and `settings.section` (top-level pages, list). Accessible names all resolve from slot content: the trigger's accessible name is its text content, the dialog points at the header content node via aria-labelledby, and close is a visually hidden text seat. Each section is contributed by a feature plugin; the shell only reads entry metadata from the slot ledger to build the navigation, rendering the current section via `only`. General is registered by `ui-settings-general` (order 0) and declares the `settings.general.item` list slot, into which the feature plugins' preference rows slot by order. - -The Settings entry is the Settings row in the sidebar Foot; clicking it directly opens a 1080×700 centered overlay (black 24% mask); the close button, a mask click, and ESC all close it. There is no intermediate menu form of any kind. - -`@deepseek-ai/dsh-client-locale` provides `ctx.locale`; `ui-theme` provides `ctx.theme`. Both services read through a getter, write through a setter, and publish immutable snapshots via typed Cordis change events; each service persists its own preference (storing only the id, with bad values falling back to the default). - -Each feature row's apply layer subscribes to its own change event (locale to `locale/change`, ui-theme to `theme/change`) and projects the snapshot into the slot store declared when that row registered. React components only read `useStore` and write through the injected setter callbacks, never reading ctx or the services. - -The theme preference has three states — `light`, `dark`, `system` — defaulting to `system` (when no persisted preference exists or the value is bad). Resolving system belongs to the theme domain: ThemeRuntime holds the `prefers-color-scheme` matchMedia listener (environment sensing, not DOM presentation) and re-emits the snapshot when the preference is system and the system color scheme changes; the snapshot carries both `preference` and the resolved `active` definition. - -The theme service never touches the DOM. `ui-layout` reads the Theme getter initially and then subscribes to `theme/change`; the presenter owned by Layout updates `body[data-ds-dark-theme]` and the theme tokens according to `active`. The presenter has no notion of system — it consumes only resolved results. - -### First-phase registration surfaces - -| Registration surface | Owning plugin | First-phase content | -|---|---|---| -| chrome content (trigger/header/close) | `ui-settings-general` | Settings entry-row icon and copy, panel title, close hidden text | -| General section (order 0) | `ui-settings-general` | Permission and Tool Call visual skeletons (no write operations) plus the `settings.general.item` slot declaration | -| Language row (item order 0) | `locale` | Selector dropdown; 中文/English genuinely switch | -| Appearance row (item order 10) | `ui-theme` | Light/Dark/System three cubes genuinely switch (the selected state reflects preference) | -| Models section (order 10) | `ui-settings-models` | Navigation item only, with an empty content area; later model-management features land in that package | -| Plugin | none | Not built this phase, and the navigation does not show the item (once a later plugin feature package registers the section it appears automatically) | - -The first phase localizes only the copy inside the Settings overlay; dictionaries stay close to their owners — the chrome plus the General skeletons live in `ui-settings-general`'s `settings` namespace, and feature-row copy lives in each feature package (`settings.locale`, `settings.theme`, `settings.models`). - -### Slot topology - -```text -root -└─ sidebar - └─ sidebar.settings single/root - └─ ui-settings(壳,零文案) - ├─ settings.trigger single/root ui-settings-general 注册 - ├─ settings.header single/root ui-settings-general 注册 - ├─ settings.close single/root ui-settings-general 注册 - └─ settings.section list/root - ├─ general (order 0) ui-settings-general 注册 - │ └─ settings.general.item list/root - │ ├─ language (0) locale 注册 - │ └─ appearance (10) ui-theme 注册 - └─ models (order 10) ui-settings-models 注册 -``` - -Section and item contributions use `ctx.slots.inject()` and do not depend on the client manifest's apply order; localized labels ride the label thunk from the [full-rollout note](../../archived/architecture/2026-07-30-client-locale-full-rollout.md). The SlotMap types split homes: trigger/header/close/section have their canonical home in the ui-settings contract (the consumers, general and models, both depend on the shell — no cycle); `settings.general.item`'s canonical home is the locale package — it is the lowest common dependency of all item registrants (a settings row always carries copy), while the declarer general's contract is unreachable from locale/ui-theme (it would form a cycle); ui-theme consumes it through a re-export outlet. - -### Slot declarations are first-class injectable waits - -`SlotRegistry.inject()` now waits on the typed ledger key directly; it does not bridge declarations into synthetic `slot:` Cordis services. The callback follows declaration collapse and redeclaration while its controller remains owned by the contributing plugin fiber, and direct registration into an undeclared slot still fails loud. This removes the stale-disposer presence machine and the typo-prone parallel service namespace. The complete lifecycle and failure contract lives in the [slot declaration injection decision](../../archived/architecture/2026-08-05-slot-declaration-injection.md). - -### Service contracts - -```ts -export type ThemePreference = 'light' | 'dark' | 'system' - -export interface ThemeDefinition { - id: string - colorScheme: 'light' | 'dark' - tokens: Record -} - -export interface ThemeSnapshot { - preference: ThemePreference - active: ThemeDefinition // system 已解析为具体 light/dark 定义 - themes: readonly ThemeDefinition[] - revision: number -} - -export interface LocaleDefinition { - id: 'zh' | 'en' - label: string -} - -export interface LocaleSnapshot { - active: 'zh' | 'en' - locales: readonly LocaleDefinition[] - revision: number -} - -export interface Events { - /** @param snapshot - Current locale registry snapshot. @mode emit */ - 'locale/change'(snapshot: LocaleSnapshot): void - /** @param snapshot - Current theme registry snapshot. @mode emit */ - 'theme/change'(snapshot: ThemeSnapshot): void -} -``` - -Locale ships with 中文 and English built in; `setLocale`/`setTheme` are the only write entry points, and an unknown id fails. - -## Alternatives considered - -**Having the app shell subscribe to preferences centrally and re-render the root slot tree.** A language or theme change only needs to update the actual consumers; a whole-tree refresh amplifies the blast radius and wires business preferences into the shell. - -**The theme service mutating the DOM directly.** The registry service would then depend on the presentation environment, with unclear lifecycle and global-style ownership; Layout already owns the page-root presentation boundary. - -**Resolving system in the Layout presenter.** The presenter would need its own matchMedia subscription and would pick the concrete definition out of the themes list, forcing the presentation layer to understand preference semantics; resolving on the service side gives every consumer the same resolved snapshot. - -**Settings importing and enumerating the sections.** Adding a page would require modifying the shell plugin, breaking the composition model where each feature occupies a slot from its own plugin. - -**A per-feature `ui-settings-*` satellite package for each section.** It divorces the settings surface from the feature itself: changing Theme behavior touches two packages, the package count grows linearly with settings items, and the satellite packages depending back on the locale/theme services form an intermediate layer that exists purely for the package split. Under feature-owner self-registration that layer does not exist: preference rows ship with their feature packages, and `ui-settings-general` takes in only the ownerless copy (the chrome and the General skeletons), carrying no feature's settings surface. - -**Injecting the Locale/Theme snapshots into React directly.** Inject results are cached by entry identity, so volatile values go stale; hand-rolling a React hook per service also bypasses the slot store's unified binding. - -## Acceptance criteria - -- The Settings shell depends only on the slot ledger, never on any feature implementation; General's item list likewise depends only on the ledger. -- Adding a settings item = the feature package registering it itself (a section or a general item), with zero shell changes. -- Locale and Theme writes go only through the setters; ongoing synchronization goes only through the change events. -- Each feature row's store initializes from the getter and is thereafter updated by its own change event with local re-renders. -- Layout applies the theme snapshot on its own and the theme service never accesses the DOM; no system branch appears in the presenter. -- 中文/English and Light/Dark/System switch and are restored after a refresh; with the preference on system, a system color-scheme change takes effect immediately. -- Models has only a navigation item and an empty content area; the Permission and Tool Call skeletons perform no writes. -- The overlay closes via the close button, a mask click, and ESC. - -## Risks - -The apply order of slot declarations and contributions is not fixed, so every section/item registrant must use `ctx.slots.inject()` rather than a service or local-disposer presence signal. Service events may fire before a row's first render, so both a feature row store's init and the inject attach must align to the current snapshot from the getter. The duplicated merge copies of `settings.general.item` (locale, ui-theme) must stay verbatim-identical to the ui-settings canonical home — any drift means changing all three together. Layout must clean up the global attributes it set on unmount, and ThemeRuntime must remove its matchMedia listener on dispose, so nothing lingers after HMR. diff --git a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.zh.md b/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.zh.md deleted file mode 100644 index 9edd7395af..0000000000 --- a/.agents/notes/rejected/architecture/2026-07-25-client-settings-locale-theme.zh.md +++ /dev/null @@ -1,130 +0,0 @@ -# Agent Note: Client Settings、Locale 与 Theme 分层 - -Status: rejected — closed as a proposal: the shipped ui-settings, locale, and ui-theme packages and their READMEs own the design - -[English](2026-07-25-client-settings-locale-theme.md) | 中文 - -## 问题 - -浏览器端已有的 Settings 直接写在 Sidebar 内,语言和主题也由组件本地状态直接改 DOM。这使 Settings 无法由独立插件扩展,偏好状态没有稳定的跨插件服务约定,主题注册表同时承担状态与呈现职责。 - -## 提案 - -**协作导向(后续所有模块接入 Settings 的方式):功能属主自注册。** Settings 壳是纯组合面:只声明 slot、渲染 chrome 结构,零文案、不依赖 locale、不 import 也不枚举任何功能;一个功能要出现在 Settings 里,由它自己的插件向对应 slot 注册——locale 注册 Language 行,ui-theme 注册 Appearance 行,ui-settings-models 注册 Models 一级面板。不为「某功能的设置页」单开 `ui-settings-*` 包:设置面属于功能包本身(做 Theme 功能,Theme 的设置选择就随 ui-theme 一起交付)。不属于任何单一功能的内容(trigger/标题/close 的 chrome 文案、General 目录与骨架行、`settings` 字典)由 `ui-settings-general` 拥有——它是「无主文案」的属主,不是功能卫星包。 - -Sidebar 声明 `sidebar.settings` single slot,`ui-settings` 占用它并声明四个 slot:`settings.trigger` / `settings.header` / `settings.close`(chrome 内容座,single)与 `settings.section`(一级页面,list)。无障碍名称全部解析自 slot 内容:trigger 的无障碍名称即其文本内容,dialog 经 aria-labelledby 指向 header 内容节点,close 是视觉隐藏文本座。每个 section 由功能插件贡献;壳只从 slot ledger 读取 entry metadata 生成导航,通过 `only` 渲染当前 section。General 由 `ui-settings-general` 注册(order 0)并声明 `settings.general.item` list slot,功能插件的偏好行按 order 排入。 - -Settings 入口是 sidebar Foot 的 Settings 行,点击直接打开 1080×700 居中浮层(黑 24% 遮罩);close 按钮、点击遮罩、ESC 均关闭。无任何中间菜单形态。 - -`@deepseek-ai/dsh-client-locale` 提供 `ctx.locale`,`ui-theme` 提供 `ctx.theme`。两个服务都以 getter 读取、setter 写入并用 typed Cordis 变更事件发布不可变快照;服务自己持久化偏好(只存 id,无效值回退默认)。 - -功能行的 apply 层各自订阅自家变更事件(locale 订 `locale/change`,ui-theme 订 `theme/change`),把快照投影到该行注册时声明的 slot store。React 组件只读 `useStore`、写注入的 setter callback,不读取 ctx 或服务。 - -Theme 偏好三态:`light`、`dark`、`system`,默认 `system`(无持久化偏好或无效值时)。system 的解析属主题领域:ThemeRuntime 持有 `prefers-color-scheme` matchMedia 监听(环境感知,非 DOM 呈现),偏好为 system 且系统配色变化时重发快照;快照同时携带 `preference` 与解析后的 `active` 定义。 - -Theme 服务不操作 DOM。`ui-layout` 初始读取 Theme getter,随后订阅 `theme/change`,由 Layout 持有的 presenter 按 `active` 更新 `body[data-ds-dark-theme]` 和主题 token;presenter 不感知 system,只消费已解析结果。 - -### 首期注册面 - -| 注册面 | 属主插件 | 首期内容 | -|---|---|---| -| chrome 内容(trigger/header/close)| `ui-settings-general` | 设置入口行图标+文案、面板标题、close 隐藏文本 | -| General section(order 0)| `ui-settings-general` | Permission、Tool Call 视觉骨架(无写操作)+ `settings.general.item` slot 声明 | -| Language 行(item order 0)| `locale` | Selector 下拉,中文/English 真实可切 | -| Appearance 行(item order 10)| `ui-theme` | Light/Dark/System 三 cube 真实可切(选中态看 preference) | -| Models section(order 10)| `ui-settings-models` | 仅导航项,内容区为空;后续模型管理功能落在该包 | -| 插件 | 无 | 首期不做,导航不出现该项(后续插件功能包注册 section 即自动出现) | - -首期只对 Settings 浮层内文案进行本地化;字典就近存放——chrome + General 骨架归 `ui-settings-general` 的 `settings` namespace,功能行文案归各功能包(`settings.locale`、`settings.theme`、`settings.models`)。 - -### slot 拓扑 - -```text -root -└─ sidebar - └─ sidebar.settings single/root - └─ ui-settings(壳,零文案) - ├─ settings.trigger single/root ui-settings-general 注册 - ├─ settings.header single/root ui-settings-general 注册 - ├─ settings.close single/root ui-settings-general 注册 - └─ settings.section list/root - ├─ general (order 0) ui-settings-general 注册 - │ └─ settings.general.item list/root - │ ├─ language (0) locale 注册 - │ └─ appearance (10) ui-theme 注册 - └─ models (order 10) ui-settings-models 注册 -``` - -section/item contribution 使用 `ctx.slots.inject()`,不依赖 client manifest(元数据清单)的 apply 顺序;本地化 label 走 [全量接入 Note](../../archived/architecture/2026-07-30-client-locale-full-rollout.md) 的 label thunk。SlotMap 类型分家:trigger/header/close/section 正家在 ui-settings 约定(消费方 general/models 均依赖壳,无环);`settings.general.item` 正家在 locale 包——它是全部 item 注册方的最低公共依赖(设置行必带文案),而声明方 general 的约定对 locale/ui-theme 不可达(会成环);ui-theme 经 re-export 出口消费。 - -### slot 声明是一等可注入等待对象 - -`SlotRegistry.inject()` 直接等待有类型约束的 ledger key;它不会将声明桥接为合成的 `slot:` Cordis 服务。回调会跟随声明折叠与重新声明,而其控制器仍归贡献方插件 fiber 所有;直接向未声明 slot 注册仍会直接报错。这删除了基于陈旧 disposer 的在位状态机,以及容易因拼写错误出错的平行服务命名空间。完整的生命周期与失败约定见 [slot 声明注入决策](../../archived/architecture/2026-08-05-slot-declaration-injection.md)。 - -### 服务约定 - -```ts -export type ThemePreference = 'light' | 'dark' | 'system' - -export interface ThemeDefinition { - id: string - colorScheme: 'light' | 'dark' - tokens: Record -} - -export interface ThemeSnapshot { - preference: ThemePreference - active: ThemeDefinition // system 已解析为具体 light/dark 定义 - themes: readonly ThemeDefinition[] - revision: number -} - -export interface LocaleDefinition { - id: 'zh' | 'en' - label: string -} - -export interface LocaleSnapshot { - active: 'zh' | 'en' - locales: readonly LocaleDefinition[] - revision: number -} - -export interface Events { - /** @param snapshot - Current locale registry snapshot. @mode emit */ - 'locale/change'(snapshot: LocaleSnapshot): void - /** @param snapshot - Current theme registry snapshot. @mode emit */ - 'theme/change'(snapshot: ThemeSnapshot): void -} -``` - -Locale 内置中文和 English;`setLocale`/`setTheme` 是唯一写入口,未知 id 失败。 - -## 曾考虑的替代方案 - -**由 app shell 统一订阅偏好并重渲染 root slot tree。** 语言和主题变化只需要更新实际消费方;全树刷新放大影响面,也把业务偏好接入 shell。 - -**Theme 服务直接修改 DOM。**注册表服务因此依赖呈现环境,生命周期与全局样式所有权不清;Layout 已经拥有页面根呈现边界。 - -**system 由 Layout presenter 解析。** presenter 需自带 matchMedia 订阅并在 themes 列表里挑选具体定义,呈现层被迫理解偏好语义;解析放服务侧则所有消费方拿到一致的已解析快照。 - -**Settings import 并枚举各 section。** 新增页面必须修改壳插件,破坏「每个功能由自己的插件占用 slot」的组合模型。 - -**按功能为每个 section 单开 `ui-settings-*` 卫星包。** 设置面与功能本体分家:改 Theme 行为要动两个包,包数随设置项线性膨胀,且卫星包反向依赖 locale/theme 服务,形成纯粹为拆包而生的中间层。功能属主自注册下不存在这层:preference 行随功能包交付;`ui-settings-general` 只收无主文案(chrome 与 General 骨架),不承载任何功能的设置面。 - -**把 Locale/Theme 快照直接注入 React。** inject 结果按 entry identity 缓存,易变值会陈旧;为每个服务自造 React 钩子也绕开 slot store 的统一绑定。 - -## 验收标准 - -- Settings 壳只依赖 slot ledger,不依赖任一功能实现;General 的 item 列表同样只依赖 ledger。 -- 新增一个设置项 = 功能包自己注册(section 或 general item),零壳改动。 -- Locale 与 Theme 的写入只走 setter,持续同步只走变更事件。 -- 功能行 store 初始化走 getter,后续由自家变更事件更新并局部重渲染。 -- Layout 独立应用 Theme 快照,Theme 服务不访问 DOM;presenter 不出现 system 分支。 -- 中文/English 与 Light/Dark/System 能切换并刷新后恢复;偏好为 system 时系统配色变化即时生效。 -- Models 只有导航项与空内容区;Permission、Tool Call 骨架无写操作。 -- 浮层经 close 按钮、遮罩点击、ESC 均可关闭。 - -## 风险 - -slot 声明与 contribution 的 apply 顺序不固定,所有 section/item 注册方必须使用 `ctx.slots.inject()`,而不能以服务或本地 disposer 作为在位信号。service event 可能早于行首次渲染,功能行 store 的 init 与 inject attach 都必须从 getter 对齐当前快照。`settings.general.item` 的重复合并副本(locale、ui-theme)与 ui-settings 正家必须逐字一致,漂移即三处一起改。Layout 卸载时必须清理自己设置的全局属性,ThemeRuntime dispose(资源释放)时必须移除 matchMedia 监听,避免 HMR(热模块替换)后残留。 diff --git a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.i18n.yaml b/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.i18n.yaml deleted file mode 100644 index ede08a7406..0000000000 --- a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.i18n.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-08-cordis-web-dynamic-packages.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.md -/agent-note-cordis-host-client-dynamic-plugin-runtime: - en: f89118c354fe715d - zh: dcee1e4dd9bbb250 -/agent-note-cordis-host-client-dynamic-plugin-runtime/problem: - en: 19617f888995a1ec - zh: e4abb5efa2b77571 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal: - en: 986dd8097aac4d3d - zh: 986dd8097aac4d3d -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/core-principles: - en: 400bacbc13cc4ca2 - zh: efb84f4257ebf13c -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/package-responsibilities-and-dependency-direction: - en: 60b7a0c8880e579c - zh: d85388f9e9d34589 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/domain-objects: - en: fb29437ba7cc624a - zh: 2c2322991e282f7c -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/domain-objects/plugin: - en: 84d17d0aa91fa20c - zh: 354cf1a6531e2353 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/domain-objects/package: - en: 950f4e014a7fa6df - zh: 9641809a80ddbb9f -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/domain-objects/plugin-run: - en: f5cc71352e3f2eea - zh: 6933e78a031bb3e1 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/domain-objects/version-pointers: - en: 0d17a946a46eb645 - zh: 0d82d5287cd227af -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/host-authority-and-persistence: - en: 02a605a63f0b9d06 - zh: 3918b603de86ad4b -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/define-run-and-version-switching: - en: dce63bf4526ae240 - zh: 32898ea25290fd35 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/client-approval-and-authorization: - en: d3c874acd464c5da - zh: 04ffde8db6a0daa9 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/client-activation-orchestration: - en: 58708703b5069952 - zh: c378e4398b8d8063 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/package-private-client-to-host-communication: - en: 02f6557f8f3f1155 - zh: 33e2f26f371f20b6 -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/dynamic-code-guard-and-lifecycle: - en: 601f8cc278c10286 - zh: 31aa97d5c66b086c -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/inspect-providers-and-catalogs: - en: 8febbabca425cce7 - zh: 078461769a69719f -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/model-guidance-layers: - en: 879ad6fef8ca4ae7 - zh: 38ec8d838628bacd -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/pluginid-and-tool-ui: - en: 03c501a6503961fc - zh: 4e5b6633a393f8ca -/agent-note-cordis-host-client-dynamic-plugin-runtime/proposal/errors-and-model-feedback: - en: ec2d9dedbe2cd9d5 - zh: 005260a95cda020c -/agent-note-cordis-host-client-dynamic-plugin-runtime/alternatives-considered: - en: ef5f387f4019d7ad - zh: 4d5da887bafc5f22 -/agent-note-cordis-host-client-dynamic-plugin-runtime/acceptance-criteria: - en: 3a4b3100f19377d6 - zh: c36d31e04f7d4b2d -/agent-note-cordis-host-client-dynamic-plugin-runtime/risks: - en: 3ec21c3758ab98a8 - zh: e949a28f8e38042c diff --git a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.md b/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.md deleted file mode 100644 index e6c7b91351..0000000000 --- a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.md +++ /dev/null @@ -1,270 +0,0 @@ -# Agent Note: Cordis Host/Client Dynamic Plugin Runtime - -Status: rejected — closed as a proposal: the shipped packages/extensions runtime and its READMEs own the design - -English | [中文](2026-08-08-cordis-web-dynamic-packages.zh.md) - -## Problem - -The model needs to extend the current DSH process temporarily without modifying repository source, rebuilding the application, or refreshing the browser. An extension may run in the Host Node.js process, in a Client browser page, or as one plugin whose Host half retrieves data and whose Client half presents it. - -This capability cannot be limited to “execute some code.” Before writing code, the model needs to discover the Services, Events, Builtins, Slots, and theme tokens available on both platforms. The user needs to preview the code before deciding whether Client code may enter the page. A single plugin needs immutable versions, retries after failure, and rollback. Asynchronous runtime errors need to return to the model instead of remaining only in server logs or the browser console. - -Combining definition, approval, execution, version switching, capability discovery, and UI state into one action creates states that cannot be explained consistently: whether a successful definition also means a successful run; which version remains successful after a failed update; how long a Tool should wait when no page responds; which historical card owns the business UI after the same Package runs multiple times; and whether page-local Client load state can represent process-wide Host state. - -## Proposal - -### Core principles - -- The Host is the sole process-wide authority for Plugins, Packages, Runs, approvals, and version pointers. -- The Client stores only the current page's approval interaction, load results, Slot contributions, business views, and page-local errors. -- Define creates only immutable code versions; Run activates only a defined version. -- A version switch commits `currentPackageId` only after the target Package completes its required Host/Client activation. -- Before writing code, the model queries capabilities through Inspect Providers. Inspect results assist coding and are not plugin runtime business data. -- Dynamic Host and Client code both use restricted plain JavaScript contexts and attach reversible side effects to the Cordis lifecycle. -- Client code requires user authorization before entering a page. Authorization may cover one Package or future versions of the same Plugin. -- Tool calls do not wait for approval or browser operations that may occur only after the current turn ends. State stores and model steering report asynchronous outcomes. - -### Package responsibilities and dependency direction - -Four packages under `packages/self-modification/` implement the dynamic runtime: - -| Package | npm package | Responsibility | -| --- | --- | --- | -| `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` | Registers the System Prompt, seven model-facing Tools, Host Inspect Providers, `@pluginId` context injection, and Tool presentation metadata | -| `cordis-host-runner` | `@deepseek-ai/dsh-cordis-host-runner` | Stores the authoritative Registry, allocates IDs, executes Host code, and manages versions, approvals, Runs, private handlers, Inspect routing, and model feedback | -| `cordis-client-runner` | `@deepseek-ai/dsh-cordis-client-runner` | Synchronizes Inspect manifests in the browser, orchestrates approved Host→Client activation, evaluates Client code, and manages the Guard, Loader/Fiber, timer, styles, and teardown | -| `ui-cordis` | `@deepseek-ai/dsh-client-ui-cordis` | Renders Define/Run Tool cards, the global Cordis panel, approval controls, version selection, runtime status, and Package-specific business views | - -`tool-cordis` depends only on the Host Runner's in-process service and does not import the Client implementation. `ui-cordis` consumes only the Client Runner face and Client-safe wire types and does not import the Host implementation. Existing generated Remote APIs and forwarded events connect Host and Client runtime control; the gateway owns no dynamic Plugin domain logic. - -### Domain objects - -#### Plugin - -A Plugin is a dynamic plugin instance that can be modified over time. It is identified by the branded type `CordisDynamicPluginId`, for example `clock-1`. When creating a Plugin, the model submits only a semantic prefix of 3 to 6 lowercase English letters; the Host appends a process-unique numeric suffix. The model cannot specify the complete `pluginId`. - -A Plugin belongs to the Session that defined it. Model-facing Tools can read and operate only Plugins from the current Session. The global Client panel can list Plugins from all Sessions, but each action still executes under the owner Session carried by that row. - -#### Package - -A Package is an immutable code version under a Plugin. It is identified by `CordisDynamicPackageId`, for example `pkg-2`. It contains a name, a purpose, optional Host code, and optional Client code, with at least one code half present. Every `cordis_define` creates a new Package; an existing Package cannot be modified in place. - -One Plugin may own multiple Packages, but at most one physical Run may exist at a time. Whether a Package contains a Host or Client half affects only its activation steps, not its version identity. - -#### Plugin Run - -A Plugin Run is one concrete activation attempt. It is identified by `CordisDynamicPluginRunId`, for example `run-3`. Every new activation attempt receives a new ID, including an attempt that fails after approval, a retry of the same Package, and a version update. `pluginRunId` associates approval, Host activation, Client loading, private RPC, Tool cards, and errors with the same attempt. - -The Host stores the current physical Run separately from `latestRun`. The physical Run is the activation that can currently receive calls and be torn down. `latestRun` records the approval, phase, status of both halves, and diagnostics for the most recent attempt. A failed attempt may leave no live physical Run while remaining available for inspection. - -#### Version pointers - -- `currentPackageId` is the most recent Package to complete its required activation flow. Stopping the plugin, beginning an update, or failing an update does not clear it. -- `nextPackageId` is the target Package that is awaiting approval, activating, awaiting a Client, or most recently failed. It is cleared after the target succeeds and is committed as current. - -A Host-only Package commits current after the Host successfully establishes its Fiber. A Client-bearing Package commits current after Host activation succeeds and at least one Client successfully establishes the corresponding load. A Fiber that Cordis parks as waiting because a hard dependency is absent is still a successfully established lifecycle object; it is not equivalent to a parse or `apply` failure. - -If an update target fails, the old physical Run is not restarted automatically. The previous `currentPackageId` continues to identify the last successful version, and the failed target remains `nextPackageId`. The user or model can retry next, or reactivate current with `mode: "run"` to roll back. - -### Host authority and persistence - -`DynamicCordisRunnerService` and its internal Registry are the sole authority in the current DSH process. They store: - -- each Plugin's Session ownership and immutable Package set; -- `currentPackageId`, `nextPackageId`, the physical Run, and `latestRun`; -- per-Package authorization and cross-version Plugin authorization; -- pending Client activation requests; -- Host Fibers, Package-private handlers, waiting Services, and recent diagnostics; -- Host and Client Inspect Registry directories and query routing. - -These objects are not written to configuration or disk and are not restored after process restart. The Session Log may retain Tool calls, results, and metadata needed by cards, but it does not replay dynamic code to restore the Registry. Historical cards remain in the conversation after a restart, but their original `pluginId` and `packageId` are no longer runnable. - -Runtime state is not written to Session projection as recoverable state. Refreshing a page or opening a new page does not automatically restore Client halves; automatic restoration would reintroduce connection identity, startup baselines, and cross-page consistency protocols, which are outside this design. - -### Define, Run, and version switching - -`cordis_define` has two modes: creating a Plugin submits `idPrefix`, while modifying an existing Plugin submits its exact `pluginId`. Code always uses `code: { host?, client? }`. Define validates arguments and plain JavaScript syntax, records immutable source, and returns the final IDs. It does not execute `apply`, create approval, change version pointers, or run implicitly. - -There is no separate `cordis_update`. `cordis_run` expresses activation intent through `mode`: - -| Version relationship | `mode` | -| --- | --- | -| No `currentPackageId` exists | `run` | -| Target equals current, including restart, retry, or rollback | `run` | -| Target differs from an existing current | `update` | -| Retry `nextPackageId` after a failed update | `update` | - -Run first validates Plugin/Package ownership, the version relationship, and whether another transition is in progress. It then creates `pluginRunId` and writes `latestRun` and `nextPackageId`. - -A Host-only Package completes Host activation within the Tool call and returns `running` or a failure synchronously. A Client-bearing Package does not wait for a browser outcome within the Tool call: without authorization it registers approval and returns `awaiting-approval`; with authorization it registers automatic Client activation and returns `starting`. Both results mean that the request exists, not that full activation succeeded. - -When target activation actually starts, the Host stops the old physical Run before executing the target Host half. Only after Host success may the Client fetch and load source for the exact `pluginRunId`. Client success commits the version pointers. Any failure is recorded against that attempt, without restarting the old version and presenting it as target success. - -`cordis_stop` tears down the current Host/Client Run and pending approval request while retaining the Plugin, Packages, authorization, and version pointers. `cordis_undefine` stops first and then deletes the Plugin, Packages, authorization, and version pointers; historical cards then show only that the Plugin was removed. - -### Client approval and authorization - -A Package containing Client code requires user authorization before its first activation because model-generated code will run in the user's page. The approval panel offers three actions: - -- A single check authorizes the current Package. Later runs of the same Package do not need approval, but a new Package does. -- A double check authorizes future versions of the current Plugin. New Packages, updates, retries, and rollbacks no longer require per-version approval. -- Reject ends the current request without executing Host or Client code. The model must not immediately request approval again unless the user asks for it. - -Authorization is written to the Host Registry when the user allows it and remains even if a later technical step fails. When the panel runs a Package directly, the user's click itself authorizes that Package. - -A row awaiting approval shows only per-Package allow, cross-version allow, and reject; it does not simultaneously offer run, stop, or delete. The panel expands automatically when new approval appears. If auto-expansion fails or the panel is collapsed, the fixed entry and row status still show the pending approval count and state. - -### Client activation orchestration - -The Host sends Client activation requests through `cordis/request-run`. A request includes only request identity, Session, Plugin, Package, mode, name, purpose, and whether approval is required; it does not broadcast source code. - -An authorized page executes the following fixed sequence: - -1. Call `runHostHalf` to start the target Host half or bind to the same attempt's already-started Host Run. -2. After Host success, call `getClientCode` with `pluginId + pluginRunId` to retrieve only the exact current Run's Client source. -3. The Client Runner evaluates the plugin in the page, establishes its Loader entry/Fiber, and installs its Guard, styles, Slots, and page-local state. -4. The page reports success, waiting, or failure through `resolveRequestRun` or `settleUserRun`. -5. The Host accepts the report only for the still-current exact Run, commits current or saves diagnostics, and broadcasts request completion so other pages clear their activities. - -Host activation precedes Client activation so that the Client does not start before required Host handlers exist. A page may tear down the Host Run after Client failure only when that request created the Host Run; a page that merely bound to an existing Run does not own it. - -The Client Orchestrator stores pending approvals and active orchestration by `pluginId`, so one Plugin cannot run two page activations concurrently. Host inventory can reconstruct omitted pending-approval items and approval-free automatic activation requests. - -Client load state is a page-local fact. An active Host does not mean that the current page loaded the Client half. The UI has three primary states: gray “Ready” when no physical Run exists, yellow “Client ready to activate” when the Host runs but the current page has not successfully loaded the Client, and green “Running” when both halves are available in the current page. Approval and failure appear as additional states. - -This version does not establish per-connection identity or multi-page quorum. The first still-valid Client success may commit process-wide current; each page's store independently records whether that page loaded the Client. - -### Package-private Client-to-Host communication - -A dynamic Package uses a private JSON channel for Client-to-Host calls: the Host registers methods for the current Run with `harness.handle(method, handler)`, and the Client calls them with `host.call(method, args)`. Every call is associated with `pluginId + pluginRunId`, and the Host rejects stopped or stale Runs. Arguments and return values must be lossless JSON; functions, React elements, Contexts, Service instances, and class objects are forbidden. - -This channel serves only Client-to-Host calls within the same Package. It does not use public Remote Services or `ctx.remote` in dynamic code. The public Remote interface carries only the Runner's own control protocol and does not expose dynamic Packages. - -### Dynamic code, Guard, and lifecycle - -Host and Client both execute only plain JavaScript function bodies, without TypeScript, JSX, or bundler transformation. The Host executes in `node:vm`; the Client evaluates in a restricted closure. These contexts reduce misuse and provide instructional errors, but they are not security boundaries against malicious code. - -By default, the model reads an optional Service through `ctx.get('serviceName')` and checks for `undefined`. A plugin object declares `inject` only when the Service is a hard dependency whose absence must park the Package and whose later arrival must reactivate it. Direct `ctx.serviceName` access is allowed only when the same plugin declares the corresponding inject. - -Host and Client `timer` are same-named Cordis Services with the same interface, not global Builtins. A plugin that needs timers must declare `inject: ['timer']`; a timer created inside a React effect returns its disposer as cleanup. - -The current Fiber owns every registration and reversible side effect. Event listeners, Services, Tools, handlers, timers, Slots, styles, and theme overrides register through `ctx.effect()`, `ctx.on()`, or official APIs that return disposers. Stopping, updating, failure rollback, or undefining tears down both halves' contributions. Theme overrides are layered by source and return a disposer so unloading restores the previous theme values. - -Host, DSH, Cordis, and their Service instances, Event payloads, Slot props, Session/Conversation Snapshots, Tool state, and other runtime objects are internal live data. Dynamic code must not run `JSON.stringify`, `structuredClone`, recursive enumeration, full copying, or whole-object display on these objects or their descendants. It reads only leaf fields needed by the current task and constructs minimal owned data without Host references. - -### Inspect Providers and Catalogs - -Capability discovery uses three Tools: `cordis_inspect_list` lists Host/Client Provider manifests; `cordis_inspect_query` executes an explicit read-only query on the selected platform; and `cordis_inspect_self` queries the current Session's Plugins, Packages, source, version pointers, and runtime diagnostics. - -Host and Client each own a `CordisInspectRegistry`. A Provider registers a platform-unique ID, description, methods, input schemas, and output schemas. Provider methods are explicitly allowlisted queries, not arbitrary Service-method forwarding; the Registry has no layered target and does not automatically turn business Service methods into executable Inspect methods. - -The initial Providers are: - -| Platform | Provider.method | Data source | -| --- | --- | --- | -| Host / Client | `Service.listService` | Static Service Catalog for each platform | -| Host / Client | `Event.listEvents` | Static Event Catalog for each platform | -| Host / Client | `Builtin.listBuiltins` | Hand-maintained definitions beside the evaluator/Guard | -| Host | `Tool.listTools` | Tool Registry actually visible to the current Agent | -| Client | `Slots.listSubTree` | Static Slot Catalog plus the page's live subtree/occupants | -| Client | `Theme.listTokens` | Read-only inspect export from ThemeService | - -When the Client Registry changes, it synchronizes the complete manifest to the Host without storing duplicate directories per Session. Host queries execute locally. For a Client query, the Host broadcasts a request ID and a page executes the local Provider and responds. The Host accepts only the first successful result that passes output-schema validation; a failed page does not settle the request. If no page succeeds, the Tool remains pending until a later success or Tool-call cancellation. - -Inspect data is used only before code is written to confirm capabilities, signatures, types, and mounting protocols. A plugin that needs runtime business data calls the actual Service or listens to the actual Event; it must not cache, display, or depend on Inspect/Catalog results. - -`CordisCatalogProjector` generates Host and Client Service and Event Catalogs separately through TypeRT. The Slot AST generator scans `SlotMap`, registration options, standard props, owner props, and referenced types; the Slots Provider merges the static Catalog with the live tree at query time. ThemeService exports theme tokens, Builtins are maintained manually beside the evaluator/Guard, and Tool schemas come from the Registry. - -Catalog generation scans real source signatures and then applies a model-visible allowlist. The allowlist may hide Services, members, `@deprecated` APIs, Runner-owned Services, and `cordis/*` control Events, but it must not rewrite method names, parameters, or return types for the remaining APIs. Guard may reject arguments, fix sources, or hide members, but it must respect source signatures. - -Model-visible owner JSDoc requires only a complete description, `@param` for every parameter, `@returns` for every non-void return, `@mode` for Events, and descriptions for Slot/props fields. Usage recommendations, counterexamples, and cross-capability choices belong in the Skill rather than duplicated Catalog example fields. - -### Model guidance layers - -Model guidance has four layers: - -- The System Prompt carries the stable runtime model, restrictions of both platforms, lifecycle, approval, version pointers, minimum code rules, and a usage map for the seven Tools. It still supports a minimally correct implementation when the Skill is unavailable. -- The `cordis-plugin-development` Skill carries requirement navigation, capability composition, recommendations, and counterexamples without copying complete schemas. -- Each Tool description states only that action's prerequisites, parameter semantics, synchronous or asynchronous result, and next step. -- Provider/Catalog results supply current exact names, signatures, parameters, Slot props, tokens, and runtime query results. - -The System Prompt requires loading the Skill first, then listing/querying capabilities, and only then defining/running code. React examples in the Skill register into a Slot instead of returning a React Element directly from `apply()`. Examples use `React.createElement`, correct `ctx.get()`/`inject`, reversible effects, and minimal JSON RPC. - -### `@pluginId` and Tool UI - -The input system registers an `@pluginId` mention for the current Session. Selecting it injects only Plugin identity, the default baseline Package, version pointers, the active Run, and the latest status, not source code. The default baseline is selected in order from next, current, and the most recently defined Package. The model must read source through `cordis_inspect_self` before appending a Package in existing mode; an invalid mention must not silently create a replacement Plugin. - -The `cordis_define` card presents Host and Client code in two tabs. A `cordis_run` card associates with one exact attempt through `pluginRunId` and reads the Client store to show pending approval, Client ready to activate, running, failed, replaced by a later Run, or Plugin removed. - -A Package may register `key: "self"` into `tool.view.cordis`. At runtime, self binds to `pluginId + packageId`; the business Slot key omits `pluginRunId`, while owner props still provide the exact Run identity. The newest Run card for a Package owns the business UI, and earlier cards show that a newer Run exists. Cards react to store changes rather than scanning later Session Log entries or notifying one another. - -The global Cordis panel has one fixed entry and groups rows by current and other Sessions. Its title and collapse action remain fixed while only the list scrolls. A normal row can select a Package and run, stop, or delete it. A failed update can retry next or select current to roll back. A pending-approval row exposes only the two allow actions and reject. - -### Errors and model feedback - -Technical errors crossing Host and Client preserve the original `message` and preserve `stack` when the error object provides it. Structured diagnostics include `pluginId`, `packageId`, `pluginRunId`, and one phase: approval, host-load, host-apply, client-load, client-apply, or client-render. - -Host and Client Guards, Host evaluation and handlers, Client evaluation and apply, Slot `onEntryError`, and React ErrorBoundary all return errors to the owning Agent. The Client console also prints the original error object through `console.error`. A rendering error belongs to the exact Run and does not contaminate the immutable Package. - -After a model-initiated asynchronous Run succeeds, is rejected, or fails technically, `agent.steer` wakes the owning Agent. A technical failure requires the model to read diagnostics, correct the same Plugin, and retry autonomously. A user rejection forbids an automatic repeat request. A user's manual run, stop, or removal in the panel is supplied through context injection to the next step without waking the model proactively. - -## Alternatives considered - -**Combine Define and Run.** This removes the previewable “defined but not running” state and mixes syntax errors, approval, runtime errors, and retries into one action. The design therefore uses immutable Define and independent Run actions. - -**Use Package ID as Plugin ID.** A single-level ID cannot append immutable versions beneath a stable instance; updates would require stop, undefine, and a new define, while historical cards and `@` references could not retain object identity. The design therefore uses separate Plugin, Package, and Run identities. - -**Provide a separate `cordis_update`.** Update has the same loading, approval, UI, diagnostics, and execution semantics as Run, so a separate Tool would duplicate the protocol. It is represented by `cordis_run mode:"update"`. - -**Automatically restore the old physical Run after an update failure.** Automatic restoration combines “target failed” and “old version succeeded again” into one result. The design retains the old current pointer without restarting it, so the user explicitly chooses to retry next or run current. - -**Block `cordis_run` until user approval and the final Client outcome.** Approval or page interaction may only occur after the current model turn ends. Blocking would deadlock and occupy the Tool indefinitely when no page exists. The Tool returns immediately, while stores, Inspect, and steering report the final outcome. - -**Broadcast source from the Host and wait for Client acknowledgements with a timeout.** Broadcasting sends source to every page before authorization. A timeout cannot distinguish no page, a slow page, and no user action, and the Host would need compensating rollback. The protocol broadcasts only metadata, and an authorized page fetches source for the exact Run. - -**Automatically restore every Host-active Package when a page starts.** This requires connection identity, a startup baseline, and cross-page consistency. The design accepts page-local Client state and lets the user load again from the panel. - -**Connect Package halves through public Remote Services or `ctx.remote`.** This exposes dynamic Packages through the product RPC interface. Package-private `harness.handle`/`host.call` is sufficient for Client-to-Host JSON calls and rejects stale requests by `pluginRunId`. - -**Expose every Service method automatically as an Inspect query.** This turns capability discovery into a business-call proxy that bypasses plugin approval and lifecycle. Providers expose only curated read-only queries; the Service Catalog only describes business method signatures. - -**Put the complete API in the System Prompt or Skill.** Static text drifts and consumes context. The System Prompt retains stable rules, the Skill provides requirement navigation, and Provider/Catalog results return exact signatures and runtime directories. - -**Require Slot owners to register props schemas at runtime.** Slot props already exist in TypeScript types and JSDoc, so duplicate registration creates a second authority. The Slot AST Catalog extracts the static protocol and only merges the live tree at query time. - -**Write runtime state to the Session Log and restore it during replay.** Dynamic code and Fibers are process-local objects. Restoration would require re-executing historical code and reinterpreting approval. The Session retains only model-visible records; the Registry and page Runs are not restored. - -**Make historical Run cards scan later Session Log entries.** This couples Tool views to the complete log order and later message structure. The page card index/store already tells cards by Package when a later Run replaces them or their Plugin is deleted. - -## Acceptance criteria - -- A new Plugin can be created only from a 3-to-6-character lowercase English prefix; final Plugin, Package, and Run IDs are allocated by the Host and use branded types. -- `cordis_define` validates only parameters and plain JavaScript syntax and returns an immutable Package; one Plugin can append versions while old source remains inspectable. -- `cordis_run` strictly validates run/update; Host-only activation completes synchronously, while a Client-bearing activation returns `awaiting-approval` or `starting` without waiting for the final browser outcome. -- A single check authorizes only the current Package, and a double check authorizes future versions of the same Plugin. Authorization survives technical failure, while rejection executes neither half. -- The Host activates first and the Client then fetches source for the exact Run. A Client-bearing Package does not commit current before Client success, and current/next permit retry and rollback after failure. -- One Plugin has at most one physical Run at a time. Stop tears down both halves while retaining definitions and pointers; undefine deletes every Package, authorization, and state. -- The current page distinguishes “Ready,” “Client ready to activate,” and “Running,” and a pending-approval row shows only approval actions. -- `tool.view.cordis` self binds Plugin + Package. The newest Run card for a Package exclusively owns its business UI, while old cards and deleted Plugins have explicit fallback states. -- Host and Client Guards reject imports, JSX, undeclared Services, and unavailable globals. Services, timers, Slots, styles, Tools, handlers, and theme overrides are torn down with the Run. -- Package-private RPC permits only lossless JSON from Client to Host and rejects a stale `pluginRunId`. -- Inspect list returns Host and Client manifests together. Query calls only explicit read-only methods, and a Client query waits for the first schema-valid successful result or cancellation. -- Service/Event Catalogs are generated per Host/Client and apply allowlists. `@deprecated` APIs, Runner-owned Services, and `cordis/*` control Events are hidden from the model; Slot query merges static props with the live subtree. -- `cordis_inspect_self` returns layered Plugin lists, Package summaries, and exact source/diagnostics. `@pluginId` does not inject source and keeps updates in the same Plugin. -- Asynchronous technical failures, Host handlers, Client Guards, and React rendering errors preserve message/stack and steer the owning Agent; user panel actions only inject context into the next step. -- The System Prompt, Skill, Tool descriptions, and Provider/Catalog layers follow this Note. The Prompt remains sufficient to generate a minimally correct plugin if the Skill is unavailable. -- Relevant workspaces pass `pnpm run build`; implementation adds Host/Client lifecycle, versioning, approval, Inspect, Guard, Tool-card, and real-application snapshot coverage. - -## Risks - -- **A process restart loses all dynamic objects.** Historical Tool cards remain, but the Registry is not restored; the user must define again. -- **Multi-page state is not strongly consistent.** The first valid Client success may commit current while Client loading and rendering state still differs across pages. This version does not introduce connection identity, quorum, or page aggregation. -- **Client Inspect may remain pending indefinitely.** The Host stores the latest manifest, but without a page successfully executing the Provider it cannot present stale data as a live result. If every page fails, the request waits until cancellation. -- **Cross-version authorization expands trust.** A double check permits future Packages of the same Plugin without further approval. The UI must clearly distinguish per-Package and cross-version authorization. -- **A failed update can leave current pointing to an old version that is not running.** Current identifies the last successful version, not the physical Run. UI, Inspect, and prompts must show active, current, and next together. -- **Restricted contexts are not security sandboxes.** Host Services, files, commands, network access, and Client UI are real capabilities. Allowlists and approval reduce misuse but do not isolate malicious code. -- **Catalogs, Guards, and source can drift.** Generators, allowlists, and owner JSDoc must be maintained together. Guard hiding rules must not create a second signature. -- **Builtins require manual declarations.** React, harness, host, styles, and Context methods have no unified scannable source, so injection implementations and Provider definitions must share one maintenance location. -- **Provider output schemas currently permit broad JSON.** The first version prioritizes Provider ownership, input validation, and Host/Client routing; output schemas can become narrower later. -- **Host and Client Guards are parallel implementations.** Their available environments and Cordis type interfaces differ, so they remain separate. A shared specification should be extracted only if it removes code without obscuring security policy. diff --git a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.zh.md b/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.zh.md deleted file mode 100644 index 269f0f279f..0000000000 --- a/.agents/notes/rejected/architecture/2026-08-08-cordis-web-dynamic-packages.zh.md +++ /dev/null @@ -1,270 +0,0 @@ -# Agent Note: Cordis Host/Client 动态插件运行体系 - -Status: rejected — closed as a proposal: the shipped packages/extensions runtime and its READMEs own the design - -[English](2026-08-08-cordis-web-dynamic-packages.md) | 中文 - -## Problem - -模型需要在不修改仓库源码、不重新构建应用、不刷新浏览器的前提下,临时扩展当前 DSH 进程。扩展既可能运行在 Host 的 Node.js 进程,也可能运行在 Client 浏览器页面,还可能由 Host 取数、Client 展示,共同组成一个插件。 - -这项能力不能只是“执行一段代码”。模型需要在写代码前发现两端允许使用的 Service、Event、Builtin、Slot 和主题 token;用户需要先预览代码,再决定是否允许 Client 代码进入页面;同一个插件需要追加不可变版本、失败后重试或回退;运行后的异步错误需要回到模型,而不是只留在服务端日志或浏览器控制台。 - -如果把定义、审批、运行、版本切换、能力发现和 UI 状态塞进一个动作,会产生无法稳定解释的状态:定义成功是否等于运行成功,升级失败后哪个版本仍是成功版本,页面没有响应时 Tool 应等待多久,同一个 Package 多次运行时哪张历史卡片承载业务 UI,以及 Client 页面局部装载状态是否能代表 Host 的进程级状态。 - -## Proposal - -### 核心原则 - -- Host 保存 Plugin、Package、Run、审批和版本指针的唯一进程级权威状态。 -- Client 只保存当前页面的审批交互、装载结果、Slot 贡献、业务视图和页面局部错误。 -- Define 只创建不可变代码版本;Run 只激活一个已定义版本。 -- 版本切换只有在目标 Package 完成要求的 Host/Client 激活后才提交 `currentPackageId`。 -- 模型写代码前通过 Inspect Provider 查询能力;Inspect 结果只辅助编码,不作为插件运行时业务数据。 -- Host 与 Client 动态代码都使用受限的 plain JavaScript 上下文,并把可撤销副作用挂到 Cordis 生命周期。 -- Client 代码进入页面前需要用户授权;授权范围可以是单个 Package,也可以是同一 Plugin 的后续版本。 -- Tool 调用不等待当前轮结束后才可能发生的审批或浏览器操作;异步结局通过状态存储和模型 steering 反馈。 - -### 包职责与依赖方向 - -动态运行体系由 `packages/self-modification/` 下四个包组成: - -| 包 | npm 包名 | 职责 | -| --- | --- | --- | -| `tool-cordis` | `@deepseek-ai/dsh-tool-cordis` | 注册 System Prompt、七个模型 Tool、Host Inspect Provider、`@pluginId` 上下文注入和 Tool 展示元数据 | -| `cordis-host-runner` | `@deepseek-ai/dsh-cordis-host-runner` | 保存权威 Registry,分配 ID,执行 Host 代码,管理版本、审批、Run、私有 handler、Inspect 路由和模型反馈 | -| `cordis-client-runner` | `@deepseek-ai/dsh-cordis-client-runner` | 在浏览器同步 Inspect manifest,编排审批后的 Host→Client 激活,求值 Client 代码,管理 Guard、Loader/Fiber、timer、样式和 teardown | -| `ui-cordis` | `@deepseek-ai/dsh-client-ui-cordis` | 展示 Define/Run Tool 卡片、全局 Cordis 面板、审批控件、版本选择、运行状态和 Package 自定义业务视图 | - -`tool-cordis` 只依赖 Host Runner 的进程内服务,不导入 Client 实现。`ui-cordis` 只消费 Client Runner face 和 Client-safe wire 类型,不导入 Host 实现。Host 与 Client 的运行控制通过已有生成 Remote 面和转发事件连接,网关不拥有动态 Plugin 的领域逻辑。 - -### 领域对象 - -#### Plugin - -Plugin 是可持续修改的动态插件实例,由品牌类型 `CordisDynamicPluginId` 标识,例如 `clock-1`。新建 Plugin 时,模型只提交 3 至 6 位小写英文语义前缀;Host 添加进程内唯一数字后缀。完整 `pluginId` 不能由模型指定。 - -Plugin 属于定义它的 Session。模型 Tool 只能读取和操作当前 Session 的 Plugin;全局 Client 面板可以列出所有 Session 的 Plugin,但每个动作仍使用该行携带的 owner Session 执行。 - -#### Package - -Package 是 Plugin 下的不可变代码版本,由 `CordisDynamicPackageId` 标识,例如 `pkg-2`。它包含名称、用途、可选 Host 代码和可选 Client 代码,且至少包含一侧。每次 `cordis_define` 都创建新 Package;已有 Package 不允许原地修改。 - -同一个 Plugin 可以拥有多个 Package,但同一时刻最多只有一个物理 Run。Package 是否含 Host 或 Client 半只决定激活步骤,不改变版本身份。 - -#### Plugin Run - -Plugin Run 是一次具体激活尝试,由 `CordisDynamicPluginRunId` 标识,例如 `run-3`。每次新的激活尝试都会分配新 ID,包括审批后失败、重试同一 Package 和版本更新。`pluginRunId` 把审批、Host 激活、Client 装载、私有 RPC、Tool 卡片和错误关联到同一次尝试。 - -Host 分开保存当前物理 Run 与 `latestRun`。物理 Run 表示此刻仍可调用和撤销的激活;`latestRun` 表示最近一次尝试的审批、阶段、两侧状态和诊断。一次失败可以没有存活的物理 Run,但仍留下可查询的 attempt。 - -#### 版本指针 - -- `currentPackageId` 是最近一次完成要求的激活流程的 Package。停止插件、开始更新或更新失败都不清除它。 -- `nextPackageId` 是正在等待审批、正在激活、等待 Client、或最近失败的目标 Package。目标成功提交为 current 后清除。 - -Host-only Package 在 Host 成功建立 Fiber 后提交 current。包含 Client 的 Package 在 Host 激活成功且至少一个 Client 成功建立对应装载后提交 current。因硬依赖缺失而被 Cordis park 为 waiting 的 Fiber仍是成功建立的生命周期对象,不等同于解析或 `apply` 失败。 - -更新目标失败时不自动重启旧物理 Run。旧 `currentPackageId` 继续表示最后成功版本,失败目标保留为 `nextPackageId`。用户或模型可以重试 next,也可以以 `mode: "run"` 重新激活 current 完成回退。 - -### Host 权威状态与持久性 - -`DynamicCordisRunnerService` 及其内部 Registry 是当前 DSH 进程内的唯一权威,保存: - -- Plugin 的 Session 归属和不可变 Package 集合; -- `currentPackageId`、`nextPackageId`、物理 Run 和 `latestRun`; -- 单 Package 授权与 Plugin 跨版本授权; -- 待处理的 Client 激活请求; -- Host Fiber、Package 私有 handler、等待中的 Service 和最近诊断; -- Host 与 Client Inspect Registry 的目录和查询路由。 - -这些对象不写入配置或磁盘,也不在进程重启后恢复。Session Log 可以保留 Tool 调用、结果和卡片所需元数据,但不会重放动态代码来恢复 Registry。进程重启后历史卡片仍可作为对话记录存在,原 `pluginId` 和 `packageId` 不再可运行。 - -运行态不作为可恢复状态写入 Session projection。页面刷新或新页面打开不会自动恢复 Client 半;自动恢复会重新引入连接身份、启动期 baseline 和跨页面一致性协议,不属于当前设计。 - -### Define、Run 与版本切换 - -`cordis_define` 有两种模式:新建 Plugin 时提交 `idPrefix`;修改现有 Plugin 时提交精确 `pluginId`。代码统一为 `code: { host?, client? }`。Define 只校验参数和 plain JavaScript 语法,记录不可变源码并返回最终 ID。它不执行 `apply`、不产生审批、不改变版本指针,也不隐式运行。 - -不提供独立 `cordis_update`。`cordis_run` 通过 `mode` 表达激活意图: - -| 版本关系 | `mode` | -| --- | --- | -| 尚无 `currentPackageId` | `run` | -| 目标等于 current,包括重启、重试或回退 | `run` | -| 目标与已有 current 不同 | `update` | -| 更新失败后重试 `nextPackageId` | `update` | - -Run 先验证 Plugin/Package 归属、版本关系和是否已有转换在进行,再创建 `pluginRunId`、写入 `latestRun` 和 `nextPackageId`。 - -Host-only Package 在 Tool 调用内完成 Host 激活,并同步返回 `running` 或失败。包含 Client 的 Package不在 Tool 调用内等待浏览器终局:未授权时登记审批并返回 `awaiting-approval`;已授权时登记自动 Client 激活并返回 `starting`。这两种返回都表示请求已建立,不表示完整激活成功。 - -目标真正开始激活时,Host 先停止旧物理 Run,再执行目标 Host 半。Host 成功后才允许 Client 获取精确 `pluginRunId` 对应的源码并装载。Client 成功后 Host 提交版本指针;任何阶段失败都记录到该 attempt,不把旧版本重新启动伪装成目标成功。 - -`cordis_stop` 撤销当前 Host/Client Run 及待审批请求,但保留 Plugin、Package、授权和版本指针。`cordis_undefine` 先停止,再删除 Plugin、Package、授权和版本指针;删除后历史卡片只显示“插件已移除”。 - -### Client 审批与授权 - -包含 Client 代码的 Package 在第一次激活前需要用户授权,因为它将在用户页面中运行模型生成的代码。审批面板提供三个动作: - -- 单勾允许当前 Package;同一 Package 后续重跑不再审批,新 Package 仍需审批。 -- 双勾允许当前 Plugin 的后续版本;新 Package、更新、重试和回退不再逐版本审批。 -- 拒绝结束当前请求,不执行 Host 或 Client 代码;模型不得在用户没有新要求时立即重复申请。 - -授权在用户允许时写入 Host Registry,即使随后发生技术失败也保留。面板直接运行 Package 时,用户点击本身授权该 Package。 - -待审批行只显示单次允许、跨版本允许和拒绝,不同时提供运行、停止或删除。发现新审批时面板自动展开;自动展开失败或被收起时,固定入口和行状态仍显示待审批数量与状态。 - -### Client 激活编排 - -Host 通过 `cordis/request-run` 发送 Client 激活请求。请求只包含请求身份、Session、Plugin、Package、mode、名称、用途和是否需要审批,不广播源码。 - -获得授权的页面按固定顺序执行: - -1. 调用 `runHostHalf`,启动目标 Host 半或绑定同一次 attempt 已启动的 Host Run。 -2. Host 成功后,以 `pluginId + pluginRunId` 调用 `getClientCode`,只取得当前精确 Run 的 Client 源码。 -3. Client Runner 在页面求值插件,建立 Loader entry/Fiber,安装 Guard、样式、Slot 和页面局部状态。 -4. 页面调用 `resolveRequestRun` 或 `settleUserRun` 回报成功、waiting 或失败。 -5. Host 接受仍有效的精确 Run 回报,提交 current 或保存诊断,并广播请求结束,其他页面清理活动。 - -Host 激活先于 Client,避免 Client 在所需 Host handler 尚未存在时启动。只有本次请求实际创建的 Host Run 才能因本页 Client 失败而撤销;只是绑定既有 Run 的页面没有其所有权。 - -Client Orchestrator 按 `pluginId` 保存待审批和正在编排的活动,同一个 Plugin 不并发执行两次页面激活。Host inventory 可重建遗漏的待审批项和无需审批的自动激活请求。 - -Client 装载状态是页面局部事实。Host active 不代表当前页面已装载 Client 半。UI 使用三种主要状态:无物理 Run为灰色“待激活”,Host 已运行但当前页面 Client 未成功装载为黄色“Client 待激活”,当前页面两侧可用为绿色“运行中”。审批中和失败作为额外状态显示。 - -当前版本不建立 per-connection 身份或多页面法定人数。第一个仍有效的 Client 成功回报可以提交进程级 current;其他页面是否装载由各自页面 store 表示。 - -### Package 私有 Client→Host 通信 - -动态 Package 通过私有 JSON 通道从 Client 调用 Host:Host 使用 `harness.handle(method, handler)` 注册当前 Run 的方法,Client 使用 `host.call(method, args)` 调用。每次调用关联 `pluginId + pluginRunId`,Host 拒绝已停止或过期 Run。参数和返回值必须是无损 JSON,不允许函数、React 元素、Context、Service 实例或类对象。 - -该通道只服务同一 Package 的 Client→Host 调用,不使用公开 Remote Service 或动态代码中的 `ctx.remote`。公开 Remote 面只承载 Runner 自己的控制协议,不向动态 Package 暴露。 - -### 动态代码、Guard 与生命周期 - -Host 和 Client 都只执行 plain JavaScript 函数体,不经过 TypeScript、JSX 或 bundler 转译。Host 运行在 `node:vm`,Client 在受限闭包中求值。两端上下文用于减少误用并提供教学错误,不是恶意代码安全边界。 - -模型默认通过 `ctx.get('serviceName')` 读取可选 Service 并判断 `undefined`。只有 Service 是硬依赖、缺失时 Package 必须 waiting 并在 Service 出现后重新激活时,才在插件对象声明 `inject`。直接访问 `ctx.serviceName` 只在同一插件声明对应 inject 时允许。 - -Host 与 Client 的 `timer` 都是同名 Cordis Service,使用一致接口,不是全局 Builtin。需要 timer 的插件必须声明 `inject: ['timer']`;React effect 中创建的 timer 把 disposer 作为 cleanup 返回。 - -所有注册和可撤销副作用由当前 Fiber 拥有。Event listener、Service、Tool、handler、timer、Slot、样式和主题覆盖通过 `ctx.effect()`、`ctx.on()` 或返回 disposer 的官方 API 注册。停止、更新、失败回滚或 undefine 时撤销两端贡献。Theme override 必须按 source 分层并返回 disposer,使卸载后恢复此前主题值。 - -宿主、DSH、Cordis 及其 Service、Event payload、Slot props、Session/Conversation Snapshot、Tool 状态和其他运行时对象是内部 live data。动态代码不得对这些对象或其子对象执行 `JSON.stringify`、`structuredClone`、递归枚举、全量复制或整体展示;只能读取当前任务所需叶子字段,构造不含宿主引用的最小自有数据。 - -### Inspect Provider 与 Catalog - -能力发现分为三个 Tool:`cordis_inspect_list` 列 Host/Client Provider manifest;`cordis_inspect_query` 执行指定平台的显式只读查询;`cordis_inspect_self` 查询当前 Session 的 Plugin、Package、源码、版本指针和运行诊断。 - -Host 和 Client 各自拥有 `CordisInspectRegistry`。Provider 注册平台内唯一 ID、说明、method、输入 schema 和输出 schema。Provider method 是显式白名单查询,不是任意 Service 方法透传;Registry 不维护分层 target,也不自动把业务 Service 方法变成可执行 Inspect method。 - -首批 Provider 为: - -| Platform | Provider.method | 数据来源 | -| --- | --- | --- | -| Host / Client | `Service.listService` | 各平台 Service 静态 Catalog | -| Host / Client | `Event.listEvents` | 各平台 Event 静态 Catalog | -| Host / Client | `Builtin.listBuiltins` | evaluator/Guard 附近的手工定义 | -| Host | `Tool.listTools` | 当前 Agent 真正可见的 Tool Registry | -| Client | `Slots.listSubTree` | Slot 静态 Catalog与页面 live subtree/occupants | -| Client | `Theme.listTokens` | ThemeService 的只读 inspect export | - -Client Registry 变化后向 Host 同步完整 manifest,不按 Session 保存重复目录。Host query 本地执行;Client query 由 Host 广播 request ID,页面调用本地 Provider 后回送。Host 只接受第一个通过输出 schema 校验的成功结果;失败页面不抢占请求。没有页面成功回答时 Tool 保持 pending,直到后续成功或 Tool call 取消。 - -Inspect 数据只用于写代码前确认能力、签名、类型和挂载协议。插件运行时需要业务数据时必须调用实际 Service 或监听实际 Event,不能缓存、展示或依赖 Inspect/Catalog 返回值。 - -`CordisCatalogProjector` 使用 TypeRT 分别生成 Host/Client Service 与 Event Catalog;Slot AST 生成器扫描 `SlotMap`、注册选项、standard props、owner props 和引用类型;Slots Provider 查询时合并静态 Catalog 与 live tree。Theme token 由 ThemeService 导出,Builtin 在 evaluator/Guard 附近手工维护,Tool schema 来自 Registry。 - -Catalog 扫描真实源码签名,再应用 model-visible 白名单。白名单可以隐藏 Service、成员、`@deprecated` API、Runner 自身服务和 `cordis/*` 控制 Event,但不能改写剩余 API 的方法名、参数和返回类型。Guard 可以拒绝参数、固定来源或屏蔽成员,但必须尊重源码签名。 - -模型可见 owner JSDoc 只要求完整 description、每个参数的 `@param`、非 void 返回的 `@returns`、Event 的 `@mode`,以及 Slot/props 字段说明。调用推荐、反例和跨能力选择放入 Skill,不在 Catalog 增加重复 example 字段。 - -### 模型指导分层 - -模型指导分为四层: - -- System Prompt 保存稳定运行模型、两端限制、生命周期、审批、版本指针、最低代码规范和七个 Tool 的使用地图。Skill 不可用时它仍须支持最低限度正确实现。 -- `cordis-plugin-development` Skill 保存需求导航、能力组合、推荐和反例,不复制完整 schema。 -- 每个 Tool description 只说明该动作的前置条件、参数语义、同步/异步结果和下一步。 -- Provider/Catalog 返回当前精确名称、签名、参数、Slot props、token 和运行时查询结果。 - -System Prompt 要求先加载 Skill,再 list/query,之后 define/run。Skill 中 React 示例必须注册到 Slot,不能从 `apply()` 直接返回 React Element;示例使用 `React.createElement`、正确 `ctx.get()`/`inject`、可逆 effect 和最小 JSON RPC。 - -### `@pluginId` 与 Tool UI - -输入系统为当前 Session 注册 `@pluginId` mention。选择后只注入 Plugin 身份、默认基准 Package、版本指针、活动 Run 和最近状态,不注入源码。默认基准依次选择 next、current、最近定义的 Package。模型必须先用 `cordis_inspect_self` 读取源码,再以 existing 模式追加 Package;引用失效时不能静默创建替代 Plugin。 - -`cordis_define` 卡片以 Host/Client 两个子页签展示代码。`cordis_run` 卡片由 `pluginRunId` 关联精确 attempt,并读取 Client store 显示待审批、Client 待激活、运行中、失败、已被后续 Run 替代或 Plugin 已移除。 - -Package 可以向 `tool.view.cordis` 注册 `key: "self"`。运行时把 self 绑定为 `pluginId + packageId`;业务 Slot key 不含 `pluginRunId`,但 owner props 仍提供精确 Run 身份。同一 Package 最新 Run 卡片承载业务 UI,更早卡片显示已有更新运行。卡片通过 store 响应变化,不扫描后续 Session Log,也不互相通知。 - -全局 Cordis 面板使用一个固定入口,按当前会话和其他会话分组。面板标题和收起操作固定,只有列表滚动。普通行可选择 Package并运行、停止或删除;失败更新可重试 next 或选择 current 回退;待审批行只提供两个允许动作和拒绝。 - -### 错误与模型反馈 - -跨 Host/Client 的技术错误保留原始 `message`,并在错误对象提供时保留 `stack`。结构化诊断包含 `pluginId`、`packageId`、`pluginRunId` 和阶段:approval、host-load、host-apply、client-load、client-apply 或 client-render。 - -Host/Client Guard、Host 求值与 handler、Client 求值与 apply、Slot `onEntryError` 和 React ErrorBoundary 都把错误回到 owning Agent。Client 控制台同时以 `console.error` 打印原始 error 对象。渲染错误属于精确 Run,不污染不可变 Package。 - -模型发起的异步 Run 在成功、拒绝或技术失败后使用 `agent.steer` 唤醒 owning Agent。技术失败要求读取诊断、在同一 Plugin 修正并自主重试;用户拒绝则禁止自动重复申请。用户在面板手动运行、停止或移除通过 context injection 告知下一 step,但不主动唤醒模型。 - -## Alternatives considered - -**Define 与 Run 合并。** 这会失去“已定义但未运行”的可预览状态,把语法错误、审批、运行错误和重试混成一个动作,因此拆为不可变 Define 和独立 Run。 - -**Package ID 同时作为 Plugin ID。** 单层 ID 无法表达稳定实例下追加不可变版本,更新只能 stop、undefine、重新 define,历史卡片和 `@` 引用也无法保持同一对象,因此采用 Plugin、Package、Run 三层身份。 - -**提供独立 `cordis_update`。** Update 的装载、审批、UI、诊断和 Run 相同,独立 Tool 只复制协议,因此合并到 `cordis_run mode:"update"`。 - -**更新失败后自动恢复旧物理 Run。** 自动恢复会把“目标失败”和“旧版本重新成功”混成一个结果。当前设计保留旧 current 指针但不自动重启,让用户明确选择重试 next 或 run current。 - -**让 `cordis_run` 阻塞到用户审批和 Client 终局。** 审批或页面操作可能只能在当前模型轮结束后发生,阻塞会形成死锁,并在无页面时无限占用 Tool。当前设计立即返回,通过 store、Inspect 和 steering 报告终局。 - -**Host 广播源码并用超时等待 Client ack。** 广播会在授权前把代码发给所有页面;超时无法区分没有页面、页面慢和用户未操作;Host 还要维护补偿式回滚。当前协议只广播元数据,由获准页面按精确 Run 拉取源码。 - -**页面启动时自动恢复所有 Host active Package。** 这要求连接身份、启动期 baseline 和跨页面一致性。当前设计接受页面局部 Client 状态,用户可在面板重新装载。 - -**通过公开 Remote Service 或 `ctx.remote` 连接 Package 两半。** 这会把动态 Package 暴露到产品级 RPC 面。Package 私有 `harness.handle`/`host.call` 足以承载 Client→Host JSON 调用,并能按 `pluginRunId` 拒绝陈旧请求。 - -**把所有 Service 方法自动暴露成 Inspect query。** 这会把能力发现变成业务调用代理,绕过插件审批和生命周期。Provider 只暴露策展的只读查询,Service Catalog 只描述业务方法签名。 - -**把完整 API 写进 System Prompt 或 Skill。** 固化文本会漂移并占用上下文。System Prompt 保留稳定规则,Skill 负责需求导航,精确签名和运行时目录由 Provider/Catalog 返回。 - -**要求 Slot owner 在运行时注册 props schema。** Slot props 已存在于 TypeScript 类型和 JSDoc 中,重复注册会制造第二份权威。当前设计用 Slot AST Catalog 提取静态协议,只在查询时合并 live tree。 - -**把运行态写入 Session Log 并在 replay 恢复。** 动态代码和 Fiber 是进程局部对象,恢复要求重新执行历史代码并重新解释审批。Session 只保留模型可见记录,Registry 和页面 Run 不恢复。 - -**让历史 Run 卡片扫描后续 Session Log。** 这会让 Tool view 依赖全量日志顺序和后续消息结构。页面 card index/store 已能按 Package 告知旧卡片被替代或 Plugin 被删除。 - -## Acceptance criteria - -- 新 Plugin 只能由 3 至 6 位小写英文前缀创建,最终 Plugin、Package 和 Run ID 由 Host 分配并使用品牌类型。 -- `cordis_define` 只做参数和 plain JavaScript 语法检查,返回不可变 Package;同一 Plugin 可以追加版本,旧源码保持可 inspect。 -- `cordis_run` 严格校验 run/update;Host-only 同步完成,Client-bearing 返回 `awaiting-approval` 或 `starting`,不等待页面终局。 -- 单勾只授权当前 Package,双勾授权同一 Plugin 后续版本;授权在技术失败后仍保留,拒绝不执行两侧代码。 -- Host 先激活,Client 后取精确 Run 源码;Client 成功前不提交 Client-bearing Package 的 current,失败后 current/next 可用于重试和回退。 -- 一个 Plugin 同时最多一个物理 Run;stop 撤销两端贡献但保留定义和指针,undefine 删除全部 Package、授权和状态。 -- 当前页面能区分“待激活”“Client 待激活”和“运行中”,待审批时只显示审批动作。 -- `tool.view.cordis` 的 self 绑定 Plugin + Package;同 Package 最新 Run 卡片独占业务 UI,旧卡片和已删除 Plugin 有明确退化状态。 -- Host/Client Guard 拒绝 import、JSX、未声明 Service 和不可用全局;Service、timer、Slot、样式、Tool、handler 和主题覆盖随 Run teardown。 -- Package 私有 RPC 只允许 Client→Host 无损 JSON,并拒绝陈旧 `pluginRunId`。 -- Inspect list 一次返回 Host/Client manifest;query 只调用显式只读方法,Client 查询等待首个 schema-valid 成功结果或取消。 -- Service/Event Catalog 分 Host/Client 生成并应用白名单,`@deprecated` API、Runner 自身服务和 `cordis/*` 控制 Event 不向模型暴露;Slot query 合并静态 props 与 live subtree。 -- `cordis_inspect_self` 分层返回列表、Package 摘要和精确源码/诊断;`@pluginId` 不直接注入源码且更新留在同一 Plugin。 -- 异步技术失败、Host handler、Client Guard 和 React 渲染错误保留 message/stack 并 steering owning Agent;用户面板操作只注入下一 step context。 -- System Prompt、Skill、Tool description 和 Provider/Catalog 按本 Note 分层,Skill 不可用时 Prompt 仍足以生成最低限度正确的插件。 -- 相关工作区 `pnpm run build` 通过;实现阶段补齐 Host/Client lifecycle、版本、审批、Inspect、Guard、Tool 卡片与真实应用快照覆盖。 - -## Risks - -- **进程重启丢失全部动态对象。** 历史 Tool 卡片仍在,但 Registry 不恢复;用户必须重新 define。 -- **多页面状态不是强一致系统。** 第一个有效 Client 成功结果可以提交 current,各页面的 Client 装载和渲染状态仍可能不同;当前不引入连接身份、法定人数或页面聚合。 -- **Client Inspect 可能长期 pending。** Host 保存最近 manifest,但没有页面成功执行 Provider 时不能用旧数据伪装 live 结果;多个页面都失败时请求等待到取消。 -- **跨版本授权扩大信任范围。** 双勾允许同一 Plugin 后续 Package 无需再次审批;UI 必须清楚区分单次和跨版本授权。 -- **失败更新可能留下 current 指向旧版本但旧版本未运行。** current 表示最后成功版本,不表示当前物理 Run;UI、Inspect 和提示必须同时展示 active、current 和 next。 -- **受限上下文不是安全沙箱。** Host Service、文件、命令、网络和 Client UI 都是真实能力;白名单与审批降低误用,不隔离恶意代码。 -- **Catalog、Guard 和源码可能漂移。** 生成器、白名单和 owner JSDoc 必须共同维护;Guard 的隐藏策略不能产生另一套签名。 -- **Builtin 依赖手工声明。** React、harness、host、styles 和 Context 方法没有统一可扫描入口,注入实现与 Provider 定义必须放在同一维护位置。 -- **Provider 输出 schema 当前允许较宽的 JSON。** 首版优先完成 Provider 所有权、输入校验和 Host/Client 路由;更窄的输出 schema 后续再收紧。 -- **Host 与 Client Guard 存在平行实现。** 两侧开放环境和 Cordis 类型面不同,当前保留各自实现;公共规格只有在能减少代码且不隐藏安全策略时再提取。 diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml deleted file mode 100644 index 0e5a8660a4..0000000000 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.i18n.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Bilingual-pair consistency record for 2026-06-11-architectural-conformance.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/process/2026-06-11-architectural-conformance.md -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit: - en: 708fe0c94db3e25a - zh: 07437d3cb67a9240 -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/problem: - en: 2d5d19405f22920b - zh: 8ca48379706cbe35 -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/proposal: - en: 81d30d5657ece906 - zh: 1e7c61e5b9229ef6 -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/plan: - en: 56bb9b7b97ba6bf5 - zh: 2e3f0a70ce391029 -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/acceptance-criteria: - en: 2953797008c6c120 - zh: 42fd01df8a2a8f0b -/agent-note-architectural-conformance-dependency-rules-and-the-adapter-kit/risks: - en: 09eb0e6924573fe0 - zh: 60ca8648ec25afca diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md deleted file mode 100644 index 631b032235..0000000000 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.md +++ /dev/null @@ -1,36 +0,0 @@ -# Agent Note: Architectural conformance — dependency rules and the adapter kit - -Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; dsh-llm/invariant and the hygiene dependency gates own conformance - -English | [中文](2026-06-11-architectural-conformance.zh.md) - -## Problem - -Two architectural guarantees currently live only in prose: (1) nothing depends on the concrete loop package ([the microkernel promise](../../implemented/architecture/2026-06-11-microkernel-event-taxonomy.md)), and (2) every LlmAdapter speaks the chunk protocol correctly. Both should be mechanical ([the quality-gates principle](../../implemented/process/2026-06-11-quality-gates.md)). - -## Proposal - -**dependency-cruiser** with rules: - -- `packages/*` (except agent-loop's own tests and examples/) must not import `@deepseek-ai/dsh-agent-loop`. -- No cross-package deep imports (`@deepseek-ai/dsh-*/src/...` paths) — public entry points only. -- No import cycles anywhere in packages/. -- `vendor/*` must not import from `packages/*`. -- Layering: dsh-llm imports nothing from other dsh packages; dsh-session only dsh-llm; etc. (the dependency table in packages/README.md, enforced). - -**Adapter conformance kit** in dsh-llm (`@deepseek-ai/dsh-llm/conformance`): a reusable vitest suite parameterized by an adapter factory, asserting the chunk-protocol contract — index monotonicity per block, no deltas after `block-end` for an index, exactly one `finish`, usage at most once, every `tool-call-delta` carries the call id, abort honored promptly. Run it against the mocks now; the DeepSeek V4 adapter inherits it on day one. Optionally a dev-mode `strictAdapter()` wrapper enforcing the same at runtime behind a debug flag (pairs with [the dev-mode invariants](../../implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md)). - -## Plan - -dependency-cruiser config + CI step first (an hour of work, permanent guarantee); the conformance kit lands with its first consumer test against MockAdapter, and is a prerequisite for the V4 adapter phase. - -## Acceptance criteria - -- dependency-cruiser runs in CI with the rule families above; a violating import fails the build. -- The conformance kit runs against the mock adapter and both shipping adapters, and a new adapter package inherits the suite by invoking it with its factory. - -## Risks - -Dep-cruiser rule maintenance as packages are added — keep rules pattern-based (`dsh-*`) rather than enumerated. - - diff --git a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md b/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md deleted file mode 100644 index 74af5a377c..0000000000 --- a/.agents/notes/rejected/process/2026-06-11-architectural-conformance.zh.md +++ /dev/null @@ -1,36 +0,0 @@ -# Agent Note: 架构一致性——依赖规则与适配器套件 - -Status: rejected — premise gone: plugins depend on dsh-agent-loop by design; dsh-llm/invariant and the hygiene dependency gates own conformance - -[English](2026-06-11-architectural-conformance.md) | 中文 - -## 问题 - -目前有两项架构保证仅存在于行文中:(1)没有任何组件依赖具体的 agent loop(智能体循环)包([微内核承诺](../../implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md));(2)每个 LlmAdapter 都正确遵循分片协议。二者都应由机制强制执行([质量门禁原则](../../implemented/process/2026-06-11-quality-gates.zh.md))。 - -## 提案 - -**dependency-cruiser** 配合以下规则: - -- `packages/*`(除 agent-loop 自身的测试和 examples/ 外)禁止导入 `@deepseek-ai/dsh-agent-loop`。 -- 禁止跨包深层导入(`@deepseek-ai/dsh-*/src/...` 路径)——只允许使用公开入口点。 -- packages/ 内禁止出现循环依赖。 -- `vendor/*` 禁止从 `packages/*` 导入。 -- 分层:dsh-llm 不导入其他 dsh 包;dsh-session 仅导入 dsh-llm;以此类推(packages/README.md 中的依赖表,强制执行)。 - -**适配器一致性套件**位于 dsh-llm(`@deepseek-ai/dsh-llm/conformance`):一个以适配器工厂为参数的可复用 vitest 套件,用于断言分片协议约定,包括每个块内的索引单调递增、某个索引出现 `block-end` 后不再接收增量、恰好出现一个 `finish`、用量至多出现一次、每个 `tool-call-delta` 都携带调用 id,并且及时响应 abort。当前先对 mock 运行;DeepSeek V4 适配器从第一天起继承该套件。还可以选择提供开发模式下的 `strictAdapter()` 包装层,在调试标志开启时于运行时强制执行相同规则(与 [开发模式不变式](../../implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.zh.md) 配对)。 - -## 计划 - -先落地 dependency-cruiser 配置与 CI 步骤(约一小时工作量,换来永久保证);一致性套件随其首个消费方测试(针对 MockAdapter)一起落地,并作为 V4 适配器阶段的前置条件。 - -## 验收标准 - -- dependency-cruiser 在 CI 中运行上述规则族;违规导入导致构建失败。 -- 一致性套件对 mock 适配器和两个正式适配器运行,新适配器包通过调用该套件并传入自己的工厂即可继承测试。 - -## 风险 - -随着包的增加,dep-cruiser 规则需要维护——规则应基于模式(`dsh-*`)而非逐一枚举。 - - diff --git a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml b/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml deleted file mode 100644 index e0c297bfc5..0000000000 --- a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.i18n.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Bilingual-pair consistency record for 2026-08-04-artifact-first-npm-baseline-publication.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.md -/agent-note-artifact-first-npm-baseline-publication: - en: d0656ef5ec966473 - zh: 3d7930c502e0fa0a -/agent-note-artifact-first-npm-baseline-publication/problem: - en: 262109304a235ff0 - zh: 0399026e9afa9403 -/agent-note-artifact-first-npm-baseline-publication/proposal: - en: e3324fac080bfa55 - zh: 89e385b4d37df216 -/agent-note-artifact-first-npm-baseline-publication/current-implementation-boundary: - en: 49678acc335339b1 - zh: fdebfb169e49d34d -/agent-note-artifact-first-npm-baseline-publication/publication-payload-contract: - en: 6ad6a8829fe06538 - zh: 70e1507b7c85cb8a -/agent-note-artifact-first-npm-baseline-publication/artifact-plane-integration-test: - en: 129d1df710a8c8f7 - zh: 208002598644da1b -/agent-note-artifact-first-npm-baseline-publication/publication-and-recovery: - en: f45f84678f8184ae - zh: 5f87157df6e828b5 -/agent-note-artifact-first-npm-baseline-publication/github-actions-integration: - en: c376aca1e297c9d2 - zh: 2902f72e6d16e9ae -/agent-note-artifact-first-npm-baseline-publication/alternatives-considered: - en: d3b5794ac85b0a19 - zh: 222d054cbb7c8354 -/agent-note-artifact-first-npm-baseline-publication/acceptance-criteria: - en: fc56f357e3ef7cc5 - zh: ba108d552935f6bc -/agent-note-artifact-first-npm-baseline-publication/risks: - en: 7939117bb1674385 - zh: 9768d75303b04bc1 diff --git a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.md b/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.md deleted file mode 100644 index 1c31717652..0000000000 --- a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.md +++ /dev/null @@ -1,114 +0,0 @@ -# Agent Note: Artifact-first NPM baseline publication - -Status: rejected — superseded by 2026-08-10-npm-release-sequences: numbered versions and pack→protected publish shipped; timestamp versions were not adopted - -English | [中文](2026-08-04-artifact-first-npm-baseline-publication.zh.md) - -## Problem - -Runnable source in the monorepo does not prove that published packages are runnable. Workspace links, TypeScript paths, tsx source loading, and residual `lib/` files in the working tree can supply files or dependencies that are absent from a published tarball. Existing built-artifact tests still read `lib/` directly from the working tree, so they do not verify what `package.json#files` selects or the layout produced by package-manager installation. An execution that succeeds in development mode can therefore be published without a required bundle chunk, declaration, configuration file, or asset. - -Publishing many mutually dependent `@deepseek-ai` packages also creates a set-consistency problem. If a script publishes each package immediately after packing it, a later pack or validation failure leaves the first part of an unusable baseline in the registry. The npm registry has no cross-package transaction, so “publish once” cannot promise an atomic commit. It can promise that the complete publication set is packed and validated before any remote write, then published from that immutable set by one resumable orchestration command. - -The current baseline also requires a person to derive versions, authenticate, pack, publish, and retry from a local machine. A later GitHub Actions workflow must reuse the same release bundle and validation logic. It must not rebuild a different, untested set of tarballs after publication is approved. - -## Proposal - -The publication flow uses an immutable release bundle as its boundary. The pack phase builds every target package from one fixed Git commit, creates every tarball, validates tarball contents, and passes an installed-artifact integration test. The publish phase reads only those tarballs and their manifest and is forbidden from rebuilding or repacking. - -The target set contains only `@deepseek-ai/*` workspace packages discovered from `packages/*/*/package.json` and `apps/*/package.json`. The root project, `website/`, vendor, Python, and native workspaces are outside this NPM baseline. Discovery must reject duplicate names, mixed base versions, an unexpected publication privacy state, and unknown packages in the bundle instead of relying on another hand-maintained package-name list. - -The prerelease version consists of the package stable base version, a second-precision UTC timestamp captured when the command starts, and the target commit's 10-character short SHA: `--`. The dist-tag is derived as `dev-`. For example, base `0.0.1`, time `2026-08-04T00:32:00Z`, and short SHA `` produce version `0.0.1-20260804003200-` and tag `dev-0.0.1`. Retrying one release bundle must retain its version and manifest; repacking creates a version from the new command start time. - -The pack phase runs in this order: - -1. Resolve the ref to an immutable commit, capture the UTC timestamp, derive the version from that commit's root manifest, and display the commit, timestamp, version, tag, registry, and output path. Both `pack` and `release` wait for Enter at this point before expensive work; `--yes` skips this confirmation for automation. -2. Install the frozen lockfile in an isolated detached worktree and run source-manifest publication constraints before staging. Uncommitted files and old build output from the caller's working tree must not affect publication. -3. Stage every target manifest with the derived version, remove its publication-time `private` marker, and rewrite internal workspace dependencies in `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies` to the same exact version. -4. Build the target commit completely, then run publint and built-package invariants. -5. Pack every package in the target set without performing a registry write. -6. Inspect each tarball's package manifest, file inventory, internal dependency versions, name, and version, rejecting missing, duplicate, or extra tarballs. -7. Generate a release manifest and checksums containing the commit, version, tag, registry, and each package's tarball path, SHA-256, and npm integrity. -8. Install an isolated consumer from the local tarballs and run the installed-artifact probes available in the current implementation; expand those probes to the complete artifact-plane integration matrix defined below. -9. Print one directly executable publish command only after the complete set passes. The pack command itself always remains free of remote writes. - -The local `release` command composes pack and publish. It first uses the pack confirmation above to fix the expected timestamp and version, then waits for Enter again after a successful pack before publishing the same manifest; `release --yes` skips both confirmations. Separate `pack` and `publish --manifest` operations remain the primitives used by split CI jobs and recovery. - -## Current implementation boundary - -The checked-in pack command implements fixed-commit staging, exact internal dependency pins, static and tarball payload checks, the immutable manifest, and an isolated npm installation with every release tarball as a local top-level dependency. It runs the installed `dsh --version` and `dsh --dump-default-config` entries under plain Node, then starts the installed default TUI in a POSIX PTY, waits for its `main-session-` ready signal, and exits through `/exit` before printing the publish command. Publish supports integrity-based resumption, separates read-only registry verification from the authenticated identity check, and finishes with a complete remote integrity and dist-tag verification pass. - -Pull-request CI does not invoke the pack command; the installed-entry probes are local release checks rather than merge gates. Credential-free CI execution, package-owned probes for every other bin and public runtime entry, workflow-artifact transfer, and the protected publication job remain proposal scope. - -## Publication payload contract - -Published packages contain only build artifacts required by consumers. `package.json#files` must not include `src` or `lib/types/**/*.d.ts.map`; an independent tarball-content gate must also confirm that no `package/src/**` or `package/**/*.d.ts.map` entry exists, preventing manifest patterns or pack behavior from bypassing the static constraint. Runtime JavaScript, `.d.ts` declarations, configuration, assets, worker files, and dynamic bundle chunks must cover the actual entrypoint closure. - -Source manifests may retain `exports["./src/*"]` for this repository's source-plane resolution. That export does not place source in the publication payload and is not a consumer contract of the published package. Static gates must check the source plane and publication payload separately: deleting the source export must not hide broken workspace resolution, and publishing `src` must not repair missing build artifacts. - -Every tarball must be free of `workspace:` specifiers, and every internal dependency and peer dependency that points into the publication set must equal the exact derived version; `^`, `~`, and other semver ranges must not cross commit baselines. Except for `exports["./src/*"]`, which is explicitly source-plane-only, every consumer entry declared by the package manifest must point to a file present in the tarball. Dynamic imports, runtime-computed paths, and non-exported assets cannot be validated solely from the manifest and require installed execution. - -## Artifact-plane integration test - -The integration test runs after all tarballs exist and before any publish. It creates a fresh temporary project outside the monorepo, installs the declared dependency closure through local `.tgz` files from the release manifest, and executes from that installation. The test must use plain Node and package-manager-produced `node_modules`; tsx, tsconfig paths, workspace links, repository source paths, working-tree `lib/`, and the same version from the published registry are forbidden resolution inputs. The test also asserts that critical modules and bins resolve to real paths inside the temporary consumer. - -Installation uses the client behavior selected for this publication. Registry uploads must use the `npm` CLI because the private registry accepts only the npm client; pnpm may still orchestrate builds. Tarball tests must neither publish these packages to the real registry first nor repack after testing. - -The test covers at least these execution surfaces: - -- Installed `@deepseek-ai/dsh` runs `dsh --version` and `dsh --dump-default-config` successfully under plain Node, covering the static CLI entry and one dynamic mode entry. -- Installed default `dsh` completes one keyless TUI startup in a PTY and exits under test control after reaching a defined ready signal. This path must load the real TUI dynamic chunk, so a missing publication file such as `lib/tui-*.js` fails the gate. -- Every other published `bin` defines a package-owned smoke command that neither reaches a real service nor modifies user state. Different CLIs are not forced to share `--help`; the test runs the actual installed entry and checks its agreed exit or ready signal. -- Node-compatible public runtime entries load from the installation. Browser, worker, or host-protocol-only entries use matching isolated fixtures, but their inputs must still be the current tarballs exclusively. - -These tests prove executability; they do not replace unit tests, snapshots, real-API e2e, or publint. Test fixtures should reuse behavior assertions from existing built-bin and PTY scenarios while changing the entry to the tarball installation. A test that runs working-tree `lib/bin.js` directly does not satisfy this gate. - -## Publication and recovery - -The publish command validates the release manifest, every local checksum, the target registry, `npm ping`, and `npm whoami` before uploading tarballs in a deterministic order. It accepts only a manifest created by the pack phase, never workspace directories. The default registry is `https://registry.npm.harnessment.com/`; every publish passes the registry and derived tag explicitly so a user-level `.npmrc` cannot redirect the operation. - -npm provides no multi-package atomic transaction, so uploads still occur package by package. The orchestrator reduces the failure surface through idempotent recovery: upload when remote `@` does not exist; skip when it exists with the release manifest's integrity; fail immediately when it exists with different content. Dist-tag inspection reads tag assignments without resolving a default tag's target, so an unrelated dangling tag cannot block recovery. At completion it must confirm every package version's integrity and every dist-tag against the release version. The workflow reports success only when the complete set passes final verification. - -If pack, tarball inspection, or installed-artifact integration fails, the registry must receive zero writes. If publish fails after a partial upload, the operator reruns publish with the same release manifest and must not repack into another timestamped version instead of recovering. Only a code or build-input change that requires different tarballs reruns the complete pack and test flow. - -## GitHub Actions integration - -GitHub Actions separates a credential-free pack-and-test job from a protected publish job. The first checks out the exact commit, invokes the same pack entry used locally, runs tarball-consumer tests, and uploads the complete release bundle as a workflow artifact. The second depends on the first, downloads that workflow artifact, revalidates the manifest and checksums, and invokes the same publish entry. It cannot rebuild after checkout. - -Pull requests and ordinary pushes may run the credential-free pack-and-test signal so payload regressions surface before merge. Actual private-registry publication starts as `workflow_dispatch` with only a target ref as input; the pack job creates the UTC timestamp, while the base version, short SHA, tag, registry, and package inventory derive from repository state or version-controlled configuration. Stable-release triggering is outside this baseline proposal. - -The registry token is injected only into the publish job, which uses a protected GitHub Environment to govern human approval, allowed branches or tags, and concurrency. The pack-and-test job cannot read publication credentials. The workflow artifact may have a short retention period, but the publish job must use the bundle produced by the same workflow run instead of locating tarballs from an untrusted source by version. - -## Alternatives considered - -**Publish recursively from the workspace.** Rejected because it interleaves packing and registry writes, cannot prove the complete set before the first write, and allows workspace resolution and caller working-tree state to influence publication. - -**Test only built `lib/` in the working tree.** Rejected because that validates the build tree rather than the tarball selected by `package.json#files`. A dynamic chunk present in the working tree but omitted from the tarball is exactly the failure this proposal must catch. - -**Run only `dsh --help`.** Rejected because Commander can print help and exit before loading the TUI, Web, or headless dynamic entry. It does not prove the default production startup path is complete. - -**Publish `src` and declaration maps to reduce missing-file risk.** Rejected because the source plane is not a production-runtime fallback. Expanding the payload hides bundle-closure errors and turns local debugging outputs into accidental publication contracts. - -**Require truly atomic cross-package publication.** Rejected because the npm registry has no such transaction. An immutable release bundle, complete pre-publication validation, integrity comparison, and idempotent recovery provide an implementable boundary while retaining the explicit limitation that partial uploads can be briefly visible. - -**Rebuild in the publish job after approval.** Rejected because the tested and uploaded tarballs would lose content identity. The workflow artifact and checksums must carry the test inputs directly into publication. - -## Acceptance criteria - -- One pack entry discovers every target under `packages/*/*` and `apps/*` from a fixed commit, derives and displays a version from the UTC second and short commit before waiting for Enter, generates the complete release bundle before any registry write, and prints one copyable publish command; `release` waits again after packing, while `--yes` skips both confirmations. -- Static manifest and tarball-content gates both reject published `src` and `.d.ts.map` while source manifests retain `exports["./src/*"]`. -- The release bundle records the complete package set, commit, derived version, tag, registry, and per-tarball integrity; every internal dependency is pinned exactly to that version, and publish consumes only that bundle without rebuilding. -- An isolated integration test installs from local tarballs and starts the installed default `dsh` TUI under plain Node; deleting any required dynamic chunk makes the test fail deterministically. -- Every published bin and applicable public runtime entry has post-tarball-install execution coverage, with resolution paths proving that no monorepo fallback occurred. -- Publish safely resumes from the same manifest after partial success: matching integrity is skipped, conflicting integrity is rejected, and final verification requires every version and tag to agree. -- A credential-free GitHub Actions job creates and tests the bundle, a protected job uploads the identical bundle, and the publication token exists only in the latter. - -## Risks - -Full packing, installation, and startup add CI time and workflow-artifact volume. The implementation should cache external dependencies and the pnpm store, but it must not cache or reuse installed workspace output for target packages. Safe consumer probes can run in parallel to reduce latency. - -Installing every tarball as a temporary project's top-level dependency can hide an undeclared internal dependency. The test generator should install each tested application's declared recursive closure and retain existing dependency gates. For `@deepseek-ai/dsh`, whose dependency surface approaches the full set, package-manifest and static graph checks remain necessary to detect undeclared edges. - -Platform-specific optional dependencies, native addons, PTYs, and browser entries may require platform-owned probes. The first phase must cover primary `dsh` startup on the publication Linux runner and one local macOS path, then expand the matrix with the actual publication platforms. Skipping an unstable probe must not move a production path outside the gate. - -Recovery cannot remove npm's partial visibility. During a failed publication, the registry may briefly contain only some package versions from the bundle. Operators and automation must treat the final bundle verification, not one successful `npm publish`, as the baseline-availability signal. diff --git a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md b/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md deleted file mode 100644 index c2822ed0b5..0000000000 --- a/.agents/notes/rejected/process/2026-08-04-artifact-first-npm-baseline-publication.zh.md +++ /dev/null @@ -1,114 +0,0 @@ -# Agent Note: 以产物为先的 NPM 基线发布 - -Status: rejected — superseded by 2026-08-10-npm-release-sequences: numbered versions and pack→protected publish shipped; timestamp versions were not adopted - -[English](2026-08-04-artifact-first-npm-baseline-publication.md) | 中文 - -## 问题 - -monorepo 中可运行的源码并不能证明发布后的包可运行。workspace link、TypeScript paths、tsx 源码加载和工作树里残留的 `lib/` 都可能补上发布 tarball 中缺失的文件或依赖。即使现有构建产物测试使用普通 Node,它仍直接读取工作树中的 `lib/`,没有验证 `package.json#files` 最终选中了什么,也没有验证包管理器安装后的文件布局。一次开发模式正常的执行因此可能发布为缺少 bundle chunk、声明文件、配置或资源的包。 - -发布多个互相依赖的 `@deepseek-ai` 包还会产生集合一致性问题。如果脚本每 pack 一个包就立即 publish,那么后续 pack 或验证失败时,注册表中已经存在无法作为完整基线使用的前半组版本。npm 注册表没有跨包事务,因此这里的「一次性发布」不能承诺原子提交,只能承诺在任何远端写入前完整生成并验证发布集合,再由一个可恢复的编排命令发布这个不可变集合。 - -当前基线还需要人工在本机完成版本派生、认证、pack、发布和重试。后续 GitHub Actions 工作流必须复用同一套发布包与验证逻辑,不能在批准发布后重新构建另一组未经消费方测试的 tarball。 - -## 提案 - -发布流程以一个不可变的 release bundle(发布包集合)为边界。pack 阶段从一个确定的 Git commit 构建全部目标包、生成全部 tarball、检查 tarball 内容,并通过安装后集成测试;publish 阶段只读取这组 tarball 及其 manifest(元数据清单),禁止重建或重新 pack。 - -目标集合只包含 `packages/*/*/package.json` 与 `apps/*/package.json` 中命名为 `@deepseek-ai/*` 的 workspace 包。根项目、`website/`、vendor、Python 与 native workspace 不属于该 NPM 基线。发现机制必须拒绝重复包名、不同基础版本、意外的 `private` 发布状态以及集合中的未知包,而不是维护另一份手工包名列表。 - -预发布版本由包的稳定基础版本、命令启动时精确到秒的 UTC 时间戳和目标 commit 的 10 位短 SHA 组成:`--`。dist-tag 由基础版本派生为 `dev-`。例如,基础版本 `0.0.1`、时间 `2026-08-04T00:32:00Z` 和短 SHA `` 生成版本 `0.0.1-20260804003200-` 与 tag `dev-0.0.1`。同一 release bundle 的重试必须沿用原版本和 manifest;重新 pack 会按新的命令启动时间生成新版本。 - -pack 阶段按以下顺序执行: - -1. 将 ref 解析成不可变 commit,采集 UTC 时间戳,从该 commit 的根 manifest 派生版本,并显示 commit、时间戳、版本、tag、注册表和输出路径。`pack` 与 `release` 此时都会在昂贵操作开始前等待 Enter;自动化可用 `--yes` 跳过该确认。 -2. 在隔离的 detached worktree 中安装 frozen lockfile,并在暂存前运行源码 manifest 发布约束;调用方工作树中的未提交文件和旧构建输出不得参与发布。 -3. 将所有目标 manifest 暂存为派生版本,移除发布时的 `private` 标记,并把 `dependencies`、`devDependencies`、`optionalDependencies` 与 `peerDependencies` 中的内部 workspace 依赖全部改写为同一精确版本。 -4. 完整构建目标 commit,再运行 publint 和已构建包不变式。 -5. 为目标集合中的每个包执行 pack,但不执行任何注册表写入。 -6. 检查 tarball 内的 package manifest、文件清单、内部依赖版本、包名和版本,并拒绝缺失、重复或额外的 tarball。 -7. 生成包含 commit、版本、tag、注册表、每个包的 tarball 路径、SHA-256 与 npm integrity 的 release manifest 和校验和文件。 -8. 从本地 tarball 安装一个隔离消费方,运行当前实现已有的安装态产物探测,并将这些探测扩展为下文定义的完整产物平面集成测试矩阵。 -9. 仅当整个集合通过时输出一个可直接执行的 publish 命令;pack 命令本身始终保持无远端写入。 - -本地 `release` 命令组合 pack 与 publish。它先通过上述 pack 确认确定预期时间戳和版本,再在 pack 成功后等待第二次 Enter,随后发布同一 manifest;`release --yes` 跳过两次确认。独立的 `pack` 与 `publish --manifest` 仍是 CI 分 job 和断点恢复使用的基础操作。 - -## 当前实现边界 - -已提交的 pack 命令实现了固定 commit 暂存、内部依赖精确固化、静态与 tarball payload 检查、不可变 manifest,以及把每个发布 tarball 都作为本地顶层依赖的隔离 npm 安装。它在输出 publish 命令前,用普通 Node 运行安装后的 `dsh --version` 与 `dsh --dump-default-config` 入口,再在 POSIX PTY 中启动安装后的默认 TUI,等待其 `main-session-` 就绪信号,并通过 `/exit` 退出。Publish 支持按 integrity 恢复,将只读注册表验证与认证身份检查分离,并以完整的远端 integrity 和 dist-tag 验证结束。 - -PR(Pull Request) CI 不会调用 pack 命令;安装态入口探测属于本地发布检查,而不是合并门禁。免凭据 CI 执行、其他每个 bin 与公开运行时入口的包自有探测、workflow artifact 传递及受保护 publish job 仍属于提案范围。 - -## 发布 payload 约定 - -发布包只携带消费方需要的构建产物。`package.json#files` 禁止包含 `src` 和 `lib/types/**/*.d.ts.map`;tarball 内容门禁还要独立确认不存在任何 `package/src/**` 与 `package/**/*.d.ts.map`,避免 manifest pattern 或 pack 行为绕过静态约束。运行时 JS、声明文件 `.d.ts`、配置、资源、worker 文件和 bundle 动态 chunk 必须按实际入口闭包收齐。 - -源码 manifest 可以保留 `exports["./src/*"]`,供本仓库的源码平面解析使用;该 export 不代表源码会进入发布 payload,也不属于已发布包的消费方约定。静态门禁必须分别检查源码平面与发布 payload,不能通过删除 source export 来掩盖错误的 workspace 解析,也不能通过发布 `src` 来修补缺失的构建产物。 - -每个 tarball 必须不含 `workspace:` specifier,并且所有指向本次发布集合的内部依赖与对等依赖(peer dependency)都必须精确等于本次派生版本,禁止使用 `^`、`~` 或其他 semver 范围跨越 commit 基线。除了明确仅供源码平面使用的 `exports["./src/*"]`,package manifest 中声明的每个消费方入口都必须指向 tarball 内存在的文件;动态 import、运行时拼接路径和非 export 资源不能只靠 manifest 检查,必须由安装后执行覆盖。 - -## 产物平面集成测试 - -集成测试在全部 tarball 生成后、任何 publish 之前运行。它在 monorepo 外创建一个全新临时项目,通过本次 release manifest 中的本地 `.tgz` 文件安装声明依赖闭包,并从安装目录执行。测试必须使用普通 Node 与包管理器生成的 `node_modules`;禁止 tsx、tsconfig paths、workspace link、仓库源码路径、工作树 `lib/` 和已发布注册表中的同版本包参与解析。测试还要断言关键模块与 bin 的真实路径位于临时消费方内。 - -安装使用本次发布选择的客户端行为。注册表上传必须使用 `npm` CLI(命令行界面),以满足私有注册表只接受 npm 客户端的策略;构建编排仍可使用 pnpm。tarball 测试不得先把这些包发布到真实注册表,也不得在测试后重新 pack。 - -测试至少覆盖以下执行面: - -- `@deepseek-ai/dsh` 安装后的 `dsh --version` 与 `dsh --dump-default-config` 在普通 Node 下成功,分别覆盖静态 CLI 入口和一个动态模式入口。 -- 安装后的默认 `dsh` 在 PTY 中完成一次无密钥 TUI 启动,到达既定 ready 信号后由测试受控退出。这条路径必须加载真实 TUI 动态 chunk,因此缺少类似 `lib/tui-*.js` 的发布文件会使门禁失败。 -- 每个其他已发布 `bin` 都定义一个不会访问真实服务或修改用户状态的包级冒烟命令。不同 CLI 不强制共用 `--help`;测试必须运行其真实安装入口并检查约定的退出或 ready 信号。 -- Node 兼容的公开运行时入口从安装目录加载;浏览器、worker 或必须由宿主协议驱动的入口使用对应的隔离 fixture(测试前置数据),但输入仍只能是本次 tarball。 - -这些测试验证可执行性,不替代单元测试、快照、真实 API e2e 或 publint。测试 fixture 应复用现有 built-bin 和 PTY 场景的行为断言,但必须把入口改为 tarball 安装结果;直接运行工作树 `lib/bin.js` 的测试不能算作本门禁。 - -## 发布与恢复 - -publish 命令先验证 release manifest、所有本地校验和、目标注册表、`npm ping` 和 `npm whoami`,再按确定顺序上传 tarball。命令只接受 pack 阶段生成的 manifest,不接受 workspace 目录作为发布输入。默认注册表为 `https://registry.npm.harnessment.com/`,每次 publish 都显式传入注册表和派生 tag,避免用户级 `.npmrc` 改变目标。 - -npm 不提供多包原子事务,上传仍会逐包发生。编排器通过幂等恢复缩小失败面:若远端不存在 `@` 就上传;若已存在且 integrity 与 release manifest 相同就跳过;若已存在但内容不同就立即失败。dist-tag 检查只读取 tag 映射,不解析默认 tag 指向的版本,因此即使无关 tag 指向的版本已不存在,也不会阻断恢复。完成后必须逐包确认版本 integrity 和 dist-tag 都指向本次版本,只有整个集合通过最终验证,工作流才报告发布成功。 - -如果 pack、tarball 检查或安装后集成测试失败,注册表必须保持零写入。如果 publish 在部分上传后失败,操作者使用同一 release manifest 重跑 publish 命令来恢复,不得重新 pack 并生成另一个时间戳版本来代替恢复。修复代码或改变构建输入后需要不同 tarball 时,才重新执行完整 pack 与测试。 - -## GitHub Actions 集成 - -GitHub Actions 分为无凭据的 pack-and-test job 与受保护的 publish job。前者检出精确 commit,调用与本地相同的 pack 入口,运行 tarball 消费方测试,并上传完整 release bundle 作为工作流产物。后者依赖前者成功,从工作流产物下载 bundle、重新校验 manifest 和校验和,再调用同一个 publish 入口;它不能检出后重新构建。 - -PR 与普通 push 可以运行无凭据的 pack-and-test 信号,从而在合并前发现 payload 回归。实际私有注册表发布先通过 `workflow_dispatch` 提供,输入只包括目标 ref;UTC 时间戳由 pack job 生成,基础版本、短 SHA、tag、注册表和包清单都由仓库状态或受版本控制的配置派生。稳定发布触发方式不在本基线提案范围内。 - -注册表 token 只注入 publish job,并由受保护 GitHub Environment 控制人工批准、允许的分支或 tag 以及并发。pack-and-test job 不得读取发布凭据。工作流产物的保留期可以较短,但 publish job 必须使用同一 workflow run 生成的 bundle,不能按版本号从不受信任的位置寻找 tarball。 - -## 考虑过的替代方案 - -**从 workspace 直接递归 publish。** 不采用,因为命令会把 pack 与注册表写入交错,无法在第一次写入前证明整个集合完整,也容易让 workspace 解析与调用方工作树状态影响发布结果。 - -**只测试工作树中构建后的 `lib/`。** 不采用,因为这验证的是构建树,不是 `package.json#files` 选出的 tarball。工作树中存在而 tarball 中漏掉的动态 chunk 正是本提案必须捕获的失败。 - -**只运行 `dsh --help`。** 不采用,因为 Commander 可以在加载 TUI、Web 或 headless 动态入口之前输出帮助并退出。它无法证明默认生产启动路径完整。 - -**把 `src` 和声明映射一起发布以降低漏文件风险。** 不采用,因为源码平面不是生产运行时的后备路径;扩大 payload 会掩盖 bundle 闭包错误,并把本地调试产物变成无意的发布约定。 - -**要求真正的跨包原子发布。** 不采用,因为 npm 注册表没有相应事务。不可变 release bundle、发布前全量验证、integrity 比对与幂等恢复提供可实现的边界,同时明确保留部分上传短暂可见的限制。 - -**在批准后由发布 job 重新构建。** 不采用,因为测试通过的 tarball 与实际上传的 tarball 将不再具有内容身份。工作流产物与校验和必须把测试输入直接传给发布步骤。 - -## 验收标准 - -- 一个 pack 入口从确定 commit 发现 `packages/*/*` 和 `apps/*` 的全部目标包,以 UTC 秒级时间戳与短 commit 生成并显示版本,再等待 Enter;它在任何注册表写入前生成完整 release bundle,并输出一个可复制的 publish 命令;`release` 在 pack 后再次等待,`--yes` 跳过两次确认。 -- 静态 manifest 门禁和 tarball 内容门禁都拒绝发布 `src` 与 `.d.ts.map`,同时保留源码 manifest 中的 `exports["./src/*"]`。 -- release bundle 记录完整包集合、commit、派生版本、tag、注册表和逐 tarball integrity;所有内部依赖都精确固化到该版本,publish 只消费该 bundle,绝不重建。 -- 一个隔离集成测试从本地 tarball 安装消费方,并用普通 Node 启动安装后的默认 `dsh` TUI;删除任一所需动态 chunk 会使该测试稳定失败。 -- 所有已发布 bin 和适用的公开运行时入口都有 tarball 安装后的执行覆盖,且解析路径证明没有回退到 monorepo。 -- publish 可在部分成功后用同一 manifest 安全重跑;相同 integrity 被跳过,不同 integrity 被拒绝,最终验证要求所有版本与 tag 一致。 -- GitHub Actions 的无凭据 job 生成并测试 bundle,受保护 job 上传完全相同的 bundle,发布 token 只存在于后者。 - -## 风险 - -全量 pack、安装和启动会增加 CI 时间与工作流产物体积。实现应缓存外部依赖和 pnpm store,但不得缓存或复用目标包的已安装 workspace 输出;并行执行安全的消费方 probe 可以降低时延。 - -把所有 tarball 都安装为临时项目的顶层依赖可能掩盖未声明的内部依赖。测试生成器应按被测应用的声明式递归闭包安装,并结合现有依赖门禁;对依赖面接近全集的 `@deepseek-ai/dsh`,仍需依靠 package manifest 与静态图检查发现未声明边。 - -不同平台的 optional dependency、native addon、PTY 与浏览器入口可能需要平台专属 probe。第一阶段至少在发布所用 Linux runner 和一个本地 macOS 路径上覆盖主 `dsh` 启动,后续矩阵按实际发布平台扩展;不能用跳过不稳定 probe 的方式把生产路径移出门禁。 - -恢复机制不能消除 npm 的部分可见性。发布失败期间,注册表可能短暂含有本次版本的一部分包;操作者与自动化必须以最终 bundle 验证结果而非单个 `npm publish` 的成功作为基线可用信号。 diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml index 7e9de72fc1..66f5bee0e5 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml @@ -12,8 +12,8 @@ en: 8eecfc7bd6c83608 zh: 658fc8e8eac425b7 /agent-note-fold-the-persistence-interface-into-dsh-session/acceptance-criteria: - en: a141343494939ee4 - zh: 2b9a95f1aadd319f + en: c6529542fe47d400 + zh: 06aa68409bad3823 /agent-note-fold-the-persistence-interface-into-dsh-session/what-we-give-up: en: 595671143eb17f28 zh: a6f019fb64ba3a90 diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md index 58c6aa8f5b..65ff3e57b4 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md @@ -22,7 +22,7 @@ The implementing PR should update the [capability seams](../../implemented/archi - `dsh-session` exports the persistence service type, coordinator, and contract helpers. - JSONL and SQLite backend packages depend on `dsh-session` directly. - `agent-loop` resume uses the session-owned service key. -- [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md), [handle-based session persistence](../../implemented/architecture/2026-08-27-handle-based-session-persistence.md), and [package docs](../../../../packages/session/session-persistence/README.md) explain why backend implementations remain separate. +- [Session persistence](../../../../packages/session/session-persistence/README.md), [handle-based session persistence](../../implemented/architecture/2026-08-27-handle-based-session-persistence.md), and [package docs](../../../../packages/session/session-persistence/README.md) explain why backend implementations remain separate. ## What we give up diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md index 9fb53d16fc..877f502764 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md @@ -22,7 +22,7 @@ Status: rejected — 独立的持久化 Service Definition 包是持久化能力 - `dsh-session` 导出持久化服务类型、协调器和约定辅助工具。 - JSONL 和 SQLite 后端包直接依赖 `dsh-session`。 - `agent-loop` 的恢复功能使用会话包拥有的服务键。 -- [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.zh.md)、[基于句柄的会话持久化](../../implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与[包文档](../../../../packages/session/session-persistence/README.zh.md)说明后端实现为何仍保持独立。 +- [会话持久化](../../../../packages/session/session-persistence/README.zh.md)、[基于句柄的会话持久化](../../implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与[包文档](../../../../packages/session/session-persistence/README.zh.md)说明后端实现为何仍保持独立。 ## 放弃了什么 diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml index d1a8821362..0510e9d70e 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml @@ -18,5 +18,5 @@ en: 583aa8d003225c57 zh: 2d90befccf8be0ca /agent-note-truncate-interrupted-final-turns-on-load/related: - en: a2af11959bf10ac3 - zh: 402cf5f0c4f019d8 + en: 5c7d59ca77bb80f3 + zh: d05db535d6359fcf diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md index c4b0084de5..e82da371ef 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md @@ -31,6 +31,6 @@ A crash can lose real work from the final turn: assistant text, tool calls, and ## Related -This is a direct simplification of [session persistence](../../implemented/architecture/2026-06-14-session-persistence.md) and the historical [universal turn-enclosure rule](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md). It also removes much of the motivation for durable step boundary events, making [drop durable step boundary events](2026-06-20-drop-durable-step-boundaries.md) smaller. +This is a direct simplification of [session persistence](../../../../packages/session/session-persistence/README.md) and the historical [universal turn-enclosure rule](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md). It also removes much of the motivation for durable step boundary events, making [drop durable step boundary events](2026-06-20-drop-durable-step-boundaries.md) smaller. diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md index ad6c85fc62..6a7bbd6e5a 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md @@ -31,6 +31,6 @@ Status: rejected — 单个轮次可以包含大量真实工作,包括多个 ## 相关 -本提案是对[会话持久化](../../implemented/architecture/2026-06-14-session-persistence.zh.md)与历史上的[通用轮次封闭规则](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)的直接简化。它还移除了持久化步骤边界事件的大部分动机,使[移除持久化步骤边界事件](2026-06-20-drop-durable-step-boundaries.zh.md)的改动更小。 +本提案是对[会话持久化](../../../../packages/session/session-persistence/README.zh.md)与历史上的[通用轮次封闭规则](../../archived/architecture/2026-06-15-turn-enclosure-invariant.md)的直接简化。它还移除了持久化步骤边界事件的大部分动机,使[移除持久化步骤边界事件](2026-06-20-drop-durable-step-boundaries.zh.md)的改动更小。 diff --git a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml deleted file mode 100644 index 810e07aad4..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-04-prune-dead-core-spine-api.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.md -/agent-note-prune-dead-public-api-and-result-fields: - en: 787501dcdb05bf03 - zh: cf13583535e5a444 -/agent-note-prune-dead-public-api-and-result-fields/problem: - en: 277564591633536c - zh: ddfbc0231a04b51d -/agent-note-prune-dead-public-api-and-result-fields/problem/grouped-helper-export-inventory: - en: 84cb62303daf31bf - zh: 3478dd10d2a761fc -/agent-note-prune-dead-public-api-and-result-fields/proposal: - en: 414aca8de0a22c38 - zh: 9458a8db0cea11e7 -/agent-note-prune-dead-public-api-and-result-fields/alternatives-considered: - en: e8eecc41540c8d67 - zh: 60110bcdfaba5dad -/agent-note-prune-dead-public-api-and-result-fields/acceptance-criteria: - en: ef426fe8180fdfe8 - zh: 7e7f4cdb5d2ce655 -/agent-note-prune-dead-public-api-and-result-fields/risks: - en: c88948f52999d392 - zh: 3b1b98c54d082ae1 diff --git a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.md b/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.md deleted file mode 100644 index 35130b1af2..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.md +++ /dev/null @@ -1,62 +0,0 @@ -# Agent Note: Prune dead public API and result fields - -Status: rejected — stale 2026-07 inventory: rows were pruned piecemeal or gained callers; a fresh audit must supersede it - -English | [中文](2026-07-04-prune-dead-core-spine-api.zh.md) - -## Problem - -Several package-root exports, result fields, and convenience methods have no production consumer. They survive because tests import internals through public entry points or because a type anticipated a caller that never arrived. Each item is small in isolation, but together they enlarge the SDK contract, generated catalogs, documentation, and regression matrix without enabling a shipped path. - -The production corpus is `packages/*/*/src`, example sources/config, and runtime scripts. Tests, package READMEs, and Agent Note prose are evidence of publication but not fixed callers. `cordis_inspect` makes `packages/extensions/tool-cordis/src/api-catalog.ts` model-visible, and `cordis_mount` can invoke injected services through guarded real-service proxies, so catalogued service methods and returned shapes are a genuine dynamic product surface. The table therefore distinguishes absence of a fixed repository caller from unreachability: rows touching catalogued vocabulary intentionally contract what model-written mounts can discover and call, while package-root implementation helpers are not reached through that service façade. Exact-symbol searches produce the following inventory: - -| API element | Production evidence | Simplification | -| --- | --- | --- | -| `SurfaceManager.invalidate()` | Only its unit test calls it; seeding completes before the lazily-created manager exists and the session never replaces its log reference. | Delete it and its impossible wholesale-replacement contract. | -| `ToolExecutionResult.callId` | Every hook already receives the immutable `ToolExecution`; the loop and ACP correlate through the call/session event. No consumer reads the duplicate result field. | Remove the field, copy/mismatch guards, and tests that prove the duplicate cannot disagree. | -| `ReactLoopAgent` root export | Outside-package named imports are tests; production programs against `Agent` and creates/resumes through `ctx.agents`. | Return/interface-type `Agent` and make the concrete loop class package-internal; keep the deliberate synchronous config-only `AgentLoop.create()` path. | -| `workflow-worker-thread` protocol/runtime/session re-exports and named `WorkerThreadWorkflowEngine` | Every package-name consumer uses the default engine; the workflow Agent Note already defines the worker wire protocol as private. | Keep the default plugin class/config contract; drop the duplicate named class export and keep protocol modules source-private. | -| `ptc-runtime-worker` protocol/bootstrap re-exports | Outside-package production/e2e consumers use `NodePtcRuntime` and config, not `BootstrapPort`, `PatchableStream`, or worker message/boot types. | Keep the runtime class/config contract and make its wire/bootstrap vocabulary source-private. | -| ACP `agentOptions` root export | The helper has only same-file and ACP-test consumers; the sole outside-package production consumer mounts the plugin namespace. | Keep `name`, `inject`, `Config`, `AcpConfig`, and `apply`; make `agentOptions` source-private and test it through bridge behavior. | -| `providerWording` and `completedTurnPrefix` root exports | Each has one same-package production caller; only the balanced-prefix helper has a same-package white-box test. | Make them source-private and test provider behavior. | -| `depthOf`, `SubagentDepthError`, `waitForExit`, and `exitsWithin` root exports | Production subagent backends consume the in-process runner and subprocess construction/disposal helpers, not these enforcement/test internals. `SENSITIVE_ENV_PATTERN` is excluded because the SDK helper applies it to caller-supplied environments. | Keep depth and exit behavior but make the remaining helpers and error source-private; test through spawn and disposal. Keep the shared credential pattern public. | -| `LlmError.status` and replay status | Adapters/replay populate it, but production branches on stable error code/message and never reads raw status. | Remove the unread field and replay plumbing while preserving error classification. | -| `BlockAssembler.push()` return value | Both production callers ignore the returned completed block. | Return `void`; keep the deliberately public `blocks()`/`message()` contract. | -| `compactRegion`'s separate `session` argument | The fixed caller passes the same object already present as `agent.session`; the model-visible mount API can also call the method, but accepting two identities permits a mounted plugin to provide an incoherent pair. | Keep the manual-region API while deliberately narrowing it to `agent.session` as the one source of truth. | -| `CompactionResult.startSeq`, `summarySeq`, `endSeq`, and `summary` | The production consumer reads only shadowed range/seq/token accounting; the durable log owns summary and event identity. | Remove the four result echoes while keeping both shared transcript renderers. | -| `BasicCompactionEngine` estimation/summarization visibility | No outside production caller invokes the five methods; the implemented Agent Note names only `estimateContentTokens()` and `summarize()` as subclass hooks. | Make those two `protected` and the three orchestration-only estimators private. | -| `CodeLogEntry.source`/`level` and `RunCodeMeta.dispatches` | Every production consumer maps logs to text; no presenter/model path reads the other fields or the persisted dispatch count. | Make ptc-runtime logs strings (or text-only entries) and remove result-meta dispatch plumbing; keep the local counter that mints deterministic dispatch ids. | -| `PtcRuntime.language` and `PtcRuntime.isolation` | The worker backend supplies the only production values, while PTC mode and every other production caller invoke only `run()`. | Remove the unread descriptors while preserving the worker's language, isolation, budgets, cancellation, and disposal behavior. | -| `ToolNotFoundError.toolName`, `SystemPrompt.config`, and `BashTask.command` | Each stored public value has no production reader. | Drop the unread field while retaining error messages, resolved configuration behavior, and task lifecycle. | -| Backend package-root implementation helpers | The exact inventory below is called only through relative same-package imports. Production namespace imports mount the retained plugin contract without reading these properties; named root consumers are tests. | Retain each adapter/provider/service and its config/error contract; stop exporting the listed helper functions/constants at package roots. | -| Consumer package-root implementation helpers | The exact inventory below has only same-package production callers. Production namespace imports mount plugin contracts without reading helper properties; named root consumers are tests. | Retain plugin contracts and stable error codes; move tests to package-local modules or public behavior and stop exporting the listed helpers at package roots. | - -### Grouped helper-export inventory - -- `dsh-llm-deepseek`: `httpErrorCode`, `serializeMessages`, `serializeRequest`, `DONE`, `parseSse`, `mapFinishReason`, `mapUsage`, and `translate`; `dsh-llm-pi-ai`: `buildModel`, `mapStopReason`, `mapUsage`, `toPiContext`, and `toStreamChunks`. -- `dsh-bash-local`: `DEFAULT_GRACE_MS`, `ENV_OVERRIDES`, `killGroup`, `OutputCollector`, and `runBash`; `dsh-bash-sandbox`: `shellQuote`, `classifyDenial`, and `classifyRunnerFailure`; `dsh-sandbox-local`: `bwrapProfileArgs`, `landlockProfileArgs`, and `seatbeltProfileArgs`. The public mutable test-injection fields and their types are outside this proposal. -- `dsh-fs-local`: `applyLiteralEdit`, `listDirectory`, `probe`, `readForEdit`, `readTextForDiff`, `readWholeText`, `resolveLocalTarget`, `restoreLineEndings`, `streamWholeText`, and `writeFileAtomic`. -- `dsh-web-fetch-http`: `classifyContentType`, `decoderForCharset`, `isSameOrigin`, `parseCharset`, and `validateFetchUrl`; `dsh-web-search-exa`: `mapExaResponse` and `mapExaResult`; `dsh-web-search-deepseek`: `citationSnippets` and `mapAnthropicResponse`; `dsh-web-search-perplexity`: `mapPerplexityResponse` and `mapPerplexityResult`. -- `dsh-tool-fs`: `READ_LIMIT`, `STREAM_MIN_SIZE`, `READ_MAX_BYTES`, `READ_MAX_LINE_LENGTH`, `DIFF_CONTEXT`, `applyReadTool`, `parseReadArgs`, `applyWriteTool`, `formatWriteOutput`, `parseWriteArgs`, `applyEditTool`, `formatEditOutput`, `parseEditArgs`, `buildWindow`, `formatReadOutput`, `computeHunkDiffs`, and `diffsFromMeta`. -- `dsh-tool-web`: `WEB_SEARCH_MAX_RESULTS`, `applyWebSearchTool`, `formatSearchOutput`, `parseSearchArgs`, `presentSearchCall`, `applyWebFetchTool`, `formatFetchOutput`, `parseFetchArgs`, `presentFetchCall`, `renderBody`, and `htmlToMarkdown`; `dsh-tool-call-timeout-policy`: `toolTimeoutResult`; `dsh-compaction-basic`: `resolveConfig`; `dsh-tool-bash`: `renderResult`. - -## Proposal - -Remove or demote every row as one bounded coordinated public-surface cleanup. Update package READMEs, JSDoc, generated API/event catalogs, type-equivalence records, exports maps where needed, and tests so they exercise the owning public contract instead of preserving test-only entry points. Do not collapse any capability seam, LLM adapter, persistence provider, or lifecycle quiescence contract. - -## Alternatives considered - -**Keep test conveniences and self-contained results public.** Public helpers can make white-box tests convenient, self-contained result fields can look ergonomic, and future embedders might want the concrete loop or enumeration methods. Those benefits are hypothetical; retaining them makes every implementation and document explain states that no shipped caller can observe. A real consumer can introduce the smallest contract it needs, with its ownership and failure semantics known. - -**Keep every catalogued member for model-written mounts.** The self-referential toolset is a real generic consumer route, not generated-doc noise. Its value comes from an accurate, composable service API, however, not from preserving duplicate fields or incoherent argument pairs indefinitely; each catalogued contraction above removes a fact available elsewhere on the same execution, agent, or result and updates the API reference in the same change. - -## Acceptance criteria - -- Exact-symbol searches show no removed API outside this Agent Note and any implemented-Agent Note amendments. -- Every API element listed in this Agent Note is absent or demoted as specified; deliberately retained extension/test contracts outside the inventory are unchanged. -- Tool execution, compaction, both LLM adapters, the persistence provider, workflow isolation, and agent creation/resume retain their shipped behavior. -- Typecheck, coverage, snapshots, doc-sync, module-graph verification, build, and hygiene pass. - -## Risks - -Most removals are compile-visible but runtime-neutral. The compaction argument cleanup deliberately forbids a session/context mismatch while retaining the manual-region API. External pre-release embedders and existing model-written mounts may import fewer helpers, pass fewer arguments, or receive narrower result shapes; this is an intentional product-surface contraction, not merely generated-catalog cleanup. The repository is unreleased, so carrying unsupported surface is the larger foundation cost. diff --git a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.zh.md b/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.zh.md deleted file mode 100644 index d2782b733b..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-04-prune-dead-core-spine-api.zh.md +++ /dev/null @@ -1,62 +0,0 @@ -# Agent Note: 裁剪无用的公开与结果接口 - -Status: rejected — stale 2026-07 inventory: rows were pruned piecemeal or gained callers; a fresh audit must supersede it - -[English](2026-07-04-prune-dead-core-spine-api.md) | 中文 - -## 问题 - -若干包根导出、结果字段和便利方法没有生产消费方。它们之所以存活,要么是因为测试通过公开入口导入了内部实现,要么是因为某个类型预期了一个从未出现的调用者。每一项单独看都很小,但合在一起,它们扩大了 SDK 约定、生成的 catalog、文档和回归矩阵,却没有支撑任何已交付的路径。 - -生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Note 行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/extensions/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单: - -| 接口 | 生产证据 | 简化方式 | -| --- | --- | --- | -| `SurfaceManager.invalidate()` | 只有其单元测试调用它;seeding 在惰性创建的 manager 存在之前就已完成,且会话从不替换其日志引用。 | 删除它及其不可能触发的整体替换约定。 | -| `ToolExecutionResult.callId` | 每个钩子已经接收不可变的 `ToolExecution`;循环和 ACP(Agent Client Protocol)通过调用/会话事件关联。没有消费方读取这个重复的结果字段。 | 移除该字段、复制/不匹配守卫,以及证明该重复不可能不一致的测试。 | -| `ReactLoopAgent` 根导出 | 包外的命名导入都是测试;生产代码面向 `Agent` 编程,通过 `ctx.agents` 创建/恢复。 | 将返回类型和接口类型设为 `Agent`,将具体循环类改为包内部;保留有意设计的同步、仅配置的 `AgentLoop.create()` 路径。 | -| `workflow-worker-thread` 的 protocol/runtime/session 再导出与命名的 `WorkerThreadWorkflowEngine` | 所有通过包名导入的消费方都使用默认引擎;工作流 Agent Note 已将 worker 协议格式(wire format)定义为私有。 | 保留默认插件类/配置约定;移除重复的命名类导出,将协议模块保持为源码私有。 | -| `ptc-runtime-worker` 的 protocol/bootstrap 再导出 | 包外的生产/e2e 消费方使用 `NodePtcRuntime` 和配置,而非 `BootstrapPort`、`PatchableStream` 或 worker 消息/启动类型。 | 保留运行时类/配置约定,将其协议格式/bootstrap 词汇改为源码私有。 | -| ACP 的 `agentOptions` 根导出 | 该辅助函数只有同文件和 ACP 测试消费方;唯一的包外生产消费方挂载的是插件命名空间。 | 保留 `name`、`inject`、`Config`、`AcpConfig` 和 `apply`;将 `agentOptions` 改为源码私有,通过桥接层行为测试。 | -| `providerWording` 与 `completedTurnPrefix` 根导出 | 各有一个同包生产调用者;只有 balanced-prefix 辅助函数有一个同包白盒测试。 | 改为源码私有,测试提供方行为。 | -| `depthOf`、`SubagentDepthError`、`waitForExit` 与 `exitsWithin` 根导出 | 生产 subagent 后端消费的是进程内 runner 和子进程构造/dispose(资源释放)辅助函数,而非这些强制机制和测试内部实现。`SENSITIVE_ENV_PATTERN` 不在其中,因为 SDK helper 会将它应用于调用方传入的环境。 | 保留深度与退出行为,但将剩余辅助函数和 error 改为源码私有;通过 spawn 和 dispose 测试。保持共享凭据正则公开。 | -| `LlmError.status` 与回放 status | 适配器/回放填充它,但生产分支基于稳定的错误码/消息判断,从不读取原始 status。 | 移除未读字段和回放管道,保留错误分类。 | -| `BlockAssembler.push()` 返回值 | 两个生产调用者都忽略返回的已完成块。 | 返回 `void`;保留有意公开的 `blocks()`/`message()` 约定。 | -| `compactRegion` 的独立 `session` 参数 | 固定调用方传入的对象就是 `agent.session` 中已有的对象;模型可见的 mount API 也可以调用该方法,但同时接受两个独立对象,会让挂载的插件传入不一致的组合。 | 保留手动 region API,同时有意将其收窄为以 `agent.session` 为唯一真源。 | -| `CompactionResult.startSeq`、`summarySeq`、`endSeq` 与 `summary` | 生产消费方只读取 shadowed range/seq/token 统计;持久日志拥有 summary 和事件标识。 | 移除四个结果回显,保留两个共享的 transcript(文本记录)渲染器。 | -| `BasicCompactionEngine` 的估算/摘要方法可见性 | 没有包外生产调用者调用这五个方法;已实现的 Agent Note 只将 `estimateContentTokens()` 和 `summarize()` 命名为子类钩子。 | 将这两个方法改为 `protected`,其余三个编排专用的估算器改为 private。 | -| `CodeLogEntry.source`/`level` 与 `RunCodeMeta.dispatches` | 每个生产消费方都将日志映射为文本;没有 presenter/模型路径读取其他字段或持久化的 dispatch 计数。 | 将 ptc-runtime 日志改为字符串(或纯文本条目),移除 result-meta 的 dispatch 管道;保留用于生成确定性 dispatch id 的本地计数器。 | -| `PtcRuntime.language` 与 `PtcRuntime.isolation` | worker 后端提供唯一的生产值,而 PTC mode 及其他所有生产调用方只调用 `run()`。 | 移除未读描述符,同时保留 worker 的语言、隔离、预算、取消与资源释放行为。 | -| `ToolNotFoundError.toolName`、`SystemPrompt.config` 与 `BashTask.command` | 每个存储的公开值都没有生产读取者。 | 移除未读字段,保留错误消息、已解析的配置行为和任务生命周期。 | -| 后端包根实现辅助函数 | 下方精确清单仅通过相对路径的同包导入调用。生产命名空间导入挂载的是保留的插件约定,不读取这些属性;包根命名导入的消费方都是测试。 | 保留每个适配器/提供方/服务及其配置/错误约定;停止在包根导出所列辅助函数/常量。 | -| 消费方包根实现辅助函数 | 下方精确清单只有同包生产调用者。生产命名空间导入挂载的是插件约定,不读取辅助属性;包根命名导入的消费方都是测试。 | 保留插件约定和稳定的错误码;将测试迁移到包内模块或公开行为,停止在包根导出所列辅助函数。 | - -### 分组辅助导出清单 - -- `dsh-llm-deepseek`:`httpErrorCode`、`serializeMessages`、`serializeRequest`、`DONE`、`parseSse`、`mapFinishReason`、`mapUsage` 与 `translate`;`dsh-llm-pi-ai`:`buildModel`、`mapStopReason`、`mapUsage`、`toPiContext` 与 `toStreamChunks`。 -- `dsh-bash-local`:`DEFAULT_GRACE_MS`、`ENV_OVERRIDES`、`killGroup`、`OutputCollector` 与 `runBash`;`dsh-bash-sandbox`:`shellQuote`、`classifyDenial` 与 `classifyRunnerFailure`;`dsh-sandbox-local`:`bwrapProfileArgs`、`landlockProfileArgs` 与 `seatbeltProfileArgs`。公开的可变测试注入字段及其类型不在本提案范围内。 -- `dsh-fs-local`:`applyLiteralEdit`、`listDirectory`、`probe`、`readForEdit`、`readTextForDiff`、`readWholeText`、`resolveLocalTarget`、`restoreLineEndings`、`streamWholeText` 与 `writeFileAtomic`。 -- `dsh-web-fetch-http`:`classifyContentType`、`decoderForCharset`、`isSameOrigin`、`parseCharset` 与 `validateFetchUrl`;`dsh-web-search-exa`:`mapExaResponse` 与 `mapExaResult`;`dsh-web-search-deepseek`:`citationSnippets` 与 `mapAnthropicResponse`;`dsh-web-search-perplexity`:`mapPerplexityResponse` 与 `mapPerplexityResult`。 -- `dsh-tool-fs`:`READ_LIMIT`、`STREAM_MIN_SIZE`、`READ_MAX_BYTES`、`READ_MAX_LINE_LENGTH`、`DIFF_CONTEXT`、`applyReadTool`、`parseReadArgs`、`applyWriteTool`、`formatWriteOutput`、`parseWriteArgs`、`applyEditTool`、`formatEditOutput`、`parseEditArgs`、`buildWindow`、`formatReadOutput`、`computeHunkDiffs` 与 `diffsFromMeta`。 -- `dsh-tool-web`:`WEB_SEARCH_MAX_RESULTS`、`applyWebSearchTool`、`formatSearchOutput`、`parseSearchArgs`、`presentSearchCall`、`applyWebFetchTool`、`formatFetchOutput`、`parseFetchArgs`、`presentFetchCall`、`renderBody` 与 `htmlToMarkdown`;`dsh-tool-call-timeout-policy`:`toolTimeoutResult`;`dsh-compaction-basic`:`resolveConfig`;`dsh-tool-bash`:`renderResult`。 - -## 提案 - -以一次有界的、协调的公开接口清理,移除或降级上述每一行。同步更新包 README、JSDoc、生成的 API/事件 catalog、type-equiv 记录、必要的 exports map 以及测试,使测试通过所属的公开约定验证行为,而非保留仅为测试而存在的入口。不折叠任何能力 seam、LLM(大语言模型)适配器、持久化 provider 或生命周期完全停稳约定。 - -## 曾考虑的替代方案 - -**保留测试便利函数和自包含的结果字段为公开。** 公开辅助函数可以让白盒测试更方便,自包含的结果字段看起来更易用,未来的嵌入者可能需要具体循环类或枚举方法。这些好处是假设性的;保留它们会让每处实现和文档都要解释没有已交付调用者能观察到的状态。真正的消费方可以引入它所需的最小约定,其所有权和失败语义明确。 - -**保留所有 catalog 成员以供模型编写的 mount 使用。** 自引用工具集是一条真实的通用消费路径,而非生成文档的噪音。然而,它的价值来自准确、可组合的服务接口,而非无限期保留重复字段或不一致的参数对;上述每一项 catalog 收缩都移除了在同一次执行、同一个 agent(智能体)或同一结果中其他位置已可获得的事实,并在同一变更中更新 API 参考。 - -## 验收标准 - -- 精确符号搜索显示:在本 Agent Note 及任何对已实现 Agent Note 的修正之外,没有被移除的接口。 -- 本 Agent Note 列出的每个接口均按指定方式移除或降级;清单之外有意保留的扩展/测试约定不变。 -- 工具执行、上下文压缩(context compaction)、两个 LLM 适配器、持久化 provider、工作流隔离以及 agent 创建/恢复保持其已交付行为。 -- 类型检查、覆盖率、快照、doc-sync(文档同步门禁)、module-graph 校验、构建和 hygiene 通过。 - -## 风险 - -大多数移除在编译时可见但对运行时无影响。上下文压缩参数清理有意禁止会话/上下文不匹配,同时保留手动 region API。外部预发布嵌入者和现有模型编写的 mount 可能导入更少的辅助函数、传递更少的参数或接收更窄的结果形状;这是有意的产品接口收缩,而非仅仅是生成 catalog 的清理。仓库尚未发布,因此承载不受支持的接口才是更大的基础成本。 diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml index 334f3c3c9b..17c796828a 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.i18n.yaml @@ -9,8 +9,8 @@ en: dcd820314b04d01c zh: c2131cbda05d276c /agent-note-prune-unused-skill-registry-api/proposal: - en: 803833583cc47fe4 - zh: f9cc914a2b1acb07 + en: 6c1220fb96c93ba8 + zh: c862f046d3794146 /agent-note-prune-unused-skill-registry-api/alternatives-considered: en: b8f81e90df8b47c2 zh: 04d7b9c5fef53f6d diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md index 23fb016329..fc91f74ee7 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.md @@ -12,7 +12,7 @@ The skill service's embedded-runtime subsystem has zero production caller of `ct Remove `SkillRegistry.register()`, `SkillRegistration`, the runtime pseudo-provider and reserved-name rules, runtime revisions/cache branches, and runtime-only source/rank normalization. Tests that need an embedded skill register a small real provider. Retain `providerRevision` as the in-flight discovery epoch, but key completed catalogs by cwd alone: every provider mutation synchronously clears the cache, and the post-await revision comparison already prevents inserting stale work. Remove `whenToUse`, `SkillCandidate.path`, and `SkillDefinition.path` from the skill contract and local-provider copies while retaining provider locator/root paths; retain `metadata`, `disableModelInvocation`, `source`, `provider`, `locator`, and `resourceBase` as either deliberate extension vocabulary or production-consumed fields. -Amend the skill-system Agent Note, README, JSDoc, catalogs, and tests. Agent-scoped system-prompt sections, tool providers, and variables are explicitly outside this proposal: the [agent-scope contributor contract](../../implemented/architecture/2026-07-08-agent-scope-contexts.md) intentionally allows all three to be registered during `setup(agentCtx)` through the agent-owned context, so absence of a fixed in-repo scoped registration is not evidence of non-consumption. +Amend the skill-system Agent Note, README, JSDoc, catalogs, and tests. Agent-scoped system-prompt sections, tool providers, and variables are explicitly outside this proposal: the [agent-scope contributor contract](../../../../packages/core/scope/README.md) intentionally allows all three to be registered during `setup(agentCtx)` through the agent-owned context, so absence of a fixed in-repo scoped registration is not evidence of non-consumption. ## Alternatives considered diff --git a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md index e1496aab7b..c9b722454f 100644 --- a/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-api.zh.md @@ -12,7 +12,7 @@ skill(技能)服务的嵌入式运行时子系统中,`ctx.skills.register( 移除 `SkillRegistry.register()`、`SkillRegistration`、运行时伪提供方及保留名称规则、运行时 revision/缓存分支,以及仅用于运行时的 source/rank 规范化逻辑。需要嵌入式 skill 的测试改为注册一个小型真实提供方。保留 `providerRevision` 作为进行中发现操作的 epoch,但已完成的目录缓存仅以 cwd 为键:每次提供方变更同步清除缓存,await 之后的 revision 比较已能阻止插入陈旧结果。从 skill 约定和本地提供方副本中移除 `whenToUse`、`SkillCandidate.path` 与 `SkillDefinition.path`,同时保留提供方的 locator/root 路径;保留 `metadata`、`disableModelInvocation`、`source`、`provider`、`locator` 和 `resourceBase`,因为它们要么是有意开放的扩展词汇,要么是生产消费的字段。 -同步修订 skill 系统 Agent Note、README、JSDoc、目录文件与测试。agent(智能体)作用域的系统提示词段、工具提供方和变量明确不在本提案范围内:[agent 作用域贡献者约定](../../implemented/architecture/2026-07-08-agent-scope-contexts.zh.md)有意允许在 `setup(agentCtx)` 期间通过 agent 拥有的上下文注册这三者,因此仓库内没有固定的作用域注册并不能证明它们未被使用。 +同步修订 skill 系统 Agent Note、README、JSDoc、目录文件与测试。agent(智能体)作用域的系统提示词段、工具提供方和变量明确不在本提案范围内:[agent 作用域贡献者约定](../../../../packages/core/scope/README.zh.md)有意允许在 `setup(agentCtx)` 期间通过 agent 拥有的上下文注册这三者,因此仓库内没有固定的作用域注册并不能证明它们未被使用。 ## 曾考虑的替代方案 diff --git a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.i18n.yaml index 7f181e0cc6..627c2b31ef 100644 --- a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.i18n.yaml @@ -6,8 +6,8 @@ en: 22e69a4c9159be12 zh: c68cd8f8f536dc03 /agent-note-fold-the-single-compaction-backend-into-its-service-package/problem: - en: 272d8d731bdf45b7 - zh: 7502a76b7d3cd367 + en: c83a7bcd45d82228 + zh: 13b4a0f635017335 /agent-note-fold-the-single-compaction-backend-into-its-service-package/proposal: en: 4db8bc2ee4039e44 zh: e1a8c9714d1c4de0 diff --git a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.md b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.md index 17d2ff5b8b..662de3cdc8 100644 --- a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.md +++ b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.md @@ -8,7 +8,7 @@ English | [中文](2026-07-19-fold-compaction-package-split.zh.md) Compaction is split between `@deepseek-ai/dsh-compaction`, which owns an abstract two-method service and shared types, and `@deepseek-ai/dsh-compaction-basic`, which owns the only complete provider. Shipped configurations load only the basic package, and no production package independently consumes the Service Definition package except that provider. -The split adds a package manifest, README, project boundary, dependency edge, abstract forwarding class, generated catalog entries, and composition wiring without demonstrating backend substitution. The [capability-seam decision](../../implemented/architecture/2026-06-13-capability-seams.md) requires a real interface, implementation, and consumer rather than a preemptive split; the [compaction decision](../../implemented/feature/2026-06-18-compaction-capability-seam.md) records that its independent consumer was deferred. +The split adds a package manifest, README, project boundary, dependency edge, abstract forwarding class, generated catalog entries, and composition wiring without demonstrating backend substitution. The [capability-seam decision](../../implemented/architecture/2026-06-13-capability-seams.md) requires a real interface, implementation, and consumer rather than a preemptive split; the [historical compaction decision](../../archived/feature/2026-06-18-compaction-capability-seam.md) records that its independent consumer was deferred. ## Proposal diff --git a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.zh.md b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.zh.md index a981e00965..cc302d2bff 100644 --- a/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-19-fold-compaction-package-split.zh.md @@ -8,7 +8,7 @@ Status: rejected — 计划增加更多压缩后端,因此 Service Definition 压缩(compaction)目前拆分在两个包中:`@deepseek-ai/dsh-compaction` 拥有一个含两个方法的抽象服务和共享类型,`@deepseek-ai/dsh-compaction-basic` 拥有唯一的完整提供方。交付配置只加载 basic 包,除了该提供方外,没有生产包独立消费 Service Definition 包。 -该拆分增加了一份包 manifest(元数据清单)、README、项目边界、依赖边、抽象转发类、生成目录项和组合接线,却没有实际的后端替换用例。[能力 seam 决策](../../implemented/architecture/2026-06-13-capability-seams.zh.md)要求接口、实现和消费方都必须真实存在,而不能预先拆分;[压缩决策](../../implemented/feature/2026-06-18-compaction-capability-seam.zh.md)也记录了独立消费方的实现仍被推迟。 +该拆分增加了一份包 manifest(元数据清单)、README、项目边界、依赖边、抽象转发类、生成目录项和组合接线,却没有实际的后端替换用例。[能力 seam 决策](../../implemented/architecture/2026-06-13-capability-seams.zh.md)要求接口、实现和消费方都必须真实存在,而不能预先拆分;[历史压缩决策](../../archived/feature/2026-06-18-compaction-capability-seam.md)也记录了独立消费方的实现仍被推迟。 ## 提案 diff --git a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml deleted file mode 100644 index a90825c6c8..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Bilingual-pair consistency record for 2026-07-19-make-jsonrpc-directional.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.md -/agent-note-make-json-rpc-completion-and-transport-directional: - en: ee6e55930abd711c - zh: 8f42bc9a2ae73653 -/agent-note-make-json-rpc-completion-and-transport-directional/problem: - en: d464014520aeb2ba - zh: 8d5b916ec1e52ae2 -/agent-note-make-json-rpc-completion-and-transport-directional/proposal: - en: 1877df56f4a6c009 - zh: 88945feba13fd7f2 -/agent-note-make-json-rpc-completion-and-transport-directional/implementation-plan: - en: 98d36962531961b4 - zh: 732397d5ac3e9da5 -/agent-note-make-json-rpc-completion-and-transport-directional/alternatives-considered: - en: aa128111a837e3ce - zh: e2e70c0e7ca577f4 -/agent-note-make-json-rpc-completion-and-transport-directional/acceptance-criteria: - en: c003617f26b1b309 - zh: a0c92fd01a820b9d -/agent-note-make-json-rpc-completion-and-transport-directional/risks: - en: 2a4ab79704013a71 - zh: 17722ca6beff0536 diff --git a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.md b/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.md deleted file mode 100644 index 0a4660bbf1..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.md +++ /dev/null @@ -1,46 +0,0 @@ -# Agent Note: Make JSON-RPC completion and transport directional - -Status: rejected — session/prompt became an enqueue receipt without status (2026-07-30-followup-enqueue-and-owned-runs); transport narrowing may be re-proposed alone - -English | [中文](2026-07-19-make-jsonrpc-directional.zh.md) - -## Problem - -The JSON-RPC bridge models both endpoints as symmetric peers although the shipped protocol is directional. The shared transport (now `dsh-sdk-protocol`, used by the server and by the TypeScript SDK client, which exercises the outbound-request/inbound-notification direction) still implements two halves no endpoint uses: server-originated requests and client-originated notifications. The Python SDK sends requests and receives responses or notifications, but it also queues unused inbound server requests and exposes response helpers. - -`session/prompt` also reports one settled turn through two protocol shapes. The server emits `session.finished` and then returns the constant `{ accepted: true }`; the Python SDK discards that response and waits for the notification to recover the status. Because the response is written only after the handler returns, the notification necessarily precedes the constant response on the same stream. - -The unused halves add pending-request maps, generated IDs, request queues, close-time rejection paths, response helpers, and a second completion waiter without serving a production caller. - -## Proposal - -Specialize each endpoint to its actual role. The server keeps inbound requests, outbound responses, and outbound notifications; the TypeScript and Python clients keep outbound requests and inbound responses or notifications. Delete the direction no endpoint uses — server-originated requests and client-originated notifications. - -Return the settled outcome directly from `session/prompt` as `{ status, reason }` after `agent.whenIdle()`. Delete `session.finished`, the constant acceptance response, and the Python post-response completion loop. `session.event` and subagent notifications still stream before the response, and durable session events remain the source for final-response reconstruction. - -## Implementation plan - -1. In `packages/sdk/server/src/server.ts`, replace `SessionPromptResult.accepted` with `status: 'ok' | 'error' | 'aborted'` and the captured `TurnEndReason`. `HarnessSdkJsonRpcServer.prompt()` will return `completed` as `ok`, `aborted` as `aborted`, and every other current or merge-extensible reason as `error`; reaching idle without a `turn/end` remains an invariant error. Remove only `session.finished`, leaving `session.event`, `subagent.started`, and `subagent.finished` unchanged. -2. In `packages/sdk/protocol/src/transport.ts`, narrow the shared class to the directions with consumers — inbound requests/outbound responses (the server) and outbound requests/inbound responses plus inbound notifications (the TypeScript SDK client) — removing only server-originated `request()` use and client-originated notification dispatch, or split the class into a server-side and client-side transport. Request result, method-not-found, and handler-error responses retain their current behavior and remain ordered after notifications emitted by the awaited handler. -3. In `python/sdk/src/deepseek_harness/client.py`, `models.py`, and `__init__.py`, remove `IncomingRequest`, `_requests`, `notify()`, `next_request()`, `respond()`, and `respond_error()`. Add a public validated `SessionPromptResponse` carrying status and reason, return it from `session_prompt()`, and keep an explicit reader guard that ignores unexpected server-request frames instead of allowing them to match a response waiter. -4. In `python/sdk/src/deepseek_harness/api.py`, build `TurnResult.status` and a new `TurnResult.reason` from `SessionPromptResponse`, then delete the `session.finished` branch and second completion loop. Keep the subscription open during the request and preserve `_request_raw()`'s final notification drain so the last `turn/end` event and any subagent notification written before the response are collected before `Session.run()` reconstructs the final assistant message. -5. Replace the symmetric transport-pair cases in `packages/sdk/protocol/tests/transport.spec.ts` with per-direction coverage, and update `server.spec.ts`, `plugin-apply.spec.ts`, and `built-scope-carrier.e2e.ts` for direct outcomes, ordering, overlap, shutdown, and the narrowed fake; update the TypeScript SDK client (`packages/sdk/client`) and its suites for response-based settlement. Update `python/sdk/tests/test_client.py` for response-based settlement, unexpected-request-frame handling, callback and concurrency behavior, and the removed public helpers. Update the JSON-RPC and bilingual Python SDK READMEs, export JSDoc and declarations, `scripts/smoke-python-runtime.py`, and the Python single-executable snapshot. - -## Alternatives considered - -**Keep a generic symmetric JSON-RPC peer for future methods.** Server-initiated requests may eventually support interactive permissions, but no typed method or production consumer exists. The pre-release protocol can add the smallest required direction when that feature is designed instead of carrying an unexercised peer in the shipped protocol. - -**Keep `session.finished` for streaming clients.** Turn settlement is not incremental data: the request response already marks the same boundary and follows all earlier notifications on the ordered stream. A second terminal notification creates two representations that clients must reconcile. - -## Acceptance criteria - -- The TypeScript endpoint cannot originate requests or consume notifications. -- The Python endpoint cannot originate notifications or consume server requests. -- `session/prompt` returns the authoritative `ok`, `error`, or `aborted` outcome and reason after turn settlement. -- Session events and subagent lifecycle notifications emitted during the turn arrive before the response. -- Same-session overlap rejection, framing, multibyte input, handler errors, flush, shutdown ordering, and final-response reconstruction retain their behavior. -- TypeScript bridge tests, Python SDK tests, built JSON-RPC coverage, snapshots, and generated API documentation pass. - -## Risks - -This deliberately narrows the pre-release wire protocol. Raw clients listening only for `session.finished`, or embedders using the unused symmetric transport methods, must move to the prompt response. A future server-initiated request requires a new typed protocol addition rather than reusing generic dormant machinery. diff --git a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.zh.md b/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.zh.md deleted file mode 100644 index 706a342d6e..0000000000 --- a/.agents/notes/rejected/simplification/2026-07-19-make-jsonrpc-directional.zh.md +++ /dev/null @@ -1,46 +0,0 @@ -# Agent Note: 让 JSON-RPC 完成结果与传输方向单一化 - -Status: rejected — session/prompt became an enqueue receipt without status (2026-07-30-followup-enqueue-and-owned-runs); transport narrowing may be re-proposed alone - -[English](2026-07-19-make-jsonrpc-directional.md) | 中文 - -## 问题 - -JSON-RPC 桥接层把两个端点都建模为对称的对等端,但实际协议具有固定方向。共享传输层(现为 `dsh-sdk-protocol`,由服务端与 TypeScript SDK 客户端共用,后者行使出站请求/入站通知方向)仍实现着没有任何端点使用的两个半边:服务端发起的请求与客户端发起的通知。Python SDK 发送请求并接收响应或通知,却还会把来自服务端、但未使用的入站请求放入队列,并公开响应辅助方法。 - -`session/prompt` 还会用两种协议结构报告同一个已结束轮次。服务端先发出 `session.finished`,再返回常量 `{ accepted: true }`;Python SDK 丢弃该响应,转而等待通知以取得状态。响应只有在处理函数返回后才会写入,因此在同一条有序流上,通知必然先于这个常量响应。 - -这些未使用的双向能力引入了待处理请求表、生成 ID、请求队列、关闭时的拒绝路径、响应辅助方法和第二套完成等待逻辑,却没有任何生产调用方使用。 - -## 提案 - -按实际角色收窄两个端点。服务端保留入站请求、出站响应和出站通知;TypeScript 与 Python 客户端保留出站请求以及入站响应或通知。删除没有任何端点使用的方向——服务端发起的请求与客户端发起的通知。 - -在 `agent.whenIdle()` 完成后,由 `session/prompt` 直接返回 `{ status, reason }` 作为轮次结果。删除 `session.finished`、常量接纳响应以及 Python 中响应后的完成等待循环。`session.event` 与 subagent 通知仍在响应前流式发出,持久化会话事件仍是最终响应重建的真源。 - -## 实施计划 - -1. 在 `packages/sdk/server/src/server.ts` 中,用 `status: 'ok' | 'error' | 'aborted'` 和捕获的 `TurnEndReason` 替换 `SessionPromptResult.accepted`。`HarnessSdkJsonRpcServer.prompt()` 把 `completed` 映射为 `ok`,把 `aborted` 映射为 `aborted`,把其他当前已有或可通过声明合并扩展的原因映射为 `error`;进入空闲状态却没有 `turn/end` 仍视为不变量错误。只删除 `session.finished`,保持 `session.event`、`subagent.started` 和 `subagent.finished` 不变。 -2. 在 `packages/sdk/protocol/src/transport.ts` 中,把共享类收窄到有消费者的方向——入站请求/出站响应(服务端)与出站请求/入站响应及入站通知(TypeScript SDK 客户端)——只删除服务端发起的 `request()` 用法与客户端发起的通知分发,或把该类拆分为服务端与客户端两个传输。请求结果、方法不存在与处理器错误响应保持原有行为,并继续排在被等待处理器发出的通知之后。 -3. 在 `python/sdk/src/deepseek_harness/client.py`、`models.py` 和 `__init__.py` 中,删除 `IncomingRequest`、`_requests`、`notify()`、`next_request()`、`respond()` 和 `respond_error()`。新增公开且经过校验的 `SessionPromptResponse` 来携带状态与原因,由 `session_prompt()` 返回该对象,并保留明确的读取保护:忽略意外的服务端请求帧,避免它们命中响应等待器。 -4. 在 `python/sdk/src/deepseek_harness/api.py` 中,根据 `SessionPromptResponse` 构造 `TurnResult.status` 和新增的 `TurnResult.reason`,再删除 `session.finished` 分支与第二个完成循环。请求期间保持订阅打开,并保留 `_request_raw()` 最后的通知排空步骤,确保写在响应前的最后一条 `turn/end` 事件与任何 subagent 通知,都会在 `Session.run()` 重建最终助手消息之前被收集。 -5. 用按方向的覆盖替换 `packages/sdk/protocol/tests/transport.spec.ts` 中的对称传输对用例,并更新 `server.spec.ts`、`plugin-apply.spec.ts` 和 `built-scope-carrier.e2e.ts`,覆盖直接结果、顺序、重叠、关闭和收窄后的伪实现;同步更新 TypeScript SDK 客户端(`packages/sdk/client`)及其套件以采用基于响应的结束流程。更新 `python/sdk/tests/test_client.py`,覆盖基于响应的结束流程、意外请求帧处理、回调与并发行为,以及已删除的公开辅助方法。同步更新 JSON-RPC README、双语 Python SDK README、导出 JSDoc 与声明、`scripts/smoke-python-runtime.py` 和 Python 单可执行文件快照。 - -## 备选方案 - -**为未来方法保留通用的对称 JSON-RPC 对等端。** 服务端发起的请求将来可能用于交互式权限,但没有类型化方法或生产消费方。该功能完成设计后,预发布协议可以增加所需的最小方向,无需提前保留未使用的对等端能力。 - -**为流式客户端保留 `session.finished`。** 轮次结束不是增量数据:请求响应已经标识同一个边界,并且在有序流中位于先前所有通知之后。第二条终止通知会产生两种结果表示,迫使客户端进行协调。 - -## 验收标准 - -- TypeScript 端点无法发起请求,也不消费通知。 -- Python 端点无法发起通知,也不消费服务端请求。 -- 轮次结束后,`session/prompt` 返回权威的 `ok`、`error` 或 `aborted` 状态及其原因。 -- 轮次中发出的会话事件与 subagent 生命周期通知都先于响应到达。 -- 同一会话的重叠拒绝、分帧、多字节输入、处理器错误、flush、关闭顺序与最终响应重建保持原有行为。 -- TypeScript 桥接测试、Python SDK 测试、构建后 JSON-RPC 覆盖、快照和生成的 API 文档全部通过。 - -## 风险 - -本提案会刻意收窄预发布协议格式。仅监听 `session.finished` 的原始客户端,以及使用未使用对称传输方法的嵌入方,都必须改为读取请求响应。未来若需要服务端发起请求,应新增类型化协议,而不是复用休眠的通用机制。 diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml index 0b54901ab4..ab9a40ab32 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.i18n.yaml @@ -9,8 +9,8 @@ en: 7b7248fc12313b69 zh: a55e2c3376c401a7 /agent-note-dependency-swaps-rejected-by-the-2026-07-nih-audit/proposal: - en: 0a8ed95bec08cf62 - zh: 06de9cab1c862ac4 + en: d95debbdb2a7e730 + zh: 1edbf6ca992a2319 /agent-note-dependency-swaps-rejected-by-the-2026-07-nih-audit/alternatives-considered: en: 229a0110ca47384e zh: 3f86f858d56190c5 diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md index 1ea01d96c5..aa95cee133 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.md @@ -30,7 +30,7 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu **Data and validation:** -- **Ajv for the tools JSON Schema validator**: the [schema-DSL note](../../implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md) explicitly rejected accepting a larger schema language; the validator also does realm-intrinsic prototype checks Ajv does not. +- **Ajv for the tools JSON Schema validator**: the [schema-DSL reference](../../../../packages/core/tools/README.md) explicitly rejected accepting a larger schema language; the validator also does realm-intrinsic prototype checks Ajv does not. - **`structuredClone` for session `snapshotJsonValue`/`isJsonValue`**: it is a validator + detacher enforcing the lossless-JSON boundary with single-read-per-getter and cross-realm intrinsic checks; `structuredClone` accepts Map/Date/-0 and enforces nothing. Same for the deliberately dependency-free `ptc-runtime-worker` mirror hardened against a model-mutated realm. - **`fast-deep-equal` for session surface `isDeepEqualJson`** and **`safe-stable-stringify` for repeat-tool-reminder canonicalization**: both swaps work mechanically but each trades ~17–20 commented, tested lines for the first external runtime dependency of a core package — negative net at this size. - **zod/valibot for durable-event strict decoders** (goal fold, tool-ralph, session): exact-key fail-loud decoders at durable boundaries with event-specific messages; a second schema library beside repo-standard schemastery is a policy change, not a deletion. @@ -39,8 +39,8 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu **Filesystem, subprocess, terminal:** -- **`write-file-atomic` for fs-local/storage-json atomic writes**: the packages lack the private 0700 staging dir, Win32 DACL copy/`ReplaceFileW`, AbortSignal support, and parent-dir fsync — each the point of the hand-roll. The koffi Win32 bindings themselves are justified by the [Windows durable-publish note](../../implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md). -- **`fzstd`/native zstd packages for JSONL frame scanning**: `node:zlib`'s builtin zstd already does the compression ([zstd note](../../implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md), which explicitly rejected an external native dependency); the remaining `scanZstdFrames` locates RFC 8878 frame boundaries *without decompressing* for torn-tail repair, which no package exposes. +- **`write-file-atomic` for fs-local/storage-json atomic writes**: the packages lack the private 0700 staging dir, Win32 DACL copy/`ReplaceFileW`, AbortSignal support, and parent-dir fsync — each the point of the hand-roll. The koffi Win32 bindings themselves are justified by the [Windows durable-publish reference](../../../../packages/session/session-persistence-jsonl/src/win32.ts). +- **`fzstd`/native zstd packages for JSONL frame scanning**: `node:zlib`'s builtin zstd already does the compression ([zstd reference](../../../../packages/session/session-persistence-jsonl/README.md), which explicitly rejected an external native dependency); the remaining `scanZstdFrames` locates RFC 8878 frame boundaries *without decompressing* for torn-tail repair, which no package exposes. - **`picomatch`/`tinyglobby`/`ignore` for fs search**: no glob engine exists — both discovery tools shell out to ripgrep per the [bash-backed discovery note](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.md). - **`istextorbinary`/`chardet` for text detection**: the hand-roll is a ~15-line NUL-sample plus fatal `TextDecoder`; heuristic packages are larger and would change which files the model can read (model-visible `FS_NOT_TEXT` drift). - **`shell-quote` for POSIX single-quoting**: two 1-line quoting helpers with exhaustive tests versus a maintenance-mode package with a CVE history and different escaping output — a safety boundary is the wrong place to save one line. @@ -62,7 +62,7 @@ Adopt the following dependency swaps. Rejected — per-item evidence below; a fu **Repo tooling:** -- **`wireit` for `run-gates.ts`**: could express the `needs:` graph, but allowFailure observational legs and mode-specific concurrency caps have no equivalent, caching must be defensively disabled for a correctness gate runner, and every CI workflow invocation would restructure. The [parallel-gates note](../../implemented/process/2026-07-06-parallel-pre-push-gates.md) accepts a custom scheduler as the cost; keep is defensible. +- **`wireit` for `run-gates.ts`**: could express the `needs:` graph, but allowFailure observational legs and mode-specific concurrency caps have no equivalent, caching must be defensively disabled for a correctness gate runner, and every CI workflow invocation would restructure. The [parallel-gates reference](../../../../scripts/run-gates.ts) accepts a custom scheduler as the cost; keep is defensible. - **`@arethetypeswrong/cli` for `verify-node-next-types`**: attw is per-package (100+ invocations vs one fast whole-workspace compile) and does not check the repo-specific explicit-`.ts`-specifier invariant, so the scan half stays regardless. Recorded as considered; keep the script. - **`syncpack`/`manypkg` for `check-workspace-constraints.ts`**: they cover ~20 lines of range alignment; the load-bearing 200+ lines (computed `files` lists, cordis peer=dev pairing, hierarchy shape) are repo policy no generic engine expresses. - **`remark-validate-links` for `verify-md-links.ts`**: the gate rides the repo's shared mdast toolchain; adopting remark-cli adds a second markdown stack to delete one small file. diff --git a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md index d7140b88a7..943753a5b4 100644 --- a/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md +++ b/.agents/notes/rejected/simplification/2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md @@ -30,7 +30,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 **数据与校验:** -- **以 Ajv 承担 tools 的 JSON Schema 校验器**:[schema DSL 决策](../../implemented/architecture/2026-07-20-unified-json-value-schema-dsl.zh.md)已明确否决接纳更大的 schema 语言;这个校验器还会做 Ajv 不做的、针对 realm 内建原型的检查。 +- **以 Ajv 承担 tools 的 JSON Schema 校验器**:[schema DSL 参考](../../../../packages/core/tools/README.zh.md)已明确否决接纳更大的 schema 语言;这个校验器还会做 Ajv 不做的、针对 realm 内建原型的检查。 - **以 `structuredClone` 替换会话的 `snapshotJsonValue`/`isJsonValue`**:它是校验器加分离器,以「每个 getter 只读一次」和跨 realm 内建对象检查强制执行无损 JSON 边界;`structuredClone` 接受 Map/Date/-0,什么都不强制。有意保持零依赖、针对被模型篡改的 realm 做过加固的 `ptc-runtime-worker` 镜像实现同理。 - **以 `fast-deep-equal` 替换会话接口面的 `isDeepEqualJson`**、**以 `safe-stable-stringify` 承担 repeat-tool-reminder 的规范化**:两项替换在机械层面都可行,但每一项都是拿约 17–20 行带注释、有测试的代码,去换一个核心包的第一个外部运行时依赖——在这个体量上是净亏损。 - **以 zod/valibot 承担持久事件的严格解码器**(goal fold、tool-ralph、session):它们是位于持久化边界、键集精确匹配、失败即明确报错、带事件专属报错信息的解码器;在仓库标准 schemastery 之外再放一个 schema 库是政策变更,不是删除。 @@ -39,8 +39,8 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 **文件系统、子进程与终端:** -- **以 `write-file-atomic` 承担 fs-local/storage-json 的原子写**:这些包缺少私有 0700 暂存目录、Win32 DACL 复制/`ReplaceFileW`、AbortSignal 支持和父目录 fsync——每一项都正是手写实现的意义所在。koffi Win32 绑定本身由 [Windows 持久发布决策](../../implemented/architecture/2026-07-05-windows-jsonl-durable-publish.zh.md)提供依据。 -- **以 `fzstd`/原生 zstd 包承担 JSONL 帧扫描**:`node:zlib` 内置的 zstd 已经负责压缩([zstd 决策](../../implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.zh.md),其中明确否决了外部原生依赖);剩下的 `scanZstdFrames` 为撕裂尾部修复*不做解压*地定位 RFC 8878 帧边界,没有任何包公开这项能力。 +- **以 `write-file-atomic` 承担 fs-local/storage-json 的原子写**:这些包缺少私有 0700 暂存目录、Win32 DACL 复制/`ReplaceFileW`、AbortSignal 支持和父目录 fsync——每一项都正是手写实现的意义所在。koffi Win32 绑定本身由 [Windows 持久发布参考](../../../../packages/session/session-persistence-jsonl/src/win32.ts)提供依据。 +- **以 `fzstd`/原生 zstd 包承担 JSONL 帧扫描**:`node:zlib` 内置的 zstd 已经负责压缩([zstd 参考](../../../../packages/session/session-persistence-jsonl/README.zh.md),其中明确否决了外部原生依赖);剩下的 `scanZstdFrames` 为撕裂尾部修复*不做解压*地定位 RFC 8878 帧边界,没有任何包公开这项能力。 - **以 `picomatch`/`tinyglobby`/`ignore` 承担 fs 搜索**:根本不存在 glob 引擎——依照 [bash 承载的发现工具决策](../../archived/feature/2026-07-09-bash-backed-grep-glob-discovery.md),两个发现类工具都通过 shell 调用 ripgrep。 - **以 `istextorbinary`/`chardet` 承担文本检测**:手写实现是约 15 行的 NUL 采样加 fatal 模式的 `TextDecoder`;启发式包体量更大,还会改变模型能读到哪些文件(模型可见的 `FS_NOT_TEXT` 漂移)。 - **以 `shell-quote` 承担 POSIX 单引号包裹**:两个各 1 行、测试详尽的引号辅助函数,对上一个处于维护模式、有 CVE 历史、转义输出还不一样的包——安全边界不是省一行代码的地方。 @@ -62,7 +62,7 @@ Status: rejected — 下列每一项替换在证据上都未达到净简化门 **仓库工具链:** -- **以 `wireit` 替换 `run-gates.ts`**:它能表达 `needs:` 图,但 allowFailure 观测支路和按模式设置的并发上限没有等价物,对一个正确性门禁运行器来说缓存必须防御性禁用,而且每一处 CI 工作流调用都要重构。[并行门禁决策](../../implemented/process/2026-07-06-parallel-pre-push-gates.zh.md)把自研调度器认作代价;保留是站得住的。 +- **以 `wireit` 替换 `run-gates.ts`**:它能表达 `needs:` 图,但 allowFailure 观测支路和按模式设置的并发上限没有等价物,对一个正确性门禁运行器来说缓存必须防御性禁用,而且每一处 CI 工作流调用都要重构。[并行门禁说明](../../../../scripts/run-gates.ts)把自研调度器认作代价;保留是站得住的。 - **以 `@arethetypeswrong/cli` 替换 `verify-node-next-types`**:attw 按包运行(100+ 次调用对一次快速的全工作区编译),而且不检查仓库特有的显式 `.ts` 说明符不变式,因此扫描的那一半无论如何都得保留。记录为已考虑;保留脚本。 - **以 `syncpack`/`manypkg` 替换 `check-workspace-constraints.ts`**:它们只覆盖约 20 行的版本范围对齐;承重的 200+ 行(计算生成的 `files` 列表、cordis peer=dev 配对、层级形状)是仓库政策,没有通用引擎能表达。 - **以 `remark-validate-links` 替换 `verify-md-links.ts`**:该门禁搭载仓库共享的 mdast 工具链;采用 remark-cli 等于为删掉一个小文件而增加第二套 markdown 技术栈。 diff --git a/.agents/skills/dsh-code-review/SKILL.md b/.agents/skills/dsh-code-review/SKILL.md index bc145e212a..484cd1c288 100644 --- a/.agents/skills/dsh-code-review/SKILL.md +++ b/.agents/skills/dsh-code-review/SKILL.md @@ -15,7 +15,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo — - [dsh-prose-standard](../dsh-prose-standard/SKILL.md): required coverage and editorial judgment for comments, docs, prompts, and visible strings. - [dsh-ci-test-reliability](../dsh-ci-test-reliability/SKILL.md): isolation and regression-proof rules for resource-owning, asynchronous, or flaky tests and fixtures. - [dsh-client-ui-ux](../dsh-client-ui-ux/SKILL.md): feedback-surface choice, overlay safety, platform window adaptation, and visual token discipline for product-user-visible GUI changes. -- [docs/testing.md](../../../docs/testing.md) and the [quality-gates Agent Note](../../notes/implemented/process/2026-06-11-quality-gates.md): required test tiers and gates. +- [docs/testing.md](../../../docs/testing.md) and the [quality-gates reference](../../../docs/testing.md): required test tiers and gates. - [Agent Notes](../../notes/README.md): design rationale. Treat disagreement with an Agent Note as a design discussion, not an automatic veto. - For bilingual changes, read [translation-rules.md](../../../docs/i18n/translation-rules.md) and [terminology.md](../../../docs/i18n/terminology.md); the extended translation skill is outside automatic review and runs only on explicit user invocation. @@ -25,9 +25,8 @@ description: Use when reviewing a pull request in the deepseek-harness repo — 2. **Docs match the code.** Config, defaults, errors, wire fields, events, and public behavior update the package README and JSDoc in the same diff. Comments state non-obvious contracts; flag implementation narration, test walkthroughs, review history, and duplicated rationale for deletion or a link to their one home. 3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [subsystems](../../../docs/subsystems/README.md) page and any `type-equiv` entry. Internal types need no catalog entry. 4. **Registrations clean up.** Verify each new registry contribution passes the disposal tests required by [packages/AGENTS.md](../../../packages/AGENTS.md). -5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship with independent observations at the point where that package can observe it; service or method presence, plugin metadata or effects, fixed pure examples, and probes that call the same operation they claim to verify belong in load, behavior, or unit tests. When no plausible relationship exists, require the package to omit the companion and publication wiring and record its package-specific reason in the README. Reject empty installers and invented checks ([repository rule](../../../AGENTS.md#conventions); [package invariant rules](../../../packages/AGENTS.md)). -6. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. -7. **Client UI copy is locale-owned.** Reject product text embedded in JSX, templates, helper returns, accessibility attributes, or primitive defaults. Require typed dictionary keys, the standard `t` seat or explicit localized props, `verify-client-ui-i18n`, and behavior evidence in each affected locale; preserve user/model/wire data and code tokens verbatim. +5. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. +6. **Client UI copy is locale-owned.** Reject product text embedded in JSX, templates, helper returns, accessibility attributes, or primitive defaults. Require typed dictionary keys, the standard `t` seat or explicit localized props, `verify-client-ui-i18n`, and behavior evidence in each affected locale; preserve user/model/wire data and code tokens verbatim. ## Manual checks @@ -43,7 +42,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo — - **Real entry path:** tests exercise the shipped Loader, bin, worker, ACP bridge, or subprocess where relevant. A hand-mounted plugin does not catch invalid Loader exports; a function plugin must named-export its namespace and have no default export. - **Test strength:** assertions fail on the intended regression and verify external state, logs, events, or disposal rather than restating the implementation or trusting an agent's report. Coverage is necessary but not evidence that the scenario is correct. - **Test reliability:** for a resource-owning, asynchronous, platform-sensitive, or flaky test, apply [dsh-ci-test-reliability](../dsh-ci-test-reliability/SKILL.md) to the real worker/job topology, resource allocation, global-state restoration, synchronization, timeout budget, and quiescent teardown. -- **Invariant lifecycle and negative controls:** verify candidate observations are rejected before publication where possible, session-backed checks reconstruct durable history after late loading or HMR, and a deliberately invalid case fails through the real runner for the intended rule. +- **Negative controls:** verify a deliberately invalid case fails through the real runner for each changed acceptance rule. - **Implemented Agent Notes match shipped reality:** when a PR implements a proposed Agent Note, move and rewrite it as present-tense shipped state in the same diff, then verify paths, names, and mechanisms against the implementation. - **Transcript changes:** editor-visible or model-visible changes update snapshots or explain why no snapshot applies. Review expected-output diffs as behavior changes, not formatting noise. - **Bilingual changes:** compare meaning and terminology on both sides; a green pairing hash does not prove translation quality. diff --git a/.agents/skills/dsh-find-simplifications/SKILL.md b/.agents/skills/dsh-find-simplifications/SKILL.md index 53444db411..fe98baba29 100644 --- a/.agents/skills/dsh-find-simplifications/SKILL.md +++ b/.agents/skills/dsh-find-simplifications/SKILL.md @@ -30,7 +30,7 @@ Use these questions to guide discovery. Read the relevant section of [historical Start with `rg`, then read the matches. Search exact symbols, property reads and writes, discriminants, event and wire strings, config keys, package names, and both `.method(` and `method(` forms. Tests and declarations can show a contract without proving a shipped producer or consumer. -Include `packages/*/*/src`, `apps/`, `python/`, runtime scripts, shipped profiles and overlays, package manifests and exports, loader resolution, generated runtime assets, and installed-package consumers. Classify examples and fixtures by their actual entry point rather than directory name. Follow generated catalogs to their consumers: a catalog used by model-written plugin mounts is a dynamic product API, while a documentation-only listing is not a fixed caller. Public third-party extension paths can be intentional despite zero repository call sites. +Include `packages/*/*/src`, `apps/`, `python/`, runtime scripts, shipped profiles and overlays, package manifests and exports, loader resolution, generated runtime assets, and installed-package consumers. Classify examples and fixtures by their actual entry point rather than directory name. Follow generated catalogs to their consumers: a catalog used by model-written plugin mounts is a dynamic product API, while a documentation-only listing is not a fixed caller. Public third-party extension paths can be intentional despite zero repository call sites. A static unused-code checker must cover manifest-driven loading, generated outputs, and compiler faces without maintaining a parallel per-workspace exception inventory. For each candidate, record the current owner, effective producer/consumer path, what disappears, what remains, and the strongest reason to retain it. Distinguish: @@ -46,8 +46,6 @@ For copies, freezes, validators, and callback captures, name the value's origin, For asynchronous machinery, map each promise, flag, cancellation path, disposer, and reservation to its owner and transition. Collapse mechanisms only when they express the same fact. Preserve synchronous publication and rollback, callback containment, first-terminal-outcome arbitration, worker/process ownership, and dispose-to-quiescence where required. Durable start/end markers can record real facts even when transcript renderers ignore them; dropping interrupted work is a capability loss, not redundant-log cleanup. -An invariant companion is justified by comparing independently produced observations that can diverge. Checks of service presence, plugin metadata, fixed examples, or the same mutation's return value do not qualify. Removing a companion includes its export, build entry, invariant-only compiler references, dependencies, and tests; record the package-specific omission reason in both READMEs. Keep checks comparing independent event producers, durable history, or mutable data. - ## Replace infrastructure only for a net reduction Follow the [dependency policy](../../notes/implemented/process/2026-07-26-dependencies-over-hand-rolling.md). Search existing dependencies and Node builtins at the engine floor before adding a package. Identify the exact implementation and dedicated tests the replacement deletes, its maintenance and transitive cost, and every residual behavior the glue must preserve. diff --git a/.agents/skills/dsh-find-simplifications/references/historical-patterns.md b/.agents/skills/dsh-find-simplifications/references/historical-patterns.md index f5f91342e4..a9199a9c82 100644 --- a/.agents/skills/dsh-find-simplifications/references/historical-patterns.md +++ b/.agents/skills/dsh-find-simplifications/references/historical-patterns.md @@ -10,7 +10,7 @@ Zero fixed callers is weaker evidence than unreachability. The [skill-registrati ## Reduce distinctions where consumers decide -Map every public variant to the consumer action it enables. The [model agent-availability decision](../../../notes/implemented/simplification/2026-09-15-model-agent-availability-and-team-targets.md) gives models one usable Team target and projects multiple residency states to the same availability, while internal routing retains Session identity and residency. The [capability-neutral sandbox context](../../../notes/archived/simplification/2026-07-31-capability-neutral-sandbox-policy-context.md) replaces two approximate capability registries with truthful conditional policy wording; exact tool availability remains in tool schemas. The gain includes registration methods, disposal state, invalidation, and combination tests. +Map every public variant to the consumer action it enables. The [archived model agent-availability decision](../../../notes/archived/simplification/2026-09-15-model-agent-availability-and-team-targets.md) gives models one usable Team target and projects multiple residency states to the same availability, while internal routing retains Session identity and residency. The [capability-neutral sandbox context](../../../notes/archived/simplification/2026-07-31-capability-neutral-sandbox-policy-context.md) replaces two approximate capability registries with truthful conditional policy wording; exact tool availability remains in tool schemas. The gain includes registration methods, disposal state, invalidation, and combination tests. Do not erase distinct durable facts merely because one renderer ignores them. The [durable step-boundary rejection](../../../notes/rejected/simplification/2026-06-20-drop-durable-step-boundaries.md) retains explicit start and completion facts needed for empty failed steps, crash repair, and inspection. Simplify the projection only when the underlying distinction remains necessary. @@ -22,13 +22,13 @@ State the loss as clearly as the deletion. The [interrupted-turn truncation reje ## Make assumptions explicit at their consuming operation -Look for a local default that has acquired shared storage, synchronization, or confirmation machinery. The [explicit Schedule time-zone decision](../../../notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md) keeps browser facts on the originating request and requires an explicit zone in the scheduling operation, removing Session defaults and cross-tab conflict state. The [injected-content decision](../../../notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.md) leaves framing with the caller and removes envelope controls threaded through unrelated layers. Both reduce the number of owners that must interpret one assumption. +Look for a local default that has acquired shared storage, synchronization, or confirmation machinery. The [explicit Schedule time-zone decision](../../../notes/implemented/simplification/2026-08-09-explicit-schedule-time-zone.md) keeps browser facts on the originating request and requires an explicit zone in the scheduling operation, removing Session defaults and cross-tab conflict state. The [archived injected-content decision](../../../notes/archived/simplification/2026-07-20-unwrap-injected-content-envelopes.md) leaves framing with the caller and removes envelope controls threaded through unrelated layers. Both reduce the number of owners that must interpret one assumption. -Do not move a requirement past the place that must enforce it. The [Host-only Remote input-validation decision](../../../notes/implemented/simplification/2026-09-15-host-only-remote-input-validation.md) removes duplicate Client schema execution but retains Host validation before lookup and business invocation, plus local arity and Context binding checks. Its documented cost is later malformed-input failure and the loss of Client parsing as accidental redaction. +Do not move a requirement past the place that must enforce it. The [archived Host-only Remote input-validation decision](../../../notes/archived/simplification/2026-09-15-host-only-remote-input-validation.md) removes duplicate Client schema execution but retains Host validation before lookup and business invocation, plus local arity and Context binding checks. Its documented cost is later malformed-input failure and the loss of Client parsing as accidental redaction. ## Derive at use or reuse an existing delivery owner -For each cache or event subscription, ask when the consumer needs the value and whether an authoritative source already retains it. [Feedback telemetry](../../../notes/archived/simplification/2026-08-06-buffer-free-feedback-telemetry.md) reads the canonical Session prefix at feedback time, deleting a continuously copied pre-feedback buffer and its listeners. [Workspace recency](../../../notes/implemented/simplification/2026-09-10-derived-workspace-recency.md) sorts current summaries instead of persisting promotion history; manual ordering remains independent user state. [Web subagent projections](../../../notes/implemented/simplification/2026-09-08-web-subagent-catalog-projections.md) reuse a shared stream and projection store, removing feature notifications, repeated reads, and a second membership cache. +For each cache or event subscription, ask when the consumer needs the value and whether an authoritative source already retains it. [Feedback telemetry](../../../notes/archived/simplification/2026-08-06-buffer-free-feedback-telemetry.md) reads the canonical Session prefix at feedback time, deleting a continuously copied pre-feedback buffer and its listeners. [archived Workspace recency](../../../notes/archived/simplification/2026-09-10-derived-workspace-recency.md) sorts current summaries instead of persisting promotion history; manual ordering remains independent user state. [archived Web subagent projections](../../../notes/archived/simplification/2026-09-08-web-subagent-catalog-projections.md) reuse a shared stream and projection store, removing feature notifications, repeated reads, and a second membership cache. The timing promise determines whether this works. Feedback-time redaction differs from capture-time redaction; manual placement cannot be derived from activity timestamps; an initial historical read may remain necessary when a stream baseline is absent. Name such residual obligations instead of declaring all caches redundant. diff --git a/.agents/skills/dsh-speed-up-perf/SKILL.md b/.agents/skills/dsh-speed-up-perf/SKILL.md index fbd5d262e1..f8b5eb6141 100644 --- a/.agents/skills/dsh-speed-up-perf/SKILL.md +++ b/.agents/skills/dsh-speed-up-perf/SKILL.md @@ -13,7 +13,7 @@ Read [AGENTS.md](../../../AGENTS.md), [architecture](../../../docs/architecture. Agree on the user-visible endpoint, workload range, resource constraints, acceptable minor behavior differences, and stopping rule. Keep backend and browser end-to-end measurements separate: a fast history iterator or Client fold does not prove fast transport, paint, scrolling, or input response. Exclude model/network latency when measuring local overhead, and state that exclusion rather than calling the result complete product latency. -Inspect the exact current base, not just the running checkout. Study final merged diffs, owning source, tests, and resolved review threads; a PR body can describe an abandoned implementation. Separate merged, closed-unmerged, superseded, estimated, and newly measured evidence. The [performance workflow decision and evidence](../../notes/implemented/process/2026-09-06-evidence-driven-performance-skill.md) supply historical leads, not authority to reintroduce their implementations. +Inspect the exact current base, not just the running checkout. Study final merged diffs, owning source, tests, and resolved review threads; a PR body can describe an abandoned implementation. Separate merged, closed-unmerged, superseded, estimated, and newly measured evidence. The [archived performance workflow decision and evidence](../../notes/archived/process/2026-09-06-evidence-driven-performance-skill.md) supply historical leads, not authority to reintroduce their implementations. ## Survey user paths, then rank candidates @@ -75,7 +75,7 @@ Change one causal factor at a time. Re-run both the focused scenario and its end ## Preserve behavior and resource ownership -Performance measurements complement functional evidence; they do not replace it. Run or add the narrow owning tests for output, ordering, paging, stream indexes, errors, cancellation, concurrency, and disposal as applicable. Preserve model-visible/logged equivalence, released-generation immutability, atomic publication, required validation, and writable readiness. Do not silently truncate history, skip tool results, disable invariants, or change lifecycle semantics to reach a number. +Performance measurements complement functional evidence; they do not replace it. Run or add the narrow owning tests for output, ordering, paging, stream indexes, errors, cancellation, concurrency, and disposal as applicable. Preserve model-visible/logged equivalence, released-generation immutability, atomic publication, required validation, and writable readiness. Do not silently truncate history, skip tool results, or change lifecycle semantics to reach a number. State any deliberate minor visible difference and verify it through the owning keyless snapshot. For a product-visible GUI change, include the required browser evidence/GIF. Keep functional expectations independent of benchmark internals; benchmark assertions need enough evidence to reach the real endpoint, not a second semantic test suite. diff --git a/.agents/skills/dsh-trim-cot-leakage/SKILL.md b/.agents/skills/dsh-trim-cot-leakage/SKILL.md index cd26b03891..f8a852bd66 100644 --- a/.agents/skills/dsh-trim-cot-leakage/SKILL.md +++ b/.agents/skills/dsh-trim-cot-leakage/SKILL.md @@ -5,7 +5,7 @@ description: Use when auditing or fixing prose that reads like a leaked reasonin # Trimming Chain-of-Thought Leakage -Chain-of-thought leakage is prose whose vantage is the authoring session rather than the repository: it cites artifacts only that session could see, narrates the change instead of the state, or argues with a reviewer who has left. The fix is never deletion alone when a passage carries factual clauses — restate each so it stands at HEAD, then delete the transcript around it; a passage carrying none (an audit code, control-flow narration) is deleted outright. **REQUIRED BACKGROUND:** [dsh-prose-standard](../dsh-prose-standard/SKILL.md) owns the complete-proposition rule this skill applies; the [committed-artifact-citations note](../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) owns the citation rule's rationale. It is guidance, not a script. +Chain-of-thought leakage is prose whose vantage is the authoring session rather than the repository: it cites artifacts only that session could see, narrates the change instead of the state, or argues with a reviewer who has left. The fix is never deletion alone when a passage carries factual clauses — restate each so it stands at HEAD, then delete the transcript around it; a passage carrying none (an audit code, control-flow narration) is deleted outright. **REQUIRED BACKGROUND:** [dsh-prose-standard](../dsh-prose-standard/SKILL.md) owns the complete-proposition rule this skill applies; the [archived committed-artifact-citations note](../../notes/archived/process/2026-08-09-committed-artifact-citations.md) records the citation rule's historical rationale. It is guidance, not a script. ## The one test diff --git a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md index 4802413c4d..8571622df5 100644 --- a/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md +++ b/.agents/skills/dsh-trim-cot-leakage/references/recall-batteries.md @@ -5,7 +5,7 @@ Probes for [the taxonomy](../SKILL.md#taxonomy), tuned during the 2026-08 purge. ## Invocation rules - Add `--hidden --glob '!.git/**'` so `.agents/` is searched; ripgrep skips dot-directories by default and the purge's biggest miss risk was Agent Notes. -- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/**'` (the skill's own files quote leaked wording as calibration), plus recorded fixture and snapshot directories in scope. The [owning note](../../../notes/implemented/process/2026-08-09-committed-artifact-citations.md) also self-hits through its quoted evidence; judge it as evidence, not usage. +- Exclusions go last so a later include cannot re-admit them: `--glob '!vendor/**' --glob '!node_modules/**' --glob '!.agents/notes/archived/**' --glob '!.agents/skills/dsh-trim-cot-leakage/**'` (the skill's own files quote leaked wording as calibration), plus recorded fixture and snapshot directories in scope. The [owning reference](../SKILL.md) also self-hits through its quoted evidence; judge it as evidence, not usage. - Natural-language lines carry `-i` so sentence-initial capitals hit ("This PR adds…", "Probably fine…"); the first line, which matches code patterns, stays case-sensitive — `-i` would turn `\bT\d\b` and `\bP-I\b` into noise. - Bound complete phrases. `\bthis PR\b` must match "this PR adds" without matching "this project", "this process", or "this provider". - A zero-hit pattern proves nothing until it matches a known positive, and a noisy pattern proves nothing until it rejects a near-miss negative. Calibrate both before trusting a corpus result. diff --git a/.agents/skills/record-browser-gif/SKILL.md b/.agents/skills/record-browser-gif/SKILL.md index b425b35b6f..a16f8294e9 100644 --- a/.agents/skills/record-browser-gif/SKILL.md +++ b/.agents/skills/record-browser-gif/SKILL.md @@ -7,7 +7,7 @@ description: Record browser or Web UI interaction demos as optimized GIFs using Produce a short, truthful UI demonstration as a local GIF, and — only when the task includes attaching it to a pull request — publish it through the attach workflow at the end of this skill. The available browser-control workflow remains preferred. Use [Playwright Videos](https://playwright.dev/docs/videos) when that workflow supports continuous capture at higher frame rates; use the bundled encoder for trimming, playback speed, final hold, dimensions, and size. -The [evidence-chain decision](../../notes/implemented/process/2026-08-08-browser-gif-evidence-chain.md) owns why one storyboard comes from one isolated run and why publication revalidates both the artifact and the demonstrated pull-request head. +The [archived evidence-chain decision](../../notes/archived/process/2026-08-08-browser-gif-evidence-chain.md) records why one storyboard comes from one isolated run and why publication revalidates both the artifact and the demonstrated pull-request head. ## Every GUI pull request includes a GIF @@ -27,7 +27,7 @@ The recording itself is part of the evidence: use a real server booted from that A GIF for a specific pull request demonstrates that pull request's tree, so stage per pull request: 1. Require a clean worktree, record its exact commit with `git rev-parse HEAD`, then build that recorded tree — here, `pnpm run build && pnpm run build:web`. A GIF recorded against another commit's build misattributes the evidence. -2. Boot one server per port from that tree with fresh scratch `DSH_HOME`, `DSH_AGENTS_HOME`, workspace, and session state. Give the browser a fresh isolated context or profile as well; if the browser workflow cannot create one, clear that origin's cookies and site storage before navigation so persisted client state cannot affect the evidence. Source the root `.env` for the API key through the application's normal path; never echo the key. +2. Boot one server per port from that tree with fresh scratch `DSH_HOME`, `DSH_AGENTS_HOME`, workspace, and session state. Give the browser a fresh isolated context or profile as well; if the browser workflow cannot create one, clear that origin's cookies and site storage before navigation so persisted client state cannot affect the evidence. Real API credentials can be available in `~/.dsh/.credentials.yaml` even when `DEEPSEEK_API_KEY` and the root `.env` are absent. With a scratch `DSH_HOME`, use a recording overlay to set the `dsh-credentials-local` provider's `path` to that existing file's absolute path. Let the application's normal credential provider resolve the key; never echo it. 3. Treat one storyboard as one evidence run: every published frame comes from that server and those state roots, workspace, session, and model-backed scenario run. If capture automation fails, discard its frames and rerun from fresh roots; never splice frames from separate runs. 4. When switching between pull requests, stop the old server by PID or an exact match on its command line. A broad `pkill -f` pattern can match and kill the shell that launched it — including your own. diff --git a/.github/AGENTS.md b/.github/AGENTS.md index f4f3c5d555..7cc2946f53 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,3 +1,3 @@ # AGENTS.md — GitHub Actions -Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. Native Windows build and process checks contribute to the pull-request `all checks passed` verdict; Wine runs Windows Node on hosted Linux only in `ci-master.yml`. Python runtime CI checks Linux/Windows x64 on pull requests and Linux ARM64 plus both macOS architectures on master pushes; releases retain all five targets ([platform policy](../.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md)). `ci.yml` is pull-request-only. Master-only platform checks, Linux/Windows self-hosted standbys, and manual runner benchmarks live in `ci-master.yml`, which listens to master pushes and `workflow_dispatch`, not `pull_request`; separating workflow triggers keeps master-only jobs out of PR check panels. The master standbys validate the self-hosted failover targets; preserve the existing per-platform switches (values `selfhosted` for the in-house standbys and `blacksmith` for Blacksmith's hosted runners) and the Dependabot hosted fallback under the `selfhosted` values ([failover runbook](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md), [blacksmith failover leg note](../.agents/notes/implemented/process/2026-09-09-blacksmith-failover-leg.md)). +Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. Native Windows build and process checks contribute to the pull-request `all checks passed` verdict; Wine runs Windows Node on hosted Linux only in `ci-master.yml`. Python runtime CI checks Linux/Windows x64 on pull requests and Linux ARM64 plus both macOS architectures on master pushes; releases retain all five targets ([platform policy](workflows/ci-master.yml)). `ci.yml` is pull-request-only. Master-only platform checks, Linux/Windows self-hosted standbys, and manual runner benchmarks live in `ci-master.yml`, which listens to master pushes and `workflow_dispatch`, not `pull_request`; separating workflow triggers keeps master-only jobs out of PR check panels. The master standbys validate the self-hosted failover targets; preserve the existing per-platform switches (values `selfhosted` for the in-house standbys and `blacksmith` for Blacksmith's hosted runners) and the Dependabot hosted fallback under the `selfhosted` values ([failover runbook](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md), [blacksmith failover leg note](../.agents/notes/implemented/process/2026-09-09-blacksmith-failover-leg.md)). diff --git a/.github/issue-management/README.i18n.yaml b/.github/issue-management/README.i18n.yaml index 0d62118377..e008dab1ab 100644 --- a/.github/issue-management/README.i18n.yaml +++ b/.github/issue-management/README.i18n.yaml @@ -18,11 +18,11 @@ en: ad11184112771cc8 zh: cc8c112a76dcf62e /issue-management/lifecycle-events: - en: 62bca299f0a5e213 - zh: 8853e01a9e3776ec + en: f0e434f0ead4e952 + zh: c7c62cee2486a3ff /issue-management/configuration-and-limitations: - en: 3ef00734bb83737f - zh: 9d91319c953496dd + en: 36cbf0d37f910b04 + zh: cdab6aef6763941f /issue-management/module-ownership: en: e83f694e0be99e69 zh: 85fb201f86af2ce8 diff --git a/.github/issue-management/README.md b/.github/issue-management/README.md index 49e91859f4..f7dc1a3523 100644 --- a/.github/issue-management/README.md +++ b/.github/issue-management/README.md @@ -45,16 +45,18 @@ REST reads use the repository `GITHUB_TOKEN`. Project validation uses a separate Approval-only and comment-only reviews do not allocate a lifecycle runner. PR pushes and label changes, and Issue assignment changes, do not trigger lifecycle work. Other subscribed Issue events maintain membership, state, and audit comments; exact subscriptions live in the workflow. -PR opening initializes an empty Project `Start Date` for every referenced Issue, including informational references, using the PR creation date in the configured time zone. This lifecycle operation can add Project membership and needs Project write access; the informational-reference read exemption applies only to PR validation. [Planning-field ownership](../../.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.md) defines date preservation. +PR opening initializes an empty Project `Start Date` for every referenced Issue, including informational references, using the PR creation date in the configured time zone. This lifecycle operation can add Project membership and needs Project write access; the informational-reference read exemption applies only to PR validation. Existing Start Date values are preserved. ----- ## Configuration and limitations -[config.json](config.json) selects the repository, Project, field names, statuses, lifecycle actor, and time zone. The policy reads the Project custom single-select `Priority` field, not a native organization Issue Priority field. Maintainers set Project Priority manually; skill guidance that directs edits to native Issue fields does not populate this value. Issue audits remove PR-only kinds and retired label aliases before validating the remaining metadata. There is no field migration or Priority synchronization. +[config.json](config.json) selects the repository, Project, field names, statuses, lifecycle actor, and time zone. The policy reads the Project custom single-select `Priority` field, not a native organization Issue Priority field. Project-local planning fields need no separate organization Issue Fields permission. Maintainers set Project Priority manually; skill guidance that directs edits to native Issue fields does not populate this value. Issue audits remove PR-only kinds and retired label aliases before validating the remaining metadata. There is no field migration or Priority synchronization. -Lifecycle processing is event-driven, not a reconciler. Omitted events do not repair Project state, and concurrent Project mutations have no atomic compare-and-swap. Selective evaluation does not redesign required-check authority or guarantee measured Actions-minute savings. The [selective-evaluation decision](../../.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.md) records the trade-offs. +Before removing a legacy organization field, compare every value with its Project value, including archived Project items. + +Lifecycle processing is event-driven, not a reconciler. Omitted events do not repair Project state, and concurrent Project mutations have no atomic compare-and-swap. Selective evaluation does not redesign required-check authority or guarantee measured Actions-minute savings. The [archived selective-evaluation decision](../../.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.md) records the trade-offs. ----- diff --git a/.github/issue-management/README.zh.md b/.github/issue-management/README.zh.md index 8424f90cd2..385eb45bcc 100644 --- a/.github/issue-management/README.zh.md +++ b/.github/issue-management/README.zh.md @@ -45,16 +45,18 @@ REST 读取使用仓库 `GITHUB_TOKEN`。Project 校验使用独立的 App token 仅批准或仅评论的评审不分配生命周期 runner。PR 推送与标签变更,以及 Issue 指派变更,不触发生命周期工作。其他已订阅的 Issue 事件维护 Project 归属、状态及审计评论;精确订阅列表由工作流定义。 -PR 打开时,工作流按配置时区中的 PR 创建日期,为每个被引用 Issue(包括信息型引用)初始化空的 Project `Start Date`。此生命周期操作可以添加 Project 归属,并需要 Project 写权限;信息型引用的读取豁免仅适用于 PR 校验。[规划字段归属](../../.agents/notes/implemented/process/2026-09-02-project-local-issue-planning-fields.zh.md)定义日期保留规则。 +PR 打开时,工作流按配置时区中的 PR 创建日期,为每个被引用 Issue(包括信息型引用)初始化空的 Project `Start Date`。此生命周期操作可以添加 Project 归属,并需要 Project 写权限;信息型引用的读取豁免仅适用于 PR 校验。已有 Start Date 值会保留。 ----- ## 配置与限制 -[config.json](config.json)选择仓库、Project、字段名、状态、生命周期操作者和时区。策略读取 Project 自定义单选 `Priority` 字段,而非组织原生 Issue Priority 字段。维护者手动设置 Project Priority;指引编辑原生 Issue 字段的 skill 不会填充该值。Issue 审计先移除 PR 专用 kind 标签和已停用的标签别名,再校验其余元数据。不提供字段迁移或 Priority 同步。 +[config.json](config.json)选择仓库、Project、字段名、状态、生命周期操作者和时区。策略读取 Project 自定义单选 `Priority` 字段,而非组织原生 Issue Priority 字段。Project 本地规划字段不需要另行授予组织 Issue Fields 权限。维护者手动设置 Project Priority;指引编辑原生 Issue 字段的 skill 不会填充该值。Issue 审计先移除 PR 专用 kind 标签和已停用的标签别名,再校验其余元数据。不提供字段迁移或 Priority 同步。 -生命周期处理由事件驱动,不是协调器。被省略的事件不会修复 Project 状态,并发 Project mutation 也没有原子比较并交换保护。选择性求值不重新设计必需检查的权威来源,也不保证已测得的 Actions 分钟节省。[选择性求值决策](../../.agents/notes/implemented/process/2026-09-07-selective-issue-policy-evaluation.zh.md)记录取舍。 +删除旧的组织字段前,必须逐一核对其中的值与对应的 Project 值,包括已归档的 Project item。 + +生命周期处理由事件驱动,不是协调器。被省略的事件不会修复 Project 状态,并发 Project mutation 也没有原子比较并交换保护。选择性求值不重新设计必需检查的权威来源,也不保证已测得的 Actions 分钟节省。[已归档的选择性求值决策](../../.agents/notes/archived/process/2026-09-07-selective-issue-policy-evaluation.md)记录取舍。 ----- diff --git a/.github/preview-sizing/README.i18n.yaml b/.github/preview-sizing/README.i18n.yaml index d2372d8529..065a63ad3e 100644 --- a/.github/preview-sizing/README.i18n.yaml +++ b/.github/preview-sizing/README.i18n.yaml @@ -18,5 +18,5 @@ en: 25ee2f4b26ef7770 zh: 788b7fb4bee6e159 /pr-preview-runner-sizing/dev-note: - en: 85ac56719e30a350 - zh: f53f31c1b8553cc0 + en: c3e8959997c4d5d0 + zh: 109a391915c2bdfb diff --git a/.github/preview-sizing/README.md b/.github/preview-sizing/README.md index 142854e582..5fcec8ad2c 100644 --- a/.github/preview-sizing/README.md +++ b/.github/preview-sizing/README.md @@ -32,4 +32,4 @@ Runner selection does not alter pull-request events, per-PR cancellation, immuta ## Dev Note -The [runner decision](../../.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.md) records measurements, cost estimates, and image/CPU variation. The build-only experiment does not verify production deployment. +The [archived runner decision](../../.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.md) records measurements, cost estimates, and image/CPU variation. The build-only experiment does not verify production deployment. diff --git a/.github/preview-sizing/README.zh.md b/.github/preview-sizing/README.zh.md index a71325189d..43258d716a 100644 --- a/.github/preview-sizing/README.zh.md +++ b/.github/preview-sizing/README.zh.md @@ -32,4 +32,4 @@ ## 开发备注 -[运行器决策](../../.agents/notes/implemented/process/2026-09-06-preview-hosted-runner-sizing.zh.md) 记录测量、成本估算及镜像/CPU 差异。仅构建实验不验证生产部署。 +[已归档的运行器决策](../../.agents/notes/archived/process/2026-09-06-preview-hosted-runner-sizing.md) 记录测量、成本估算及镜像/CPU 差异。仅构建实验不验证生产部署。 diff --git a/.github/review-ownership/README.md b/.github/review-ownership/README.md index 2da4c5e2cd..26ae70e6f6 100644 --- a/.github/review-ownership/README.md +++ b/.github/review-ownership/README.md @@ -58,6 +58,6 @@ Run `pnpm run test:approval-policy` for policy parsing, effective review decisio ## Dev Note -[Production blame weighting](../../.agents/notes/implemented/process/2026-09-11-production-blame-approval-weight.md) records the scoring rationale and measured costs. +[archived Production blame weighting](../../.agents/notes/archived/process/2026-09-11-production-blame-approval-weight.md) records the scoring rationale and measured costs. -[PR-scoped delegation](../../.agents/notes/implemented/process/2026-09-15-pr-approval-delegation.md) records score ownership and revocation choices. +[archived PR-scoped delegation](../../.agents/notes/archived/process/2026-09-15-pr-approval-delegation.md) records score ownership and revocation choices. diff --git a/.github/review-ownership/requirements.txt b/.github/review-ownership/requirements.txt index a0c04bed92..3429a636c6 100644 --- a/.github/review-ownership/requirements.txt +++ b/.github/review-ownership/requirements.txt @@ -1 +1 @@ -Pygments==2.19.2 +Pygments==2.21.0 diff --git a/.github/workflows/build-exe-for-python-sdk.yml b/.github/workflows/build-exe-for-python-sdk.yml index e53d524ead..9ea51311b6 100644 --- a/.github/workflows/build-exe-for-python-sdk.yml +++ b/.github/workflows/build-exe-for-python-sdk.yml @@ -72,7 +72,7 @@ jobs: version: ${{ steps.version.outputs.version }} repository-version: ${{ steps.version.outputs.repository-version }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - name: Resolve repository version id: version @@ -130,7 +130,7 @@ jobs: && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v6.3.0 with: @@ -145,7 +145,7 @@ jobs: --package sdk --output-dir dist-python - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@v7.0.1 with: name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl path: dist-python/deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl @@ -168,11 +168,11 @@ jobs: matrix: include: ${{ fromJSON(needs.plan.outputs.matrix) }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} @@ -185,7 +185,7 @@ jobs: # setup-node's built-in pnpm store cache keys on platform AND arch, so # the Linux architectures sharing runner.os stay on separate caches. - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm @@ -199,7 +199,7 @@ jobs: # Cache pkg's target Node binary; lockfile changes roll the # exact key while the restore prefix can seed its replacement. - - uses: actions/cache@v4 + - uses: actions/cache@v6.1.0 with: path: ~/.pkg-cache key: pkg-fetch-${{ matrix.target }}-${{ hashFiles('pnpm-lock.yaml') }} @@ -232,7 +232,8 @@ jobs: esac addon_dir="$(realpath packages/subprocess/subprocess-local/node_modules/node-pty)" pnpm_setup_root="$(realpath "$(dirname "$(dirname "$PNPM_HOME")")")" - (cd "$addon_dir" && npm_config_build_from_source=true pnpm run install) + # Verification installs would install node-gyp inside the package and package it into the exe. + (cd "$addon_dir" && npm_config_build_from_source=true pnpm --config.verifyDepsBeforeRun=false run install) addon="$addon_dir/build/Release/pty.node" [ -f "$addon_dir/build/Makefile" ] || { echo "::error::node-pty install did not generate $addon_dir/build/Makefile" @@ -259,7 +260,9 @@ jobs: - name: Build single-exe env: DSH_BUILD_CLIENT_PROFILE: official - run: pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }} + run: >- + pnpm exec tsx scripts/build-exe-for-python-sdk.ts --targets=${{ matrix.target }} + ${{ inputs.ci && github.event_name == 'pull_request' && '--artifacts-only' || '' }} - name: Resolve platform outputs (POSIX) id: runtime-posix @@ -308,7 +311,7 @@ jobs: --runtime-exe "${{ steps.runtime-posix.outputs.exe || steps.runtime-windows.outputs.exe }}" --output-dir dist-python - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@v8.0.1 with: name: deepseek_harness_sdk-${{ needs.plan.outputs.version }}-py3-none-any.whl path: dist-python @@ -497,7 +500,7 @@ jobs: env -u PYTHONPATH -u DSH_RUNTIME_MODE /tmp/dsh-sdk/bin/python /work/scripts/smoke-python-runtime.py --scenario sdk-mcp --installed-wheel ' - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@v7.0.1 with: name: ${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }} path: dist-python/${{ steps.runtime-posix.outputs.wheel || steps.runtime-windows.outputs.wheel }} diff --git a/.github/workflows/build-preview-cloudflare.yml b/.github/workflows/build-preview-cloudflare.yml index 80caebdb76..e8ad9bd2ec 100644 --- a/.github/workflows/build-preview-cloudflare.yml +++ b/.github/workflows/build-preview-cloudflare.yml @@ -35,15 +35,15 @@ jobs: runs-on: ubuntu-24.04 name: cloudflare pages preview steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -57,7 +57,7 @@ jobs: # Read-only: the preview lane consumes the default-branch cache without # putting cache upload on its own path. - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} @@ -69,9 +69,9 @@ jobs: # apps/web consumes workspace packages as built lib products, and # build:preview packs the image through the packer's installed bin - # (lib/bin.js), so neither half exists before the full build runs. + # (lib/bin.js), so neither half exists before the artifact build runs. - name: Build workspace - run: pnpm run build + run: pnpm run build --artifacts-only - name: Build the preview page and pack the VFS image env: diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml index 75410ad410..5feb9fb33c 100644 --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -48,15 +48,15 @@ jobs: name: windows node 24 / wine timeout-minutes: 15 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -68,7 +68,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} @@ -80,7 +80,7 @@ jobs: id: wine-cache-key run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" - - uses: actions/cache@v4 + - uses: actions/cache@v6.1.0 with: path: ~/wine-debs key: ${{ steps.wine-cache-key.outputs.key }} @@ -139,21 +139,21 @@ jobs: # github.event.before, so full history is required: depth 2 would miss it # on multi-commit or force pushes; full fetch is cheap here because # checkout resolves against the VM's local mirror. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 # Redirect the Node compile cache (enabled by pnpm and TypeScript) off - # the root partition's /tmp before the first pnpm call in this lane — - # see .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md. + # the root partition's /tmp before the first pnpm call in this lane. + # see .github/workflows/ci.yml. - name: Redirect Node compile cache to runner temp run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -191,13 +191,13 @@ jobs: name: serial / macos runs-on: macos-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -230,7 +230,7 @@ jobs: runs-on: [self-hosted, dsh-win-ci, windows] timeout-minutes: 120 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - name: Enable Developer Mode (symlink support) shell: pwsh @@ -238,11 +238,11 @@ jobs: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -356,15 +356,15 @@ jobs: blacksmith: '' workload: production-site steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} # The benchmark's Windows lanes deliberately skip the store cache like # the independent native Windows job; an empty input disables caching. - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} cache: ${{ matrix.platform == 'linux' && 'pnpm' || '' }} @@ -467,15 +467,15 @@ jobs: blacksmith: '' workers: '2' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} # Unlike the larger-runner suite, both platforms cache the store here: # the consolidated topology measures cache mechanics as workload. - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} cache: pnpm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f79dab45b..c8f2afcf8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,22 +61,21 @@ jobs: echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" # Fetch complete history so the archive gate can read the trusted PR base from a reused shallow checkout. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false # Redirect the Node compile cache (enabled by pnpm and TypeScript) off - # the root partition's /tmp before the first pnpm call in this lane — - # see .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md. + # the root partition's /tmp before the first pnpm call in this lane. - name: Redirect Node compile cache to runner temp run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -91,7 +90,7 @@ jobs: # Pull requests consume the default-branch cache but do not put cache # compression and upload on the paid latency-critical path. Skipped # under failover — see the coverage lane's identical rationale. - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -138,21 +137,28 @@ jobs: run: | echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false + - name: Restore coverage duration history + uses: actions/cache/restore@v6.1.0 + with: + path: .coverage-times.json + key: coverage-times-Linux-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_LINUX || 'enterprise' }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + coverage-times-Linux-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_LINUX || 'enterprise' }}- + # Redirect the Node compile cache (enabled by pnpm and TypeScript) off - # the root partition's /tmp before the first pnpm call in this lane — - # see .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md. + # the root partition's /tmp before the first pnpm call in this lane. - name: Redirect Node compile cache to runner temp run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -166,7 +172,7 @@ jobs: # Self-hosted runners reuse the shared persistent pnpm store. # Hosted runners restore the remote cache into their ephemeral store. - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -190,6 +196,13 @@ jobs: - name: Run exhaustive coverage run: pnpm run check:ci:coverage + - name: Save coverage duration history + if: ${{ !cancelled() }} + uses: actions/cache/save@v6.1.0 + with: + path: .coverage-times.json + key: coverage-times-Linux-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_LINUX || 'enterprise' }}-${{ github.run_id }}-${{ github.run_attempt }} + node-24-bench: if: github.event_name == 'pull_request' runs-on: ubuntu-24.04 @@ -198,15 +211,15 @@ jobs: # benchmark lane alone instead of joining a concurrent gate aggregate. timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -218,7 +231,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} @@ -264,21 +277,20 @@ jobs: echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" echo "PLAYWRIGHT_BROWSERS_PATH=${RUNNER_TEMP%/*}/ms-playwright" >> "$GITHUB_ENV" - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false # Redirect the Node compile cache (enabled by pnpm and TypeScript) off - # the root partition's /tmp before the first pnpm call in this lane — - # see .agents/notes/implemented/process/2026-08-28-ci-node-compile-cache-data-disk.md. + # the root partition's /tmp before the first pnpm call in this lane. - name: Redirect Node compile cache to runner temp run: echo "NODE_COMPILE_CACHE=${{ runner.temp }}/node-compile-cache" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -295,7 +307,7 @@ jobs: # hosted cache keys, so each restore-keys fallback hits the matching archived # entry until it evicts, after which the store is cold. Self-hosted runners # reuse the shared persistent store instead of restoring the remote archive. - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -304,7 +316,7 @@ jobs: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- # Self-hosted runners retain their own browser cache between jobs. - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: vars.DSH_CI_FAILOVER_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]' with: path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} @@ -386,6 +398,7 @@ jobs: DSH_GATE_FAIL_FAST: '1' strategy: fail-fast: false + max-parallel: 1 matrix: include: - node: '22.19' @@ -404,7 +417,7 @@ jobs: runner: ubuntu-latest gate_concurrency: '1' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false @@ -416,11 +429,11 @@ jobs: echo "npm_config_devdir=$RUNNER_TEMP/node-gyp" >> "$GITHUB_ENV" echo "PNPM_CONFIG_STORE_DIR=$HOME/.local/share/pnpm/store" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 env: # The runner overwrites RUNNER_* step env before starting JavaScript actions. NODE_OPTIONS: ${{ runner.environment == 'self-hosted' && '--import=./scripts/ci-compatible-toolcache.mjs' || '' }} @@ -461,7 +474,7 @@ jobs: runs-on: ubuntu-latest name: python 3.10 / keyless SDK steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - uses: actions/setup-python@v6.3.0 with: @@ -471,7 +484,7 @@ jobs: - name: Install uv run: python -m pip install uv==0.11.23 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 @@ -486,6 +499,7 @@ jobs: # Linux ARM64 and both macOS architectures run in ci-master.yml. python-runtime: if: github.event_name == 'pull_request' + needs: python-sdk name: python runtime / release-shaped matrix uses: ./.github/workflows/build-exe-for-python-sdk.yml with: @@ -514,7 +528,7 @@ jobs: # A failing build or site aborts the sibling gate on the same runner. DSH_GATE_FAIL_FAST: '1' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - name: Enable Developer Mode (symlink support) @@ -522,10 +536,10 @@ jobs: run: >- reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - name: Install (immutable) @@ -582,7 +596,7 @@ jobs: # multi-minute instrumented run. DSH_GATE_FAIL_FAST: '1' steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false # Checkout removes the gitignored duration history, so restore it from @@ -592,24 +606,24 @@ jobs: # entries are immutable, so the save key is unique per run and the # restore matches the newest entry through the stable prefix. - name: Restore coverage duration history - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v6.1.0 with: path: .coverage-times.json - key: coverage-times-${{ github.run_id }} + key: coverage-times-Windows-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_WINDOWS || 'enterprise' }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | - coverage-times- + coverage-times-Windows-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_WINDOWS || 'enterprise' }}- - name: Enable Developer Mode (symlink support) shell: pwsh run: >- reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} # Node 24 still awaits the cppgc Realm-lifetime backport (#66168). # 26.10.0 contains nodejs/node#65778; an exact version prevents a # runner cache from selecting an older Node 26 with the same crash. - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: '26.10.0' - name: Install (immutable) @@ -638,10 +652,10 @@ jobs: # measured durations remain useful even when a partition failed. The # per-run key keeps every save a fresh immutable cache entry. if: ${{ !cancelled() }} - uses: actions/cache/save@v4 + uses: actions/cache/save@v6.1.0 with: path: .coverage-times.json - key: coverage-times-${{ github.run_id }} + key: coverage-times-Windows-${{ runner.environment }}-${{ vars.DSH_CI_FAILOVER_WINDOWS || 'enterprise' }}-${{ github.run_id }}-${{ github.run_attempt }} windows-native-tests: if: github.event_name == 'pull_request' @@ -654,7 +668,7 @@ jobs: name: windows node 24 / native tests timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - name: Enable Developer Mode (symlink support) @@ -662,10 +676,10 @@ jobs: run: >- reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - name: Install (immutable) @@ -689,7 +703,7 @@ jobs: --testTimeout 90000 packages/shell/tool-pwsh/tests/loader.spec.ts packages/workflow/workflow-ptc/tests/workflow-ptc.spec.ts - packages/workflow/tool-ralph/tests/integration.spec.ts + packages/experimental/tool-ralph/tests/integration.spec.ts packages/subprocess/subprocess-local/tests/process-exit.spec.ts # Single stable required check for branch protection: require "all checks diff --git a/.github/workflows/docs-pages.yml b/.github/workflows/docs-pages.yml index 6cd25b059d..6c6cc155d4 100644 --- a/.github/workflows/docs-pages.yml +++ b/.github/workflows/docs-pages.yml @@ -41,14 +41,14 @@ jobs: pages: read steps: # Complete history: the release scripts read tags. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} cache: pnpm diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8b3bfc1400..397ac07b45 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -67,11 +67,11 @@ jobs: # and retry 2; 45m still bounds retry storms against a slow API. timeout-minutes: 45 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm @@ -107,7 +107,7 @@ jobs: # the built artifact under plain Node, resolving plugins through real package # exports — the shape a real consumer runs. That requires a prior build. - name: Build (lib for the e2e example bins) - run: pnpm run build:official + run: ${{ github.event_name == 'pull_request' && 'pnpm run build --artifacts-only --profile official' || 'pnpm run build:official' }} - name: Prepare Office runtime run: pnpm run prepare:primary-runtime --target linux-x64 --output .cache/e2e-office diff --git a/.github/workflows/expected-filenames.yml b/.github/workflows/expected-filenames.yml index 99c9886e35..12ff5d7bc7 100644 --- a/.github/workflows/expected-filenames.yml +++ b/.github/workflows/expected-filenames.yml @@ -22,7 +22,7 @@ jobs: ${{ vars.DSH_CI_FAILOVER_LINUX == 'blacksmith' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - name: Check tracked filenames run: scripts/check-expected-filenames.sh diff --git a/.github/workflows/node-addon-system-release.yml b/.github/workflows/node-addon-system-release.yml index 6c5596232b..fef93c885b 100644 --- a/.github/workflows/node-addon-system-release.yml +++ b/.github/workflows/node-addon-system-release.yml @@ -32,7 +32,7 @@ jobs: outputs: prebuilds: ${{ steps.matrix.outputs.prebuilds }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - id: matrix run: echo "prebuilds=$(node ./scripts/github-matrix.mjs release-prebuild)" >> "$GITHUB_OUTPUT" @@ -45,13 +45,13 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.prebuilds) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: package_json_file: package.json - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm @@ -80,7 +80,7 @@ jobs: pnpm test:packaging - name: Upload prebuild artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7.0.1 with: name: ${{ matrix.artifact }} path: native/system/${{ matrix.dir }}/bin/** @@ -92,13 +92,13 @@ jobs: needs: build-prebuilds runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: package_json_file: package.json - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm @@ -116,7 +116,7 @@ jobs: RELEASE_PUBLISH: ${{ inputs.publish }} - name: Download prebuild artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8.0.1 with: pattern: prebuild-* path: native/system/.release/prebuild-artifacts @@ -138,7 +138,7 @@ jobs: NALR_REQUIRE_LANDLOCK: 1 - name: Upload npm tarballs - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7.0.1 with: name: npm-tarballs path: native/system/dist/npm/* @@ -157,17 +157,17 @@ jobs: steps: # The publish script is the only repository file this job needs, and it # imports nothing outside Node's builtins, so there is no install step. - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 registry-url: https://registry.npmjs.org - name: Download npm tarballs - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8.0.1 with: name: npm-tarballs path: native/system/dist/npm diff --git a/.github/workflows/node-addon-system.yml b/.github/workflows/node-addon-system.yml index a763a2a8d2..c50411aeba 100644 --- a/.github/workflows/node-addon-system.yml +++ b/.github/workflows/node-addon-system.yml @@ -48,7 +48,7 @@ jobs: outputs: ci: ${{ steps.matrix.outputs.ci }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - id: matrix run: echo "ci=$(node ./scripts/github-matrix.mjs ci)" >> "$GITHUB_OUTPUT" @@ -61,13 +61,15 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.matrix.outputs.ci) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + # pnpm v11 ships no darwin-x64 standalone binary, and the pnpm/action-setup + # v6 self-update path needs one; this job stays on the v5 npm-install line. + - uses: pnpm/action-setup@v5.0.0 with: package_json_file: package.json - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm @@ -125,7 +127,7 @@ jobs: node:24-alpine node --test ./test/flock.test.js ./test/package-matrix.test.js - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 20 @@ -139,7 +141,7 @@ jobs: node:20-alpine node --test ./test/flock.test.js ./test/package-matrix.test.js - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 22 @@ -153,7 +155,7 @@ jobs: node:22-alpine node --test ./test/flock.test.js ./test/package-matrix.test.js - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7.0.0 with: node-version: 26 diff --git a/.github/workflows/pi-ai-provider-e2e.yml b/.github/workflows/pi-ai-provider-e2e.yml index 255c7654e7..5a4774e705 100644 --- a/.github/workflows/pi-ai-provider-e2e.yml +++ b/.github/workflows/pi-ai-provider-e2e.yml @@ -30,11 +30,11 @@ jobs: name: Azure OpenAI Responses + Anthropic Messages timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index b756f5bf38..0474ccfe92 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -40,7 +40,7 @@ jobs: matrix: python: ['3.10', '3.14'] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false @@ -48,7 +48,7 @@ jobs: with: python-version: ${{ matrix.python }} - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@v8.0.1 with: pattern: deepseek_harness_* path: dist @@ -83,7 +83,7 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false @@ -134,7 +134,7 @@ jobs: } fi - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@v8.0.1 with: pattern: deepseek_harness_* path: dist @@ -175,7 +175,7 @@ jobs: sha256sum *.whl | sort -k2 > SHA256SUMS cat SHA256SUMS - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@v7.0.1 with: name: python-release-${{ steps.version.outputs.version }} path: dist/* @@ -193,7 +193,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@v8.0.1 with: name: python-release-${{ needs.validate.outputs.version }} path: dist @@ -228,7 +228,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@v8.0.1 with: name: python-release-${{ needs.validate.outputs.version }} path: dist diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index cdee5ce196..ec03a117de 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -21,16 +21,16 @@ jobs: runs-on: ubuntu-24.04 steps: # Complete history: the release scripts read tags. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -42,7 +42,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} @@ -82,7 +82,7 @@ jobs: - name: Verify packed install run: pnpm run release:verify-packed-install --family dsh --from dist/npm --from dist/npm-vendor --from dist/npm-landlock - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7.0.1 with: name: dsh-npm-tarballs path: dist/npm/* @@ -104,15 +104,15 @@ jobs: steps: # Checkout and install carry the release scripts only. There is no build # step: publication uploads the bytes the pack job produced. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} registry-url: https://registry.npmjs.org @@ -120,7 +120,7 @@ jobs: - name: Install (immutable, no package scripts) run: pnpm install --frozen-lockfile --ignore-scripts - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8.0.1 with: name: dsh-npm-tarballs path: dist/npm diff --git a/.github/workflows/release-vendor-publish.yml b/.github/workflows/release-vendor-publish.yml index a50a408025..25ec400b27 100644 --- a/.github/workflows/release-vendor-publish.yml +++ b/.github/workflows/release-vendor-publish.yml @@ -7,6 +7,12 @@ name: Release publish (vendor) on: workflow_dispatch: + inputs: + dist-tag: + description: Optional unused npm channel. Leave empty for latest (stable) or next (prerelease). + required: false + type: string + default: '' permissions: contents: read @@ -21,16 +27,16 @@ jobs: runs-on: ubuntu-24.04 steps: # Complete history: the release scripts read tags. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} @@ -42,7 +48,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 with: path: ${{ steps.pnpm-store.outputs.path }} key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} @@ -68,7 +74,7 @@ jobs: - name: Verify packed install run: pnpm run release:verify-packed-install --family vendor --from dist/npm-vendor - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7.0.1 with: name: vendor-npm-tarballs path: dist/npm-vendor/* @@ -87,15 +93,15 @@ jobs: contents: read steps: # Checkout and install carry the release scripts only; no build step. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} registry-url: https://registry.npmjs.org @@ -103,7 +109,7 @@ jobs: - name: Install (immutable, no package scripts) run: pnpm install --frozen-lockfile --ignore-scripts - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8.0.1 with: name: vendor-npm-tarballs path: dist/npm-vendor @@ -111,4 +117,10 @@ jobs: - name: Publish tarballs env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: pnpm run release:publish --family vendor --from dist/npm-vendor + RELEASE_DIST_TAG: ${{ inputs.dist-tag }} + run: | + args=() + if [[ -n "$RELEASE_DIST_TAG" ]]; then + args+=(--dist-tag "$RELEASE_DIST_TAG") + fi + pnpm run release:publish --family vendor --from dist/npm-vendor "${args[@]}" diff --git a/.github/workflows/release-vendor.yml b/.github/workflows/release-vendor.yml index 75fb865767..f8bf170c1c 100644 --- a/.github/workflows/release-vendor.yml +++ b/.github/workflows/release-vendor.yml @@ -43,7 +43,7 @@ jobs: || 'ubuntu-24.04' }} steps: # Complete history: the release scripts read tags. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -55,11 +55,11 @@ jobs: echo "npm_config_devdir=${{ runner.temp }}/node-gyp" >> "$GITHUB_ENV" echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} package-manager-cache: false @@ -72,7 +72,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: runner.environment == 'github-hosted' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -89,7 +89,7 @@ jobs: # The vendored packages publish their own sources and build outputs; the # host build produces what their manifests select. - name: Build - run: pnpm run build:lib:host + run: ${{ github.event_name == 'pull_request' && 'pnpm exec tsx scripts/compile-referenced-projects.ts host-libraries' || 'pnpm run build:lib:host' }} # Concurrency here is rehearsal-only: the credentialed publish workflows # invoke release:pack without the flag and keep the strictly serial path. @@ -99,7 +99,7 @@ jobs: - name: Verify packed install run: pnpm run release:verify-packed-install --family vendor --from dist/npm-vendor - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7.0.1 with: name: vendor-npm-tarballs path: dist/npm-vendor/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 633186c8f1..7fd8c9ab8a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') || 'ubuntu-24.04' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: persist-credentials: false clean: true @@ -52,11 +52,11 @@ jobs: echo "npm_config_devdir=${{ runner.temp }}/node-gyp" >> "$GITHUB_ENV" echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} package-manager-cache: false @@ -69,7 +69,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: runner.environment == 'github-hosted' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -102,7 +102,7 @@ jobs: || 'ubuntu-24.04' }} steps: # Complete history: the release scripts read tags. - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -114,11 +114,11 @@ jobs: echo "npm_config_devdir=${{ runner.temp }}/node-gyp" >> "$GITHUB_ENV" echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV" - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }} - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} package-manager-cache: false @@ -131,7 +131,7 @@ jobs: store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) echo "path=$store_path" >> "$GITHUB_OUTPUT" - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v6.1.0 if: runner.environment == 'github-hosted' with: path: ${{ steps.pnpm-store.outputs.path }} @@ -146,7 +146,7 @@ jobs: run: pnpm run release:verify --family dsh - name: Build - run: pnpm run build:official + run: ${{ github.event_name == 'pull_request' && 'pnpm run build --artifacts-only --profile official' || 'pnpm run build:official' }} # Concurrency here is rehearsal-only: the credentialed publish workflows # invoke release:pack without the flag and keep the strictly serial path. @@ -172,7 +172,7 @@ jobs: - name: Verify packed install run: pnpm run release:verify-packed-install --family dsh --from dist/npm --from dist/npm-vendor --from dist/npm-landlock - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7.0.1 with: name: dsh-npm-tarballs path: dist/npm/* diff --git a/.github/workflows/sandbox.yml b/.github/workflows/sandbox.yml index 46c6ac71f2..1494b8d1db 100644 --- a/.github/workflows/sandbox.yml +++ b/.github/workflows/sandbox.yml @@ -66,11 +66,11 @@ jobs: || matrix.os }} timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 @@ -146,11 +146,11 @@ jobs: runs-on: macos-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7.0.1 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5.0.0 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7.0.0 with: node-version: 24 diff --git a/.oxlintrc.json b/.oxlintrc.json index 348b057bc3..f52fdb1e84 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -23,6 +23,7 @@ "packages/typert/generator/tests/fixtures/type-model/**", // tsgolint rejects this fixture's preserved project shapes before rules run. "website/.generated/**", "vendor/**", // Vendored source keeps upstream style and idioms. + "packages/experimental/claude-code-mods/examples/*/{hooks,tests,types}/**", // Claude Code's published example mods and their tests keep the upstream style; see the headers. "native/**", // The imported landlock-run subtree has its own gates; see native/README.md. "**/*.js", "**/*.mjs", diff --git a/AGENTS.md b/AGENTS.md index fc94452a0f..38e5ae46cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,6 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// jobs/ background jobs bundle/ profile bundles workflow/ workflow execution - webhook/ webhook ingress todo/ todo_write tool plan/ logged planning goal/ session goals @@ -48,7 +47,6 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// preset/ agent composition guard/ loop/tool guards extensions/ runtime self-modification - hooks/ Claude Code/Codex bridges session/ durable sessions session-query/ browsing/search/export attachment/ binary attachments @@ -66,9 +64,8 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// host/ GUI host client/ GUI client mcp/ external tools - experimental/ pre-stable prototypes; public by default with explicit private exceptions + experimental/ evaluated capabilities without a product-support commitment test-support/ test infrastructure - runtime-diagnostics/ runtime invariants util/ zero-dependency utilities python/ Python SDK/runtime (python/README.md) native/ @deepseek-ai/node-addon-system source (native/README.md) @@ -79,7 +76,7 @@ scripts/ gates and generators website/ VitePress documentation projection ``` -Package groups: [packages/README.md](packages/README.md). +Package groups: [packages/README.md](packages/README.md). [Experimental status](packages/experimental/README.md#status) is independent of optional delivery; nonexperimental packages need product use or an explicit [classification](scripts/product-package-policy.ts). ## Commands @@ -128,11 +125,10 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and ## Conventions -- Packages use `@deepseek-ai/dsh-`; vendor is [rescoped](docs/rescope.md) and `private: true`. Harness packages declare `@deepseek-ai/cordis` in `peerDependencies`/`devDependencies`. Workspace dependency sections use DSH `workspace:*`, vendor/native `workspace:~` ([rules](.agents/notes/implemented/process/2026-09-22-workspace-release-ranges.md)). +- Packages use `@deepseek-ai/dsh-`; vendor is [rescoped](docs/rescope.md) and `private: true`. Harness packages declare `@deepseek-ai/cordis` in `peerDependencies`/`devDependencies`. Workspace dependency sections use DSH `workspace:*`, vendor/native `workspace:~` ([rules](.agents/notes/implemented/process/2026-08-10-npm-release-sequences.md)). - ESM everywhere (`"type": "module"`). Use package names across packages and `.ts` in local relative imports. Config subprocesses run built `lib/` under plain Node; source regressions use their declared launcher ([testing policy](docs/testing.md#test-subprocess-launch-modes)). The `dsh` CLI source launch runs through tsx's ESM-only hook (`node --import tsx/esm`); modules it reaches must stay ESM (no CJS-only exports) — Node's native TypeScript modes are unavailable across the engines range ([source-launch contract](.agents/notes/implemented/architecture/2026-07-29-dsh-source-launch-tsx-esm.md)). Raw/Web `cordis.yml` bare plugins must appear in their resolver manifest's `dependencies`; `verify-cordis-config` enforces it. - **Registrations are effects**: every contribution goes through `ctx.effect()` / `ctx.on()`; a registry's `register()` returns the disposer. -- **Runtime invariants assert owned relationships.** Publish `./invariant` only when independent observations can diverge. Otherwise omit its source and wiring and record why in its README; empty installers and checks of service presence, plugin metadata, effects, or fixed examples are invalid ([package invariant rules](packages/AGENTS.md)). -- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`; scoped keys absent from payloads need `@dshScopeScan unsupported`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read by default — builds that do not know a type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)). +- **Typed events use declaration merging** and merge-extensible maps. Event JSDoc needs `@mode` and payload `@param`. Public service methods document parameters and non-void returns. `SessionEventMap` members are required-on-read by default — builds that do not know a type refuse the log unless the event carries the envelope's `ignorable: true`; only structural format changes bump `SESSION_FORMAT_VERSION` ([mechanism](.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)). - **Switch on discriminant tags.** Closed unions end in `assertNever`; merge-extensible unions fall through a documented default. - **Waterfall listeners MUST call `next()`** to delegate; returning without it short-circuits the chain ([semantics](docs/cordis-primer.md#cordis-waterfall-semantics)). - **Model-visible ⟺ logged**: anything that reaches a model request must be reconstructable from the session log; a new model-visible input requires a session event. @@ -148,7 +144,7 @@ Real-API tests/demos read `DEEPSEEK_API_KEY`, optional `DEEPSEEK_BASE_URL`, and - **Source plane vs artifact plane, never mixed.** Static gates and tests resolve workspace imports through tsconfig `paths` to `src` and pass on a clean tree; gates consuming built `lib/` declare that dependency ([layout](docs/development.md#typescript-project-layout)). - **Keep compiler faces explicit.** A package with both Host and Client programs exposes face-specific leaf configs and a solution-only root; repo-wide programs seed a face config, never the root solution ([layout](docs/development.md#typescript-project-layout)). - **An empty `catch` names the error** and why; keep its `try` to one statement. -- **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). +- **Keep comments local.** Do not restate code, expand unrelated comments, or explain distant behavior without local need ([rationale](.agents/skills/dsh-prose-standard/SKILL.md)). - **Ban `prove` + `nance`** ([rule](.agents/notes/implemented/process/2026-08-26-ban-ambiguous-origin-label.md)). - **Prefer symmetry for parallel values**; unexplained asymmetry usually signals a missed extraction. - **Tests describe behavior, not correctness.** Change obsolete behavior with its tests; explain why in the PR. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b863bd7258..741c98db08 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -36,7 +36,7 @@ External packages installed for runtime use or distributed inside the prebuilt b | [`@anthropic-ai/sdk`](https://github.com/anthropics/anthropic-sdk-typescript) | MIT | | [`@babel/code-frame`](https://github.com/babel/babel) | MIT | | [`@browserbasehq/stagehand`](https://github.com/browserbase/stagehand) | MIT | -| [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit) | MPL-2.0 | +| [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-ai/dsh-libreoffice-kit) | MPL-2.0 | | [`@earendil-works/pi-ai`](https://github.com/earendil-works/pi) | MIT | | [`@eslint-community/regexpp`](https://github.com/eslint-community/regexpp) | MIT | | [`@fortune-sheet/core`](https://github.com/ruilisi/fortune-sheet) | MIT | @@ -78,6 +78,7 @@ External packages installed for runtime use or distributed inside the prebuilt b | [`big.js`](https://github.com/MikeMcl/big.js) | MIT | | [`buffer`](https://github.com/feross/buffer) | MIT | | [`chokidar`](https://github.com/paulmillr/chokidar) | MIT | +| [`chrome-devtools-frontend`](https://github.com/ChromeDevTools/devtools-frontend) | BSD-3-Clause | | [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp) | Apache-2.0 | | [`clsx`](https://github.com/lukeed/clsx) | MIT | | [`commander`](https://github.com/tj/commander.js) | MIT | @@ -121,6 +122,7 @@ External packages installed for runtime use or distributed inside the prebuilt b | [`readable-stream`](https://github.com/nodejs/readable-stream) | MIT | | [`resolve.exports`](https://github.com/lukeed/resolve.exports) | MIT | | [`semver`](https://github.com/npm/node-semver) | ISC | +| [`serve-static`](https://github.com/expressjs/serve-static) | MIT | | [`sharp`](https://github.com/lovell/sharp) | Apache-2.0 | | [`sherpa-onnx-node`](https://github.com/csukuangfj/sherpa-onnx) | Apache-2.0 | | [`shiki`](https://github.com/shikijs/shiki) | MIT | @@ -139,7 +141,7 @@ External packages installed for runtime use or distributed inside the prebuilt b pnpm applies local patches to the following packages at install time, so shipped artifacts carry modified copies; each patch file is the complete record of the modification: -- `@earendil-works/pi-ai@0.87.1` — [`patches/@earendil-works__pi-ai@0.87.1.patch`](patches/@earendil-works__pi-ai@0.87.1.patch) +- `@earendil-works/pi-ai@1.0.2` — [`patches/@earendil-works__pi-ai@1.0.2.patch`](patches/@earendil-works__pi-ai@1.0.2.patch) - `@electron/osx-sign@1.3.3` — [`patches/@electron__osx-sign@1.3.3.patch`](patches/@electron__osx-sign@1.3.3.patch) - `@fortune-sheet/core@1.0.4` — [`patches/@fortune-sheet__core@1.0.4.patch`](patches/@fortune-sheet__core@1.0.4.patch) - `@fortune-sheet/react@1.0.4` — [`patches/@fortune-sheet__react@1.0.4.patch`](patches/@fortune-sheet__react@1.0.4.patch) @@ -147,6 +149,11 @@ pnpm applies local patches to the following packages at install time, so shipped - `exceljs@4.4.0` — [`patches/exceljs@4.4.0.patch`](patches/exceljs@4.4.0.patch) - `node-pty@1.2.0-beta.15` — [`patches/node-pty@1.2.0-beta.15.patch`](patches/node-pty@1.2.0-beta.15.patch) +## Chrome DevTools frontend + +The optional experimental Inspector distributes a locally compiled copy of [chrome-devtools-frontend 1.0.1638082](https://www.npmjs.com/package/chrome-devtools-frontend/v/1.0.1638082), from upstream revision [0e1186138ed519d9659c1874bf6375eca4483c72](https://chromium.googlesource.com/devtools/devtools-frontend/+/0e1186138ed519d9659c1874bf6375eca4483c72). The build includes the Chromium [BSD-3-Clause license](packages/experimental/inspector/assets/devtools/LICENSE) and the third-party license and notice files supplied by the npm source. The Chromium root license does not replace those dependencies' licenses. + + ## Official Claude Code platform payloads The project owner authorizes distribution of every version of the official `@anthropic-ai/claude-agent-sdk` package and the official Claude Code CLI/platform payloads that each version declares through `optionalDependencies`. This identity-scoped authorization does not classify their declared terms as permissive and does not cover any unrelated runtime package; version, declared-license, and payload-set changes still require the ordinary dependency, lockfile, compatibility, terms, and notices review. @@ -169,7 +176,7 @@ The installed SDK 0.3.263 declares the following optional platform packages. Eac `@deepseek-ai/libreoffice-kit`, `@deepseek-ai/libreoffice-kit-wasm`, `@deepseek-ai/libreoffice-kit-darwin-arm64`, `@deepseek-ai/libreoffice-kit-darwin-x64`, `@deepseek-ai/libreoffice-kit-win32-arm64`, `@deepseek-ai/libreoffice-kit-win32-x64` declare MPL-2.0, which remains outside the permissive-license allowlist; the notices check accepts only these package identities at those terms. The [distribution decision](.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md) records the source obligations. -The [kit repository](https://github.com/deepseek-harness/libreoffice-kit) supplies the corresponding LibreOffice source pin, modifications, build instructions, Node API, and artifact validation. Its engine packages retain their license and third-party notices; the Node API retains its MPL-2.0 declaration and NOTICE. Recipients must have access to those corresponding sources and notices. +The [kit repository](https://github.com/deepseek-ai/dsh-libreoffice-kit) supplies the corresponding LibreOffice source pin, modifications, build instructions, Node API, and artifact validation. Its engine packages retain their license and third-party notices; the Node API retains its MPL-2.0 declaration and NOTICE. Recipients must have access to those corresponding sources and notices. ## Development-only npm dependencies @@ -205,6 +212,7 @@ External packages **directly declared** for development, tests, types, or toolin | [`@types/react-dom`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | | [`@types/readable-stream`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | | [`@types/semver`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | +| [`@types/serve-static`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | | [`@types/spdx-expression-parse`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | | [`@types/turndown`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | | [`@types/use-sync-external-store`](https://github.com/DefinitelyTyped/DefinitelyTyped) | MIT | diff --git a/apps/cli/composition.md b/apps/cli/composition.md index e627d9e47d..9888602296 100644 --- a/apps/cli/composition.md +++ b/apps/cli/composition.md @@ -64,6 +64,10 @@ flowchart LR cfg --> plugin_dsh_base_attachment_local plugin_dsh_base_session_query_sqlite["session-query-sqlite
@deepseek-ai/dsh-session-query-sqlite"] cfg --> plugin_dsh_base_session_query_sqlite + plugin_dsh_base_working_directory["working-directory
@deepseek-ai/dsh-working-directory"] + cfg --> plugin_dsh_base_working_directory + plugin_dsh_base_tool_working_directory["tool-working-directory
@deepseek-ai/dsh-tool-working-directory"] + cfg --> plugin_dsh_base_tool_working_directory plugin_dsh_base_session_projection["session-projection
@deepseek-ai/dsh-session-projection"] cfg --> plugin_dsh_base_session_projection plugin_dsh_base_storage["storage
@deepseek-ai/dsh-storage"] @@ -112,8 +116,6 @@ flowchart LR cfg --> plugin_dsh_base_skill plugin_dsh_base_skill_filesystem["skill-filesystem
@deepseek-ai/dsh-skill-filesystem"] cfg --> plugin_dsh_base_skill_filesystem - plugin_dsh_base_skill_badge["skill-badge
@deepseek-ai/dsh-skill-badge"] - cfg --> plugin_dsh_base_skill_badge plugin_dsh_base_tool_skill["tool-skill
@deepseek-ai/dsh-tool-skill"] cfg --> plugin_dsh_base_tool_skill plugin_dsh_base_commands["commands
@deepseek-ai/dsh-commands"] @@ -170,8 +172,6 @@ flowchart LR cfg --> plugin_dsh_base_tool_todo plugin_dsh_base_tool_goal["tool-goal
@deepseek-ai/dsh-tool-goal"] cfg --> plugin_dsh_base_tool_goal - plugin_dsh_base_tool_ralph["tool-ralph
@deepseek-ai/dsh-tool-ralph"] - cfg --> plugin_dsh_base_tool_ralph plugin_dsh_base_repeat_tool_reminder["repeat-tool-reminder
@deepseek-ai/dsh-repeat-tool-reminder"] cfg --> plugin_dsh_base_repeat_tool_reminder plugin_dsh_base_web["web
@deepseek-ai/dsh-web"] @@ -228,6 +228,8 @@ flowchart LR | `session-persistence-jsonl` | `@deepseek-ai/dsh-session-persistence-jsonl` | | `attachment-local` | `@deepseek-ai/dsh-attachment-local` | | `session-query-sqlite` | `@deepseek-ai/dsh-session-query-sqlite` | +| `working-directory` | `@deepseek-ai/dsh-working-directory` | +| `tool-working-directory` | `@deepseek-ai/dsh-tool-working-directory` | | `session-projection` | `@deepseek-ai/dsh-session-projection` | | `storage` | `@deepseek-ai/dsh-storage` | | `storage-json` | `@deepseek-ai/dsh-storage-json` | @@ -252,7 +254,6 @@ flowchart LR | `agent-instructions` | `@deepseek-ai/dsh-agent-instructions` | | `skill` | `@deepseek-ai/dsh-skill` | | `skill-filesystem` | `@deepseek-ai/dsh-skill-filesystem` | -| `skill-badge` | `@deepseek-ai/dsh-skill-badge` | | `tool-skill` | `@deepseek-ai/dsh-tool-skill` | | `commands` | `@deepseek-ai/dsh-commands` | | `command-feedback` | `@deepseek-ai/dsh-command-feedback` | @@ -281,7 +282,6 @@ flowchart LR | `image-offload` | `@deepseek-ai/dsh-compaction-image-offload` | | `tool-todo` | `@deepseek-ai/dsh-tool-todo` | | `tool-goal` | `@deepseek-ai/dsh-tool-goal` | -| `tool-ralph` | `@deepseek-ai/dsh-tool-ralph` | | `repeat-tool-reminder` | `@deepseek-ai/dsh-repeat-tool-reminder` | | `web` | `@deepseek-ai/dsh-web` | | `web-search-deepseek` | `@deepseek-ai/dsh-web-search-deepseek` | diff --git a/apps/cli/package.json b/apps/cli/package.json index 81494aea52..3da15fcad8 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh", "description": "dsh CLI: profile launch, plugin management, and configuration inspection", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -39,13 +39,12 @@ "@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:*", "@deepseek-ai/dsh-cordis-client-runner": "workspace:*", "@deepseek-ai/dsh-experimental-agent-team-profile": "workspace:*", + "@deepseek-ai/dsh-experimental-inspector-profile": "workspace:*", "@deepseek-ai/dsh-fs-local": "workspace:*", "@deepseek-ai/dsh-goal": "workspace:*", "@deepseek-ai/dsh-goal-round-driver": "workspace:*", "@deepseek-ai/dsh-headless": "workspace:*", "@deepseek-ai/dsh-home-paths": "workspace:*", - "@deepseek-ai/dsh-hooks-claude-code": "workspace:*", - "@deepseek-ai/dsh-hooks-codex": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-launch-environment": "workspace:*", "@deepseek-ai/dsh-mcp-client": "workspace:*", @@ -78,7 +77,6 @@ "@deepseek-ai/dsh-tool-jobs": "workspace:*", "@deepseek-ai/dsh-tool-pwsh": "workspace:*", "@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:*", - "@deepseek-ai/dsh-tool-ralph": "workspace:*", "@deepseek-ai/dsh-tool-schedule": "workspace:*", "@deepseek-ai/dsh-tool-skill": "workspace:*", "@deepseek-ai/dsh-tool-str-replace-editor": "workspace:*", @@ -88,12 +86,10 @@ "@deepseek-ai/dsh-tool-web": "workspace:*", "@deepseek-ai/dsh-tool-workflow": "workspace:*", "@deepseek-ai/dsh-web-app": "workspace:*", - "@deepseek-ai/dsh-webhook": "workspace:*", - "@deepseek-ai/dsh-webhook-github": "workspace:*", "@deepseek-ai/dsh-workflow-ptc": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", "commander": "^15.0.0", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.2", "node-addon-require-builtin": "^0.1.6", "@deepseek-ai/dsh-http-proxy": "workspace:*", "@deepseek-ai/dsh-mcp-resources": "workspace:*", @@ -101,9 +97,22 @@ "@deepseek-ai/dsh-atomic-write": "workspace:*", "@deepseek-ai/dsh-agent-preset": "workspace:*", "@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:*", - "@deepseek-ai/dsh-experimental-auto-review": "workspace:*" + "@deepseek-ai/dsh-experimental-auto-review": "workspace:*", + "@deepseek-ai/dsh-experimental-cot-translation-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-badge-skill-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-ralph-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-session-titles-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-terminal-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-session-search": "workspace:*", + "@deepseek-ai/dsh-experimental-tool-worktree": "workspace:*" }, "devDependencies": { + "@deepseek-ai/dsh-skill-badge": "workspace:*", + "@deepseek-ai/dsh-webhook-github": "workspace:*", + "@deepseek-ai/dsh-webhook": "workspace:*", + "@deepseek-ai/dsh-tool-ralph": "workspace:*", + "@deepseek-ai/dsh-hooks-codex": "workspace:*", + "@deepseek-ai/dsh-hooks-claude-code": "workspace:*", "@agentclientprotocol/sdk": "1.4.0", "@deepseek-ai/dsh-acp": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", @@ -149,8 +158,8 @@ "@types/js-yaml": "^4.0.9", "@types/ws": "8.18.1", "ajv": "^8.20.0", - "execa": "^10.0.0", - "ws": "8.21.0" + "execa": "^10.0.1", + "ws": "8.21.3" }, "exports": { "./profile-boot": { diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index 7019d0bc1e..da6e0863ff 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md /dsh-cli-behavior-reference: - en: d25712a65d21ab45 - zh: b1c57b706bc64085 + en: 5a4f43756dae900e + zh: d58c479481cfa5fe /dsh-cli-behavior-reference/profile-boot: en: e3a159750f5f9ed3 zh: e02d9004fcc239f9 /dsh-cli-behavior-reference/profile-boot/app-arguments: - en: 241e5525aa09a33b - zh: 398f853143097154 + en: be06f11a3b585d0f + zh: 28c7ee6e0d86dc3b /dsh-cli-behavior-reference/profile-boot/config-schema-dump: en: a356f8a68f3b3685 zh: c718713b5d709295 @@ -18,11 +18,11 @@ en: d6368bf97a02bd57 zh: 2ab7888a108961ce /dsh-cli-behavior-reference/plugin-management: - en: 9980ff1600fa9460 - zh: cc66ba45e27b5540 + en: 8d2cfff9bab7327f + zh: 21515ab6e6798504 /dsh-cli-behavior-reference/web-profile: - en: 12bba432c07573bf - zh: ab6b365cfb95003f + en: 536c6883a0d54b14 + zh: 6dd2469b71189c87 /dsh-cli-behavior-reference/shared-deployment-behavior: en: 6a7abc9339e8f37f zh: 133adb834c8e195d diff --git a/apps/cli/reference/README.md b/apps/cli/reference/README.md index ae492f22c8..e8105bab78 100644 --- a/apps/cli/reference/README.md +++ b/apps/cli/reference/README.md @@ -4,6 +4,8 @@ English | [中文](README.zh.md) This reference defines the profile, plugin-management, and config-dump command modes. Argv is parsed once through [`src/args.ts`](../src/args.ts), and [`src/bin.ts`](../src/bin.ts) dynamically imports only the selected runner. +The [generated command help](../../../docs/cli-help.md) contains complete launcher, plugin-management, and shipped-profile help. Run `pnpm run gen-cli-help` to refresh it; `pnpm run verify-cli-help` checks it against the current CLI. + ## Profile boot `dsh ` abbreviates `dsh --profile ` and boots the profile at `$DSH_HOME/profiles/`. The shorthand name must immediately follow `dsh`; `plugin` remains the plugin-management command, so boot a profile with that name using `dsh --profile plugin`. The effective tree is composed over an empty root by applying, in order: each bundle patch named in the profile manifest's `dsh.profile.bundles` list, the profile's own `cordis.patch.yml`, the home-level `$DSH_HOME/cordis.patch.yml` (machine-local preferences shared by every profile, so it outranks the per-profile layer), and each `--patch ` overlay in argv order. Later layers win per row; a patch replaces the targeted row's complete `config` value rather than deep-merging keys, and may insert new rows. The final YAML composition controls whether `dsh-hmr` watches configuration; without HMR, changes require restart. A parse, schema, resolution, or plugin boot failure is reported and exits nonzero. SIGINT and SIGTERM dispose the mounted root before exit. @@ -33,7 +35,7 @@ The shipped apps own these command lines: | Profile | Arguments | |---|---| -| `web` | `--host`, `--port`, repeatable `--trusted-host`, `--no-open` | +| `web` | `--host`, `--port`, `--public-url`, repeatable `--trusted-host`, `--no-open` | | `headless` | the task text, as the positional argument | | `sdk` | no options; stdio carries the JSON-RPC protocol | | `sdk-minimal` | no options; stdio carries the same JSON-RPC protocol | @@ -80,6 +82,8 @@ New directories and files request modes `0700` and `0600` on POSIX. The CLI prin `dsh plugin --profile ` initializes the profile when missing (shipped template, or `@deepseek-ai/dsh-base` alone for other names), then forwards `` to `pnpm` with the profile directory as working directory — `add`, `remove`, `why`, `update`, and every other pnpm verb work unchanged; pnpm must be on PATH. Relative path specs (`.`, `../plugin`, and their `file:`/`link:` forms) are anchored to the invoking directory first, so `add .` from a plugin checkout installs that checkout, not the profile. After every successful run, `dsh.profile.bundles` is reconciled against the installed state: each dependency resolving to a package whose manifest declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` joins the layer stack (so an `update` that gains the declaration activates it), a bundle-less dependency stays plain with a one-time warning, and a removed dependency leaves the stack. +`dsh plugin` reads `--profile` only before the forwarded arguments, which start at the first token it does not recognize, so `--profile ` comes first. When the forwarded arguments begin with the pnpm command, everything after that command reaches pnpm verbatim, including `-h` and `--help`. When they begin with a pnpm option such as `--filter ` or `-r`, a later `-h` or `--help` still prints the forwarder's help instead of pnpm's, so put the pnpm command before them to reach pnpm's help. `-h` or `--help` before any forwarded argument prints the forwarder's own help and exits 0. + The Codex and Claude Code subagent providers are separate optional Bundles. Add either package, both in one command, or remove either package independently: ```sh @@ -104,6 +108,8 @@ Git-hosted plugins that ship sources build during install through their `prepare `dsh web` uses the profile shorthand. Launcher flags are parsed first; the remaining flags belong to the web app, whose ordinary bundle provider parses them. `--host` and `--port` override the composed values of the rows that carry them, repeatable `--trusted-host` contributes invocation authorities through `ctx.webRuntime.trustedHosts` (a deployment expression concatenates its own authorities), and `--no-open` disables the default-browser handoff for this invocation. The client-plugin HMR receiver is always mounted and stays idle until `pnpm run dev:web` rebuilds client bundles; that command builds once, starts this same launcher, and keeps client bundles rebuilt, or with `--no-serve` runs only the watchers beside a `dsh web` started elsewhere. +`--public-url ` advertises one HTTP(S) application root — with an optional forwarding prefix — in place of the listener's loopback URL. It grants no trust, so the browser-visible authority still has to be named with `--trusted-host`; [Publish the Web UI behind a reverse proxy](../../../docs/user/guide/public-deployments.md) lists what the proxy in front must provide. + ```sh dsh web dsh web --no-open @@ -112,7 +118,7 @@ dsh web --dump-config dsh web --help ``` -The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default and, for a local launch, opens that canonical host URL only after the complete Loader tree settles. A non-empty inherited `SSH_CONNECTION` or `SSH_TTY` suppresses the browser handoff because the SSH client or editor owns the local forwarded address; the host URL is still printed. The CLI intentionally does not support `--host 0.0.0.0` and exits with a usage error. Immediately before a local handoff it prints `dsh web: opening the default browser; pass --no-open to disable`; if the operating-system handoff fails, a diagnostic on stderr states the reason, leaves the server running, and names the URL for manual use. `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence. +The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default and, for a local launch, opens that canonical host URL — the advertised root when `--public-url` names one — only after the complete Loader tree settles. A non-empty inherited `SSH_CONNECTION` or `SSH_TTY` suppresses the browser handoff because the SSH client or editor owns the local forwarded address; the host URL is still printed. The CLI intentionally does not support `--host 0.0.0.0` and exits with a usage error. Immediately before a local handoff it prints `dsh web: opening the default browser; pass --no-open to disable`; if the operating-system handoff fails, a diagnostic on stderr states the reason, leaves the server running, and names the URL for manual use. `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence. Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain — `SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed. @@ -120,7 +126,7 @@ The base-backed modes treat the invoking directory as the default workspace root New sessions in base-backed profiles default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads and network access are not confined, while process visibility depends on the selected sandbox backend — bwrap runs commands in a private PID namespace that hides host processes, and Landlock and Seatbelt leave host process visibility unchanged. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one. The standalone `sdk-minimal` tree instead pins `danger-full-access` and mounts no approval or permission-settings service. -`DSH_TOOLS_MODE` selects `native`, `ptc`, or `both` for the process; another value fails at boot. The shipped `minimal` agent preset keeps that deployment presentation, fixes the complete system prompt to `You are a helpful software engineer assistant.`, and composes only the platform-selected persistent shell. Select 极简模式 when creating a Web session; every other prompt section and model-facing plugin remains absent from that agent while the shared browser, workspace, persistence, sandbox, and permission host stays in place. +`DSH_TOOLS_MODE` selects `native` or `ptc` for the process; another value fails at boot. The shipped `minimal` agent preset keeps that deployment presentation, fixes the complete system prompt to `You are a helpful software engineer assistant.`, and composes only the platform-selected persistent shell. Select 极简模式 when creating a Web session; every other prompt section and model-facing plugin remains absent from that agent while the shared browser, workspace, persistence, sandbox, and permission host stays in place. ## Shared deployment behavior diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index e1262f1410..2471de196e 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -4,6 +4,8 @@ 本参考定义 profile 启动、插件管理和配置 dump 等命令模式。argv 由 [`src/args.ts`](../src/args.ts) 统一解析一次,[`src/bin.ts`](../src/bin.ts) 只会动态导入选中的运行器。 +[生成的命令帮助](../../../docs/cli-help.zh.md)包含启动器、插件管理和随附 profile 的完整帮助。运行 `pnpm run gen-cli-help` 刷新,运行 `pnpm run verify-cli-help` 检查它是否与当前 CLI 一致。 + ## Profile 启动 @@ -35,7 +37,7 @@ dsh rescue | Profile | 参数 | |---|---| -| `web` | `--host`、`--port`、可重复的 `--trusted-host`、`--no-open` | +| `web` | `--host`、`--port`、`--public-url`、可重复的 `--trusted-host`、`--no-open` | | `headless` | 任务文本,作为位置参数 | | `sdk` | 无选项;stdio 携带 JSON-RPC 协议 | | `sdk-minimal` | 无选项;stdio 携带相同的 JSON-RPC 协议 | @@ -82,6 +84,8 @@ dsh --profile web --patch ./extra.yml --dump-config `dsh plugin --profile ` 在 profile 缺失时先初始化它(有随附模板的用模板,其他名称只装 `@deepseek-ai/dsh-base`),然后以 profile 目录为工作目录,把 `` 转发给 `pnpm`:`add`、`remove`、`why`、`update` 及其他所有 pnpm 子命令都照常可用;pnpm 必须在 PATH 上。相对路径 spec(`.`、`../plugin` 及其 `file:`/`link:` 形式)会先锚定到调用目录,因此在插件 checkout 中执行 `add .` 安装的是该 checkout,而不是 profile。每次成功运行后,系统都会根据当前安装状态更新 `dsh.profile.bundles`:如果某项依赖解析到的包在 manifest 中声明了 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`,该依赖就会加入配置层栈;如果某项依赖在 `update` 后获得该声明,也会随即激活。没有组合包声明的依赖仍作为普通依赖保留,并显示一次性警告;已移除的依赖则从配置层栈中删除。 +`dsh plugin` 只在被转发的参数之前读取 `--profile`;被转发的参数从它无法识别的第一个 token 开始,因此 `--profile ` 必须写在最前面。被转发的参数以 pnpm 命令开头时,该命令之后的所有内容都会原样交给 pnpm,包括 `-h` 和 `--help`。被转发的参数以 `--filter ` 或 `-r` 等 pnpm 选项开头时,后面的 `-h` 或 `--help` 仍会打印转发器的帮助而不是 pnpm 的帮助,因此要查看 pnpm 的帮助,需把 pnpm 命令写在它们之前。在任何被转发的参数之前出现的 `-h` 或 `--help` 会打印转发器自身的帮助并以 0 退出。 + Codex 与 Claude Code subagent 提供方是两个彼此独立的可选组合包。可以只添加一个包、在同一命令中添加两个包,或独立移除任一包: ```sh @@ -106,6 +110,8 @@ dsh --profile tui `dsh web` 使用 profile 简写。启动器先解析自身的 flag,其余 flag 属于 web 应用,由组合包中的普通提供方解析。`--host` 和 `--port` 覆盖承载它们的那些行的组合取值,可重复的 `--trusted-host` 通过 `ctx.webRuntime.trustedHosts` 提供本次调用的 authority(部署表达式会拼接自己的 authority),`--no-open` 则只对本次调用关闭默认浏览器交接。客户端插件 HMR(热模块替换)接收器始终挂载,在 `pnpm run dev:web` 重建客户端 bundle 之前保持空闲;该命令先构建一次,再启动这同一个启动器并持续重建客户端 bundle,加 `--no-serve` 则只运行 watcher、配合别处启动的 `dsh web`。 +`--public-url ` 公告唯一的 HTTP(S) 应用根——可带转发前缀——替代监听器的 loopback URL。它不授予信任,因此浏览器可见的 authority 仍需用 `--trusted-host` 点名;[在反向代理之后发布 Web UI](../../../docs/user/guide/public-deployments.zh.md)列出了前置代理必须提供的内容。 + ```sh dsh web dsh web --no-open @@ -114,7 +120,7 @@ dsh web --dump-config dsh web --help ``` -生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`;本机启动时,只在完整 Loader 配置树结算后才用默认浏览器打开该规范宿主机 URL。继承的 `SSH_CONNECTION` 或 `SSH_TTY` 非空时会跳过浏览器交接,因为本地转发地址由 SSH 客户端或编辑器持有;宿主机 URL 仍会打印。CLI 有意不支持 `--host 0.0.0.0`,并会以用法错误退出。本机交接前会打印英文提示 `dsh web: opening the default browser; pass --no-open to disable`;若操作系统交接失败,stderr 诊断会说明原因、给出 URL 供手动访问,服务器仍继续运行。`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。 +生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`;本机启动时,只在完整 Loader 配置树结算后才用默认浏览器打开该规范宿主机 URL(配置了 `--public-url` 时即为公告根)。继承的 `SSH_CONNECTION` 或 `SSH_TTY` 非空时会跳过浏览器交接,因为本地转发地址由 SSH 客户端或编辑器持有;宿主机 URL 仍会打印。CLI 有意不支持 `--host 0.0.0.0`,并会以用法错误退出。本机交接前会打印英文提示 `dsh web: opening the default browser; pass --no-open to disable`;若操作系统交接失败,stderr 诊断会说明原因、给出 URL 供手动访问,服务器仍继续运行。`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。 进程关闭时,插件树最多有 5 秒完成 dispose。首次收到 `SIGINT` 或 `SIGTERM` 时会开始优雅排空:`SIGTERM` 是监督进程发出的常规停止请求,在所有运行模式下都以 0 退出;`SIGINT` 则报告 130。第二次收到信号时会立即强制退出。如果一次性运行在正常结束时已经卡在 dispose 阶段,第一次按下 `Ctrl+C` 就会直接升级为强制退出,而不会被忽略。 @@ -122,7 +128,7 @@ dsh web --help 基于 base 的 profile 中,新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取和网络访问不受限制,进程可见性则取决于所选沙箱后端——bwrap 在私有 PID 命名空间中运行命令并隐藏宿主进程,Landlock 与 Seatbelt 保持宿主进程可见性不变。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。独立的 `sdk-minimal` 配置树则固定为 `danger-full-access`,且不挂载 approval 或权限 settings 服务。 -`DSH_TOOLS_MODE` 为进程选择 `native`、`ptc` 或 `both`;其他值会导致启动失败。随附的 `minimal` agent preset 会保留该部署的呈现方式,将完整系统提示词固定为 `You are a helpful software engineer assistant.`,并且仅组合按平台选择的持久 shell。创建 Web 会话时请选择极简模式;该 agent 不包含任何其他提示词段落或面向模型的插件,而共享的浏览器、workspace、持久化、沙箱与权限宿主保持不变。 +`DSH_TOOLS_MODE` 为进程选择 `native` 或 `ptc`;其他值会导致启动失败。随附的 `minimal` agent preset 会保留该部署的呈现方式,将完整系统提示词固定为 `You are a helpful software engineer assistant.`,并且仅组合按平台选择的持久 shell。创建 Web 会话时请选择极简模式;该 agent 不包含任何其他提示词段落或面向模型的插件,而共享的浏览器、workspace、持久化、沙箱与权限宿主保持不变。 ## 共享部署行为 diff --git a/apps/cli/src/args.ts b/apps/cli/src/args.ts index 7adf1913a3..24d6789656 100644 --- a/apps/cli/src/args.ts +++ b/apps/cli/src/args.ts @@ -187,6 +187,10 @@ export function parseDshArgs(argv: readonly string[], version: string, manageDes if (first === 'plugin') { const plugin = program.command('plugin').description('manage a profile\'s plugins by forwarding the remaining arguments to pnpm in the profile directory') plugin + // Commander copies the launcher's disabled help option into subcommands. The + // forwarder restores its own help and stops reading options at forwarded pnpm input. + .helpOption('-h, --help', 'show this help') + .passThroughOptions() .requiredOption('--profile ', 'the profile whose plugins to manage (initialized on first use)', selectProfile) .allowUnknownOption() .argument('[args...]', 'pnpm arguments, forwarded verbatim (add , remove , why , ...)') diff --git a/apps/cli/tests/agent-team-headless.e2e.ts b/apps/cli/tests/agent-team-headless.e2e.ts index 481cfc7332..45e954e40f 100644 --- a/apps/cli/tests/agent-team-headless.e2e.ts +++ b/apps/cli/tests/agent-team-headless.e2e.ts @@ -88,11 +88,11 @@ describe('dsh run with Agent Teams enabled', () => { expect(files).toHaveLength(4) const logs = await Promise.all(files.map(file => readFile(join(sessions, file), 'utf8'))) const parsed = logs.map(records) - const workflowChild = parsed.find(log => log.some(record => record.type === 'subagent/descriptor' - && (record.data as { mode: string }).mode === 'one-shot')) + const workflowChild = parsed.find(log => log.some(record => record.type === 'user/message' + && JSON.stringify((record.data as { content: unknown }).content) === JSON.stringify([{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }]))) expect(workflowChild).toBeDefined() expect(workflowChild!.find(record => record.type === 'subagent/descriptor')?.data) - .toMatchObject({ mode: 'one-shot', provider: 'spawn' }) + .toMatchObject({ mode: 'continuable', provider: 'spawn' }) expect(workflowChild!.filter(record => record.type === 'user/message' && (record.data as { source: { kind: string } }).source.kind === 'user').map(record => record.data)) .toEqual([expect.objectContaining({ content: [{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }] })]) diff --git a/apps/cli/tests/args.spec.ts b/apps/cli/tests/args.spec.ts index 88138f2dec..e255789466 100644 --- a/apps/cli/tests/args.spec.ts +++ b/apps/cli/tests/args.spec.ts @@ -120,6 +120,25 @@ describe('parseDshArgs', () => { // Unknown pnpm flags forward verbatim. expect(parse(['plugin', '--profile', 'tui', 'add', '--save-dev', 'x'])) .toEqual({ mode: 'plugin', profile: 'tui', args: ['add', '--save-dev', 'x'] }) + // Options after the pnpm command belong to pnpm, including the forwarder's own names. + expect(parse(['plugin', '--profile', 'tui', 'add', '--help'])) + .toEqual({ mode: 'plugin', profile: 'tui', args: ['add', '--help'] }) + expect(parse(['plugin', '--profile', 'tui', 'add', '-h', '--profile', 'x'])) + .toEqual({ mode: 'plugin', profile: 'tui', args: ['add', '-h', '--profile', 'x'] }) + // A leading pnpm option starts the forwarded arguments before the pnpm command. + expect(parse(['plugin', '--profile', 'tui', '--filter', 'x', '--profile', 'y', 'add', 'z'])) + .toEqual({ mode: 'plugin', profile: 'tui', args: ['--filter', 'x', '--profile', 'y', 'add', 'z'] }) + expect(exitCode(['plugin', 'add', 'x', '--profile', 'tui'])).toBe(1) + expect(exitCode(['plugin', '--filter', 'x', 'add', '--profile', 'tui'])).toBe(1) + }) + + it.each([ + ['plugin', '--help'], ['plugin', '-h'], ['plugin', '--profile', 'tui', '--help'], + ['plugin', '--profile', 'tui', '--filter', 'x', 'add', '--help'], + ])('prints the plugin forwarder help for %j', (...argv: string[]) => { + const stdout = vi.spyOn(process.stdout, 'write').mockReturnValue(true) + expect(exitCode(argv)).toBe(0) + expect(stdout.mock.calls.map(([chunk]) => String(chunk)).join('')).toContain('Usage: dsh plugin [options] [args...]') }) it.each(['desktop', 'Desktop', 'DESKTOP'])('permits installed Desktop plugin commands for %s', (profile) => { diff --git a/apps/cli/tests/built-bin.e2e.ts b/apps/cli/tests/built-bin.e2e.ts index b26b3e538c..09a0f39b1f 100644 --- a/apps/cli/tests/built-bin.e2e.ts +++ b/apps/cli/tests/built-bin.e2e.ts @@ -372,6 +372,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', expect(web.stderr).toBe('') expect(web.stdout).toContain('Usage: dsh --profile web') expect(web.stdout).toContain('--port ') + expect(web.stdout).toContain('--public-url ') expect(web.stdout).not.toContain('dsh web: http://') const wildcardHost = await runBuiltBin(['web', '--host', '0.0.0.0'], { @@ -1248,6 +1249,9 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', ['plugin-package-inventory-deepseek', '@deepseek-ai/dsh-plugin-package-inventory-deepseek'], ['llm-deepseek', '@deepseek-ai/dsh-llm-deepseek-api-key'], ['sandbox', '@deepseek-ai/dsh-sandbox-local'], + ['filesystem', '@deepseek-ai/dsh-fs-local'], + ['working-directory', '@deepseek-ai/dsh-working-directory'], + ['tool-working-directory', '@deepseek-ai/dsh-tool-working-directory'], ['session-projection', '@deepseek-ai/dsh-session-projection'], ['sandbox-policy', '@deepseek-ai/dsh-sandbox-policy'], ['subprocess', '@deepseek-ai/dsh-subprocess-local'], @@ -1264,11 +1268,6 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', ['agent', '@deepseek-ai/dsh-agent'], ['llm-retry', '@deepseek-ai/dsh-llm-retry'], ['jobs', '@deepseek-ai/dsh-jobs-local'], - ['invariants', '@deepseek-ai/dsh-invariants'], - ['session-invariant', '@deepseek-ai/dsh-session/invariant'], - ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], - ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], - ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], diff --git a/apps/cli/tests/dsh-badge.expected.e2e.ts b/apps/cli/tests/dsh-badge.expected.e2e.ts index ca70622be2..a1f456c5b5 100644 --- a/apps/cli/tests/dsh-badge.expected.e2e.ts +++ b/apps/cli/tests/dsh-badge.expected.e2e.ts @@ -5,7 +5,7 @@ const binScript = fileURLToPath(new URL('./fixtures/dsh-badge/snapshot.ts', impo const configPath = fileURLToPath(new URL('./fixtures/dsh-badge/cordis.yml', import.meta.url)) const defaultConfigPath = fileURLToPath(new URL('./fixtures/dsh-badge/default.cordis.yml', import.meta.url)) const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)) -const badgeAssetsPath = fileURLToPath(new URL('../../../packages/skill/skill-badge/assets/', import.meta.url)) +const badgeAssetsPath = fileURLToPath(new URL('../../../packages/experimental/skill-badge/assets/', import.meta.url)) describe('dsh badge assembled snapshot', () => { it('advertises and loads the opt-in bundled skill through the shipped app', async () => { diff --git a/apps/cli/tests/expected/dump-config-schema.json b/apps/cli/tests/expected/dump-config-schema.json index ce57b82073..29f03673a0 100644 --- a/apps/cli/tests/expected/dump-config-schema.json +++ b/apps/cli/tests/expected/dump-config-schema.json @@ -445,6 +445,13 @@ ] } }, + { + "not": { + "required": [ + "preset" + ] + } + }, { "anyOf": [ { @@ -494,6 +501,13 @@ ] } }, + { + "not": { + "required": [ + "preset" + ] + } + }, { "anyOf": [ { @@ -543,6 +557,13 @@ ] } }, + { + "not": { + "required": [ + "preset" + ] + } + }, { "anyOf": [ { @@ -578,6 +599,11 @@ "properties": { "insert": { "$ref": "#/$defs/entryList" + }, + "preset": { + "type": "string", + "pattern": "\\S", + "description": "Outer preset row id. The remaining patch fields apply to its current config.plugins list; a missing or unaddressable preset fails composition." } }, "description": "An insert appends entries, optionally inside the group identified by id. Other patches replace supplied fields; config is replaced wholesale, not deep-merged. A truthy name asserts the existing plugin name rather than renaming it. Unknown targets and non-insert patches without a nonempty id are warned and skipped." diff --git a/apps/cli/tests/fixtures/dsh-badge/cordis.yml b/apps/cli/tests/fixtures/dsh-badge/cordis.yml index dd419b684f..e67020c176 100644 --- a/apps/cli/tests/fixtures/dsh-badge/cordis.yml +++ b/apps/cli/tests/fixtures/dsh-badge/cordis.yml @@ -1,5 +1,6 @@ -- id: skill-badge - disabled: false +- insert: + - id: skill-badge + name: '@deepseek-ai/dsh-skill-badge' - id: skill-filesystem config: diff --git a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts index 4c2331ed0b..c84912a9f1 100644 --- a/apps/cli/tests/fixtures/dsh-badge/snapshot.ts +++ b/apps/cli/tests/fixtures/dsh-badge/snapshot.ts @@ -11,9 +11,16 @@ const overlayPath = process.argv[2] if (overlayPath === undefined) throw new Error('dsh-badge snapshot requires an overlay path') const rootConfigPath = fileURLToPath(new URL('../../../../../packages/bundle/base/tests/fixtures/root.cordis.yml', import.meta.url)) const basePatchPath = fileURLToPath(new URL('../../../../../packages/bundle/base/cordis.patch.yml', import.meta.url)) +// The fixture owns the optional provider; the base bundle has no dependency on it. +const overlays = loadOverlayPatches('dsh-badge-snapshot', overlayPath).map(patch => patch.insert === undefined ? patch : { + ...patch, + insert: patch.insert.map(row => row.name === '@deepseek-ai/dsh-skill-badge' + ? { ...row, name: import.meta.resolve('@deepseek-ai/dsh-skill-badge') } + : row), +}) const ctx = await boot('dsh-badge-snapshot', rootConfigPath, [ ...loadOverlayPatches('dsh-badge-snapshot', basePatchPath), - ...loadOverlayPatches('dsh-badge-snapshot', overlayPath), + ...overlays, ]) try { diff --git a/apps/cli/tests/github-webhook-real.e2e.ts b/apps/cli/tests/github-webhook-real.e2e.ts index 78ad62c968..4c23211d81 100644 --- a/apps/cli/tests/github-webhook-real.e2e.ts +++ b/apps/cli/tests/github-webhook-real.e2e.ts @@ -4,15 +4,16 @@ import type { ChildProcess } from 'node:child_process' import { spawn } from 'node:child_process' import { createHmac, randomUUID } from 'node:crypto' import { existsSync } from 'node:fs' -import { mkdir, mkdtemp, realpath, rm } from 'node:fs/promises' +import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from 'node:fs/promises' import { createServer } from 'node:net' import type { AddressInfo } from 'node:net' import { tmpdir } from 'node:os' -import { join } from 'node:path' +import { dirname, join } from 'node:path' import { setTimeout as delay } from 'node:timers/promises' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' import WebSocket from 'ws' +import { initProfile, PROFILE_TEMPLATES } from '@deepseek-ai/dsh-app-boot' const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url)) const BUILT_BIN = join(REPO_ROOT, 'apps/cli/lib/bin.js') @@ -357,6 +358,18 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('GitHub webhook through the real const workspacePath = join(root, 'workspace') await mkdir(workspacePath) const canonicalWorkspacePath = await realpath(workspacePath) + const profile = join(root, '.dsh/profiles/web') + initProfile(profile, PROFILE_TEMPLATES.web!.bundles) + const manifestPath = join(profile, 'package.json') + const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as { dependencies: Record } + for (const name of ['@deepseek-ai/dsh-webhook', '@deepseek-ai/dsh-webhook-github']) { + const directory = dirname(fileURLToPath(import.meta.resolve(`${name}/package.json`))) + const link = join(profile, 'node_modules', name) + await mkdir(dirname(link), { recursive: true }) + await symlink(directory, link, 'junction') + manifest.dependencies[name] = `file:${directory}` + } + await writeFile(manifestPath, `${JSON.stringify(manifest)}\n`) const webhookPort = await freePort() const child = spawn(process.execPath, [ BUILT_BIN, diff --git a/apps/cli/tests/optional-bundle-transitions.e2e.ts b/apps/cli/tests/optional-bundle-transitions.e2e.ts new file mode 100644 index 0000000000..78d0e45813 --- /dev/null +++ b/apps/cli/tests/optional-bundle-transitions.e2e.ts @@ -0,0 +1,349 @@ +/** Optional bundles through the shipped Web Loader, live package manager, and real Agent loop. */ +import { randomUUID } from 'node:crypto' +import { existsSync } from 'node:fs' +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { createRequire } from 'node:module' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { Context } from '@deepseek-ai/cordis' +import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent' +import type { ProfileContext } from '@deepseek-ai/dsh-app-boot' +import { LlmAdapter, ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm' +import type { GenerateOptions, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm' +import { SessionId } from '@deepseek-ai/dsh-session' +import type {} from '@deepseek-ai/dsh-agent-preset-registry' +import type {} from '@deepseek-ai/dsh-hmr' +import type {} from '@deepseek-ai/dsh-plugin-manager' +import type {} from '@deepseek-ai/dsh-permission-presets' +import type {} from '@deepseek-ai/dsh-session-title-llm' +import type {} from '@deepseek-ai/dsh-skill' +import type {} from '@deepseek-ai/dsh-tools' +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest' +import { textResponse, toolCallResponse } from '../../../packages/core/agent-loop/tests/mock-adapter.ts' + +const root = fileURLToPath(new URL('../../..', import.meta.url)) +const installAnchor = join(root, 'apps/cli/package.json') +const bundles = { + search: '@deepseek-ai/dsh-experimental-session-search', + ralph: '@deepseek-ai/dsh-experimental-ralph-bundle', + terminal: '@deepseek-ai/dsh-experimental-terminal-bundle', + badge: '@deepseek-ai/dsh-experimental-badge-skill-bundle', + titles: '@deepseek-ai/dsh-experimental-session-titles-bundle', +} as const +type Response = StreamChunk[] | ((request: GenerateOptions) => StreamChunk[]) + +/** Only the external model is scripted; tools and child Agents use the shipped implementations. */ +class BundleModel extends LlmAdapter { + readonly requests: GenerateOptions[] = [] + readonly scripts = new Map() + private titleNumber = 0 + + override resolveModel(provider: string, model: string): Promise { + return Promise.resolve({ provider, id: model, name: model }) + } + + async * stream(request: GenerateOptions): AsyncIterable { + this.requests.push(request) + if (request.purpose === 'session-title') { + yield* textResponse(`Bundle title ${String(++this.titleNumber)}`) + return + } + if (request.tools?.some(tool => tool.name === 'structured_output')) { + yield* toolCallResponse(`report-${randomUUID()}`, 'structured_output', { + status: 'complete', summary: 'OPTIONAL_RALPH_DONE', evidence: ['The scripted worker completed.'], nextSteps: [], blocker: '', + }) + return + } + const response = this.scripts.get(request.sessionId ?? '')?.shift() + if (response === undefined) throw new Error(`No scripted model response for ${String(request.sessionId)}`) + yield* typeof response === 'function' ? response(request) : response + } +} + +describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official optional bundle transitions', { retry: 0 }, () => { + let ctx: Context + let temporary: string + let workspace: string + const model = new BundleModel() + const handles = new Set() + const selected = new Set() + let originalEnvironment: Record = {} + + beforeAll(async () => { + temporary = await mkdtemp(join(tmpdir(), 'dsh-optional-bundles-')) + workspace = join(temporary, 'workspace') + await mkdir(workspace) + const home = join(temporary, 'home') + const values = { + DSH_HOME: home, DSH_AGENTS_HOME: join(temporary, 'agents'), DSH_BUNDLED_SKILL_DIR: join(temporary, 'skills'), + DSH_TELEMETRY_DISABLED: '1', TMUX: '', TMUX_PANE: '', + } + originalEnvironment = Object.fromEntries(Object.keys(values).map(key => [key, process.env[key]])) + Object.assign(process.env, values) + const require = createRequire(import.meta.url) + const app = require(join(root, 'packages/boot/app-boot/lib/index.js')) as typeof import('@deepseek-ai/dsh-app-boot') + const cmdline = require(join(root, 'packages/boot/cmdline/lib/index.js')) as typeof import('@deepseek-ai/dsh-cmdline') + const directory = join(home, 'profiles', 'web') + app.initProfile(directory, ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app']) + await writeFile(join(directory, 'cordis.yml'), '[]\n') + await writeFile(join(directory, 'cordis.patch.yml'), JSON.stringify([ + { id: 'agent-default-model', config: { provider: 'bundle-test', model: 'scripted' } }, + ])) + const profile = app.loadProfileDirectory('bundle-test', directory, installAnchor) + const profileContext: ProfileContext = { + name: 'web', dir: directory, patchPath: profile.patchPath, installAnchor, cwd: workspace, home, + startedBundles: profile.layers.map(layer => layer.packageName), overlays: [], telemetryDisabledEnv: '1', + } + const resolution = await app.createRuntimeResolution({ installAnchor, home, profile }) + const listeners = new Set<() => void>() + let ready = false + ctx = await app.boot('bundle-test', join(directory, 'cordis.yml'), app.readProfilePatches('bundle-test', profileContext), async (owner) => { + owner.provide('profileContext', profileContext) + owner.provide('appReady', { onReady: (listener) => { + if (ready) { listener(); return () => {} } + listeners.add(listener) + return () => { listeners.delete(listener) } + } }) + await owner.plugin(app.PluginPackages, { resolution }) + cmdline.provideCmdline(owner, { args: ['--no-open', '--port', '0'], exit: (code) => { throw new Error(`Web command exited ${String(code)}`) } }) + }) + ready = true + for (const listener of listeners) listener() + listeners.clear() + ctx.llm.registerAdapter(['bundle-test'], model) + expect(ctx.get('hmr')).toBeDefined() + }, 120_000) + + afterEach(async () => { + for (const handle of handles) await handle.dispose() + handles.clear() + for (const name of [...selected]) { + expect(await ctx.pluginManager.setBundleEnabled(name, false)).toMatchObject({ application: 'applied' }) + selected.delete(name) + } + }, 120_000) + + afterAll(async () => { + try { + for (const handle of handles) await handle.dispose() + await ctx?.fiber.dispose() + } finally { + for (const [key, value] of Object.entries(originalEnvironment)) { + if (value === undefined) Reflect.deleteProperty(process.env, key) + else process.env[key] = value + } + if (temporary !== undefined) await rm(temporary, { recursive: true, force: true }) + } + }, 120_000) + + async function agent(preset = 'standard', cwd = workspace): Promise { + const handle = await ctx.agents.create({ + sessionId: SessionId(`optional-${preset}-${randomUUID()}`), meta: { cwd, agentPreset: preset }, + agentOptions: { provider: 'bundle-test', model: 'scripted' }, + setup: scoped => ctx.agentPresets.mount(scoped, preset).then(() => undefined), + }) + handles.add(handle) + ctx.permissionPresets.set(handle.agent.session, 'danger-full-access') + return handle + } + + async function turn(owner: Agent, prompt: string, responses: Response[]): Promise { + model.scripts.set(owner.session.id, [...responses]) + owner.followup(createUserMessage({ content: [{ type: 'text', text: prompt }], source: { kind: 'user' } })) + await owner.whenIdle() + expect(model.scripts.get(owner.session.id)).toEqual([]) + } + + async function select(name: string, enabled: boolean): Promise { + if (enabled) selected.add(name) + const result = await ctx.pluginManager.setBundleEnabled(name, enabled) + if (!enabled && result.application === 'applied') selected.delete(name) + expect(result, JSON.stringify(result)).toMatchObject({ changed: true, application: 'applied' }) + } + + const names = (owner?: Agent) => ctx.tools.schemas(owner).map(tool => tool.name).sort() + const toolResult = (owner: Agent, callId: string) => owner.session.deriveMessages() + .flatMap(message => message.role === 'tool' && message.toolCallId === callId ? message.content : []) + .flatMap(block => block.type === 'text' ? [block.text] : []).join('\n') + + it('keeps the default host and minimal tool catalogs while every optional bundle is off', async () => { + const minimal = await agent('minimal') + const standard = await agent() + expect(names()).toEqual(['working_directory']) + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory']) + for (const name of ['str_replace_editor', 'session_search', 'terminal_open', 'ralph']) expect(names(standard.agent)).not.toContain(name) + expect((await ctx.skills.list({ cwd: workspace, scope: standard.agent })).some(skill => skill.name === 'dsh-badge')).toBe(false) + }) + + it('generates and executes the PTC SDK for the selected search bundle', async () => { + await select(bundles.search, true) + const coded = await agent('ptc') + const native = await agent('cordis') + const minimal = await agent('minimal') + const assembly = await ctx.systemPrompt.assemble({ scope: coded.agent }) + expect(assembly.tools.map(tool => tool.name)).toEqual(['run_code']) + expect(assembly.sections.find(section => section.name === 'tools:sdk')?.text).toContain('session_trace') + expect(names(native.agent)).toContain('session_search') + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory']) + await turn(coded.agent, 'Read this session lineage through run_code.', [ + toolCallResponse('ptc-search', 'run_code', { + code: 'console.log(await tools.session_trace({}));', + description: 'Read the current session lineage', + }), textResponse('PTC_SEARCH_DONE'), + ]) + expect(toolResult(coded.agent, 'ptc-search')).toContain(coded.agent.session.id) + expect(coded.agent.session.snapshotEvents().some(event => event.type === 'tool/ptc-dispatch' && event.data.name === 'session_trace')).toBe(true) + await select(bundles.search, false) + }) + + it('searches persisted conversation content only through the selected search bundle', async () => { + const seed = await agent() + await turn(seed.agent, 'Remember OPTIONAL_SEARCH_MARKER for a later session.', [textResponse('Recorded the marker.')]) + const seedId = seed.agent.session.id + await seed.dispose() + handles.delete(seed) + const live = await agent() + await turn(live.agent, 'Keep OPTIONAL_SEARCH_MARKER in this live session.', [textResponse('Live marker recorded.')]) + const otherWorkspace = join(temporary, 'other-workspace') + await mkdir(otherWorkspace) + const foreign = await agent('standard', otherWorkspace) + await turn(foreign.agent, 'Keep OPTIONAL_SEARCH_MARKER in the other workspace.', [textResponse('Other marker recorded.')]) + await select(bundles.search, true) + expect(names(live.agent)).not.toContain('session_search') + const searcher = await agent() + await turn(searcher.agent, 'Find the previous marker in session history.', [ + toolCallResponse('search-history', 'session_search', { query: 'OPTIONAL_SEARCH_MARKER' }), textResponse('SEARCH_DONE'), + ]) + expect(toolResult(searcher.agent, 'search-history')).toContain(seedId) + expect(toolResult(searcher.agent, 'search-history')).toContain(live.agent.session.id) + expect(toolResult(searcher.agent, 'search-history')).not.toContain(foreign.agent.session.id) + expect(toolResult(searcher.agent, 'search-history')).toContain('OPTIONAL_SEARCH_MARKER') + await select(bundles.search, false) + expect(names((await agent()).agent)).not.toContain('session_search') + await turn(searcher.agent, 'Search the marker again using the retained capability.', [ + toolCallResponse('search-retained', 'session_search', { query: 'OPTIONAL_SEARCH_MARKER' }), textResponse('RETAINED_SEARCH_DONE'), + ]) + expect(toolResult(searcher.agent, 'search-retained')).toContain(seedId) + const sessionId = searcher.agent.session.id + await searcher.dispose() + handles.delete(searcher) + const reopened = await ctx.agents.resume({ resumeSessionId: sessionId, setup: scoped => ctx.agentPresets.mount(scoped, 'standard').then(() => undefined) }) + handles.add(reopened) + expect(names(reopened.agent)).not.toContain('session_search') + }) + + it('loads the actual badge asset only while its bundle is selected', async () => { + const owner = await agent() + const load = (callId: string) => ctx.tools.execute({ + callId: ToolCallId(callId), name: 'skill', arguments: { name: 'dsh-badge' }, + agent: owner.agent, signal: new AbortController().signal, + }) + expect((await load('badge-before')).isError).toBe(true) + await select(bundles.badge, true) + await turn(owner.agent, 'Load the dsh badge skill.', [ + toolCallResponse('badge-on', 'skill', { name: 'dsh-badge' }), textResponse('BADGE_DONE'), + ]) + expect(toolResult(owner.agent, 'badge-on')).toContain('powered by dsh') + expect(toolResult(owner.agent, 'badge-on')).toContain('dsh-badge.png') + const minimal = await agent('minimal') + expect((await ctx.skills.list({ cwd: workspace, scope: minimal.agent })).map(skill => skill.name)).toContain('dsh-badge') + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory']) + await select(bundles.badge, false) + expect((await load('badge-after')).isError).toBe(true) + }) + + it('runs and closes a real persistent terminal delivered by its bundle', async () => { + await select(bundles.terminal, true) + const owner = await agent() + const target = join(workspace, 'terminal-output.txt') + let terminalId: string | undefined + const id = () => { + if (terminalId === undefined) throw new Error('terminal_open returned no terminal id') + return terminalId + } + await turn(owner.agent, 'Use a persistent terminal to retain a variable, write its value, and close the terminal.', [ + toolCallResponse('terminal-open', 'terminal_open', { type: 'shell', name: 'optional', cwd: workspace }), + (request) => { + terminalId = /started terminal session (pty-\d+)/.exec(JSON.stringify(request.messages))?.[1] + return toolCallResponse('terminal-set', 'terminal_send', { + sessionId: id(), text: process.platform === 'win32' ? "$env:DSH_OPTIONAL = 'TERMINAL_BUNDLE_OK'" : 'export DSH_OPTIONAL=TERMINAL_BUNDLE_OK', + }) + }, + () => toolCallResponse('terminal-write', 'terminal_send', { + sessionId: id(), text: process.platform === 'win32' + ? `Set-Content -NoNewline -Path '${target.replaceAll("'", "''")}' -Value $env:DSH_OPTIONAL` + : `printf '%s' "$DSH_OPTIONAL" > ${JSON.stringify(target)}`, + }), + () => toolCallResponse('terminal-close', 'terminal_close', { sessionId: id() }), + textResponse('TERMINAL_DONE'), + ]) + expect(await readFile(target, 'utf8')).toBe('TERMINAL_BUNDLE_OK') + expect(toolResult(owner.agent, 'terminal-close')).toContain('closed terminal session') + await select(bundles.terminal, false) + expect(names((await agent()).agent)).not.toContain('terminal_open') + }) + + it('runs a real fresh-Agent Ralph workflow through the selected bundle', async () => { + await select(bundles.ralph, true) + const owner = await agent() + await turn(owner.agent, 'The human explicitly requests one Ralph loop.', [ + toolCallResponse('ralph-run', 'ralph', { objective: 'Report completion with the structured output tool.', maxRounds: 1 }), + textResponse('RALPH_DONE'), + ]) + expect(toolResult(owner.agent, 'ralph-run')).toContain('Ralph worker reported completion after 1 round') + expect(toolResult(owner.agent, 'ralph-run')).toContain('OPTIONAL_RALPH_DONE') + expect(model.requests.some(request => request.sessionId !== owner.agent.session.id && request.tools?.some(tool => tool.name === 'structured_output'))).toBe(true) + await select(bundles.ralph, false) + expect(names((await agent()).agent)).not.toContain('ralph') + }) + + it('switches title cadence off/on/off through the live manager', async () => { + const titleRequests = (owner: Agent) => owner.session.snapshotEvents().filter(event => event.type === 'session/title-llm-request') + const first = await agent() + await turn(first.agent, 'First title prompt.', [textResponse('ONE')]) + await expect.poll(() => titleRequests(first.agent).length).toBe(1) + await turn(first.agent, 'Second title prompt.', [textResponse('TWO')]) + expect(titleRequests(first.agent)).toHaveLength(1) + await select(bundles.titles, true) + const following = await agent() + await turn(following.agent, 'Start a growing conversation.', [textResponse('ONE')]) + await expect.poll(() => titleRequests(following.agent).length).toBe(1) + expect(titleRequests(following.agent)[0]?.data.titleProvider).toBe('session-title-all-prompts-llm') + await expect.poll(() => ctx.sessionTitle.get(following.agent.session)?.source.kind).toBe('provider') + await turn(following.agent, 'Continue the growing conversation.', [textResponse('TWO')]) + await expect.poll(() => titleRequests(following.agent).length).toBe(2) + expect(titleRequests(following.agent).every(event => event.data.titleProvider === 'session-title-all-prompts-llm')).toBe(true) + await select(bundles.titles, false) + const restored = await agent() + await turn(restored.agent, 'Restore first-prompt titles.', [textResponse('ONE')]) + await expect.poll(() => titleRequests(restored.agent).length).toBe(1) + await turn(restored.agent, 'The title stays on the first prompt.', [textResponse('TWO')]) + expect(titleRequests(restored.agent)).toHaveLength(1) + expect(titleRequests(restored.agent)[0]?.data.titleProvider).toBe('session-title-first-prompt-llm') + }) + + it('composes all five bundles while leaving minimal and host tools unchanged', async () => { + for (const name of Object.values(bundles)) await select(name, true) + let coded: Agent | undefined + for (const preset of ['standard', 'cordis', 'ptc']) { + const owner = await agent(preset) + expect(names(owner.agent)).toEqual(expect.arrayContaining(['session_search', 'terminal_open', 'ralph'])) + if (preset === 'ptc') coded = owner.agent + } + if (coded === undefined) throw new Error('the PTC Agent was not created') + await turn(coded, 'Read this session lineage and load the badge through one program.', [ + toolCallResponse('combined-program', 'run_code', { + code: 'console.log(await tools.session_trace({})); console.log(await tools.skill({ name: "dsh-badge" }));', + description: 'Use optional session search and badge together', + }), textResponse('COMBINED_DONE'), + ]) + expect(toolResult(coded, 'combined-program')).toContain(coded.session.id) + expect(toolResult(coded, 'combined-program')).toContain('dsh-badge') + const minimal = await agent('minimal') + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory']) + expect(names()).toEqual(['working_directory']) + for (const name of [...Object.values(bundles)].reverse()) await select(name, false) + expect(names((await agent()).agent)).not.toContain('session_search') + }) +}) diff --git a/apps/cli/tests/profiles/acp/tests/fixtures/image-offload.cordis.yml b/apps/cli/tests/profiles/acp/tests/fixtures/image-offload.cordis.yml index 21a9ead728..7059fa886e 100644 --- a/apps/cli/tests/profiles/acp/tests/fixtures/image-offload.cordis.yml +++ b/apps/cli/tests/profiles/acp/tests/fixtures/image-offload.cordis.yml @@ -36,7 +36,7 @@ name: '@deepseek-ai/dsh-system-prompt' config: personaPrefix: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. + You are a coding assistant powered by the {{model}} model. Keep answers brief and factual. diff --git a/apps/cli/tests/profiles/acp/tests/goal-expected/goal-round-driver/session.expected.jsonl b/apps/cli/tests/profiles/acp/tests/goal-expected/goal-round-driver/session.expected.jsonl index 6cd9ea429c..19495490be 100644 --- a/apps/cli/tests/profiles/acp/tests/goal-expected/goal-round-driver/session.expected.jsonl +++ b/apps/cli/tests/profiles/acp/tests/goal-expected/goal-round-driver/session.expected.jsonl @@ -1,4 +1,4 @@ -{"type":"session","version":3,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"session","version":4,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} {"type":"permission/preset","data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","data":{"mode":"danger-full-access"}} {"type":"approval/policy","data":{"policy":"never"}} @@ -6,21 +6,21 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Create a durable two-round goal for the ACP snapshot, inspect it, then report readiness."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Create a durable two-round goal","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"max_goal_rounds\":2}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":20,"outputTokens":8},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_goal_create","name":"create_goal","args":["{\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"max_goal_rounds\":2}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"max_goal_rounds\":2}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":20,"outputTokens":8}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"max_goal_rounds\":2}"}} {"type":"goal/change","data":{"kind":"goal/change","version":1,"operation":"create","goal":{"id":"goal-{{sessionId}}","revision":1,"objective":"Finish the ACP goal-round-driver snapshot proof","phase":"active","maxGoalRounds":2},"roundsStarted":0,"createdAt":0,"updatedAt":0}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_goal_create"},"content":[{"type":"tool-result","toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_goal_create"},"toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":30,"outputTokens":4},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_goal_get","name":"get_goal","args":["{}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_get","name":"get_goal","arguments":"{}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":30,"outputTokens":4}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_goal_get","name":"get_goal","arguments":"{}"}} -{"type":"tool/result","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_goal_get"},"content":[{"type":"tool-result","toolCallId":"call_goal_get","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[20],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_goal_get"},"toolCallId":"call_goal_get","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal-round-driver snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[20],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":2}} {"type":"step/start","data":{"turn":1,"step":3}} {"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":35,"outputTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["GOAL READY"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"GOAL READY"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":35,"outputTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} diff --git a/apps/cli/tests/profiles/acp/tests/goal-expected/goal-wrapup/session.expected.jsonl b/apps/cli/tests/profiles/acp/tests/goal-expected/goal-wrapup/session.expected.jsonl index 5aa8425050..7e3a02fce9 100644 --- a/apps/cli/tests/profiles/acp/tests/goal-expected/goal-wrapup/session.expected.jsonl +++ b/apps/cli/tests/profiles/acp/tests/goal-expected/goal-wrapup/session.expected.jsonl @@ -1,4 +1,4 @@ -{"type":"session","version":3,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"session","version":4,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} {"type":"permission/preset","data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","data":{"mode":"danger-full-access"}} {"type":"approval/policy","data":{"policy":"never"}} @@ -6,16 +6,16 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Create a durable goal for the wrap-up snapshot, then report readiness."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Create a durable goal for","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"max_goal_rounds\":2}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":20,"outputTokens":8},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_goal_create","name":"create_goal","args":["{\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"max_goal_rounds\":2}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"max_goal_rounds\":2}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":20,"outputTokens":8}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_goal_create","name":"create_goal","arguments":"{\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"max_goal_rounds\":2}"}} {"type":"goal/change","data":{"kind":"goal/change","version":1,"operation":"create","goal":{"id":"goal-{{sessionId}}","revision":1,"objective":"Finish the ACP goal wrap-up snapshot proof","phase":"active","maxGoalRounds":2},"roundsStarted":0,"createdAt":0,"updatedAt":0}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_goal_create"},"content":[{"type":"tool-result","toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_goal_create"},"toolCallId":"call_goal_create","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":1,\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"phase\":\"active\",\"roundsStarted\":0,\"maxGoalRounds\":2},\"activation\":\"armed\"}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL READY"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":28,"outputTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["GOAL READY"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"GOAL READY"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":28,"outputTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} @@ -30,12 +30,12 @@ {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_complete","name":"update_goal","arguments":"{\"goal_id\":\"goal-{{sessionId}}\",\"revision\":1,\"action\":\"complete\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":40,"outputTokens":9},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_goal_complete","name":"update_goal","args":["{\"goal_id\":\"goal-{{sessionId}}\",\"revision\":1,\"action\":\"complete\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_complete","name":"update_goal","arguments":"{\"goal_id\":\"goal-{{sessionId}}\",\"revision\":1,\"action\":\"complete\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":40,"outputTokens":9}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":2,"step":1,"callId":"call_goal_complete","name":"update_goal","arguments":"{\"goal_id\":\"goal-{{sessionId}}\",\"revision\":1,\"action\":\"complete\"}"}} {"type":"goal/change","data":{"kind":"goal/change","version":1,"operation":"complete","goal":{"id":"goal-{{sessionId}}","revision":2,"objective":"Finish the ACP goal wrap-up snapshot proof","phase":"complete","maxGoalRounds":2},"roundsStarted":1,"createdAt":0,"updatedAt":0}} -{"type":"tool/result","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool","callId":"call_goal_complete"},"content":[{"type":"tool-result","toolCallId":"call_goal_complete","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":2,\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"phase\":\"complete\",\"roundsStarted\":1,\"maxGoalRounds\":2},\"activation\":\"disarmed\"}"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[29],"surfaceOp":"append"} -{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal wrap-up snapshot proof\"\nThe goal is marked complete and this autonomous run is ending. Write the closing message to the user now: state the outcome, summarize what was done and how it was verified, and point to the concrete results (files, commits, or other artifacts). Report only what earlier rounds and tool results in this session actually establish; when a detail is not in the session, say so instead of inventing it. Note anything the user should review or do next. Address the user directly. Do not call any more tools in this run; further work waits for the user's next instruction.\n"}],"source":{"kind":"plugin","plugin":"tool-goal","form":"notice","summary":"complete: Finish the ACP goal wrap-up snapshot proof"},"role":"user","id":"{{sessionId}}"}]}} +{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_goal_complete"},"toolCallId":"call_goal_complete","content":[{"type":"text","text":"{\"goal\":{\"id\":\"goal-{{sessionId}}\",\"revision\":2,\"objective\":\"Finish the ACP goal wrap-up snapshot proof\",\"phase\":\"complete\",\"roundsStarted\":1,\"maxGoalRounds\":2},\"activation\":\"disarmed\"}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[29],"surfaceOp":"append"} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal wrap-up snapshot proof\"\nThe goal is marked complete and this autonomous run is ending. Write the closing message to the user now: state the outcome, summarize what was done and how it was verified, and point to the concrete results (files, commits, or other artifacts). Report only what earlier rounds and tool results in this session actually establish; when a detail is not in the session, say so instead of inventing it. Note anything the user should review or do next. Address the user directly. Do not call any more tools in this run; further work waits for the user's next instruction.\n"}],"source":{"kind":"tool-goal","form":"notice","summary":"complete: Finish the ACP goal wrap-up snapshot proof"},"role":"user","id":"{{sessionId}}"}]}} {"type":"step/end","data":{"turn":2,"step":1}} {"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":2}} -{"type":"user/message","data":{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal wrap-up snapshot proof\"\nThe goal is marked complete and this autonomous run is ending. Write the closing message to the user now: state the outcome, summarize what was done and how it was verified, and point to the concrete results (files, commits, or other artifacts). Report only what earlier rounds and tool results in this session actually establish; when a detail is not in the session, say so instead of inventing it. Note anything the user should review or do next. Address the user directly. Do not call any more tools in this run; further work waits for the user's next instruction.\n"}],"source":{"kind":"plugin","plugin":"tool-goal","form":"notice","summary":"complete: Finish the ACP goal wrap-up snapshot proof"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"\nObjective: \"Finish the ACP goal wrap-up snapshot proof\"\nThe goal is marked complete and this autonomous run is ending. Write the closing message to the user now: state the outcome, summarize what was done and how it was verified, and point to the concrete results (files, commits, or other artifacts). Report only what earlier rounds and tool results in this session actually establish; when a detail is not in the session, say so instead of inventing it. Note anything the user should review or do next. Address the user directly. Do not call any more tools in this run; further work waits for the user's next instruction.\n"}],"source":{"kind":"tool-goal","form":"notice","summary":"complete: Finish the ACP goal wrap-up snapshot proof"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"GOAL WRAP-UP: the snapshot objective is achieved and this closing message reaches the user."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":52,"outputTokens":14},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["GOAL WRAP-UP: the snapshot objective is achieved and this closing message reaches the user."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"GOAL WRAP-UP: the snapshot objective is achieved and this closing message reaches the user."}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":52,"outputTokens":14}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":2}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/acp/tests/image-offload.expected.e2e.ts b/apps/cli/tests/profiles/acp/tests/image-offload.expected.e2e.ts index 59195c7669..259ba7341e 100644 --- a/apps/cli/tests/profiles/acp/tests/image-offload.expected.e2e.ts +++ b/apps/cli/tests/profiles/acp/tests/image-offload.expected.e2e.ts @@ -139,7 +139,8 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn const fileImage = { type: 'image', source: { type: 'file', file_id: 'file-api-snapshot-1' } } const offloadedImage = `[image omitted to fit request image limits; ${attachmentId}.${normalizedAccess}]` const imageHandle = `Image ${attachmentId}; request preview 1x1px.${normalizedAccess}` - const runtimeContext = 'Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\n' + const runtimeContext = (cwd: string): string => 'Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\n' + + `Current working directory: ${JSON.stringify(cwd)}.\n\n` + 'Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\n' + 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).' const messages = requests[0]?.messages as { content?: unknown }[] | undefined @@ -151,7 +152,7 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn { type: 'text', text: imageHandle }, fileImage, { type: 'text', text: ', then use read_image on red.png and reply with DONE.' }, - { type: 'text', text: runtimeContext }, + { type: 'text', text: runtimeContext(result.cwd) }, ]) const followup = requests[1]?.messages as Array<{ role: string; content: Array> }> @@ -202,7 +203,7 @@ it('pins Messages Files offload and inline fallback in assembled requests', asyn { type: 'text', text: `Image ${attachmentId}; request preview 1x1px.${fallbackAccess}` }, { type: 'image', source: { type: 'base64', media_type: 'image/png', data: image } }, { type: 'text', text: ', then use read_image on red.png and reply with DONE.' }, - { type: 'text', text: runtimeContext }, + { type: 'text', text: runtimeContext(fallback.cwd) }, ]) } finally { await new Promise(resolve => server.close(() => { resolve() })) diff --git a/apps/cli/tests/profiles/fixtures/official-installer-observer.mjs b/apps/cli/tests/profiles/fixtures/official-installer-observer.mjs new file mode 100644 index 0000000000..5ca8db1ac6 --- /dev/null +++ b/apps/cli/tests/profiles/fixtures/official-installer-observer.mjs @@ -0,0 +1,117 @@ +/** Observe ordinary manager operations in an installed Web profile without starting native agents. */ +import { existsSync } from 'node:fs' +import { readFile, readdir, stat } from 'node:fs/promises' +import { basename, join } from 'node:path' + +export const name = 'official-installer-observer' +export const inject = ['pluginManager', 'agentPresets', 'subagents', 'sessions', 'appReady'] + +let lastStage = 'none' + +/** Report one stage on the child's stdout, which the test forwards when it fails. */ +function note(stage) { + lastStage = stage + process.stdout.write(`PACKED_OFFICIAL_STEP=${stage} ${String(Date.now())}\n`) +} + +// A run that never finishes must name its stage in the parent's log even when the child is killed at its deadline. +setInterval(() => { note(`stalled-at ${lastStage}`) }, 30_000).unref() + +export function apply(ctx, config) { + let active = true + note('applied') + const stop = ctx.appReady.onReady(() => { + note('ready') + void exercise(ctx, config).then( + value => { if (active) process.stdout.write(`PACKED_OFFICIAL_RESULT=${JSON.stringify({ value })}\n`) }, + error => { if (active) process.stdout.write(`PACKED_OFFICIAL_RESULT=${JSON.stringify({ error: String(error) })}\n`) }, + ) + }) + ctx.effect(() => () => { active = false; stop() }, 'packed-official-observer') +} + +function applied(stage, result) { + if (result.application !== 'applied') throw new Error(`${stage}: ${JSON.stringify(result)}`) +} + +/** + * Run one operation under a bound, so a stalled manager operation reports what it waits for instead of + * holding the whole child until its timeout. pnpm 11.7.0 can print its result and never exit + * (pnpm#12297, fixed in 11.23.0); the run record names the process that stayed alive. + * @param label - the operation being run. + * @param operation - the manager call to await. + * @param boundMs - the time the operation may take. + * @param profile - the isolated profile directory the operation writes. + * @returns The operation's own result. + */ +async function bounded(label, operation, boundMs, profile) { + let timer + const expiry = new Promise((_resolve, reject) => { + timer = setTimeout(() => { reject(new Error(`${label} did not settle within ${String(boundMs)}ms`)) }, boundMs) + }) + try { + return await Promise.race([operation(), expiry]) + } catch (error) { + if (!String(error).includes('did not settle')) throw error + const locks = await readdir(profile).then(files => files.filter(file => file.endsWith('.lock')).join(',') || 'none', () => 'none') + const record = await readFile(join(profile, '.plugin-manager', 'run.json'), 'utf8') + .then(text => JSON.parse(text), () => undefined) + const pid = record?.pid + const alive = pid === undefined ? false : (() => { try { process.kill(pid, 0); return true } catch { return false } })() + const state = alive && existsSync(`/proc/${String(pid)}/status`) + ? await readFile(`/proc/${String(pid)}/status`, 'utf8').then(text => text.split('\n').filter(line => /^(State|Threads):/u.test(line)).join(' '), () => '') + : '' + const cmdline = alive && existsSync(`/proc/${String(pid)}/cmdline`) + ? await readFile(`/proc/${String(pid)}/cmdline`, 'utf8').then(text => text.replaceAll('\0', ' ').trim().slice(0, 120), () => '') + : '' + throw new Error(`${String(error)}\nprofile-locks=${locks}\npnpm-run pid=${String(pid)} alive=${String(alive)} ${state} ${cmdline}`) + } finally { clearTimeout(timer) } +} + +async function exercise(ctx, config) { + note('list-initial') + const initial = await ctx.pluginManager.listBundles() + note('listed-initial') + const steps = [] + for (const name of config.packages) { + const spec = initial.find(bundle => bundle.name === name)?.installTarget?.spec + if (spec === undefined) throw new Error(`No catalog installation target for ${name}`) + const options = { enabled: true, saveExact: true, registry: config.registry } + note(`install ${name}`) + let install = await ctx.pluginManager.installBundle(spec, options) + const approval = install.pendingBuilds + if (approval?.length) install = await ctx.pluginManager.installBundle(spec, { ...options, approvedBuilds: approval }) + note(`installed ${name}`) + applied(`install ${name}`, install) + const manifest = JSON.parse(await readFile(join(config.profile, 'package.json'), 'utf8')) + const installed = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name) + // A transitive repository package the registry served, absent from npm at this version. + const localDependency = JSON.parse(await readFile(join(config.profile, 'node_modules', config.localDependency, 'package.json'), 'utf8')).version + const provider = name.endsWith('-codex') ? 'codex' : 'claude-code' + const providerEnabled = ctx.subagents.list().includes(provider) + const nativeDir = join(config.profile, 'node_modules', config.nativePackages[name]) + const binaryName = `${provider === 'codex' ? 'codex' : 'claude'}${process.platform === 'win32' ? '.exe' : ''}` + const nativeBinary = (await readdir(nativeDir, { recursive: true })).find(path => basename(path) === binaryName) + const nativeArtifact = nativeBinary !== undefined && (await stat(join(nativeDir, nativeBinary))).size > 0 + const presets = await ctx.agentPresets.list() + const beforeUnavailable = await readFile(join(config.profile, 'package.json'), 'utf8') + note(`unavailable ${name}`) + const unavailable = await ctx.pluginManager.installBundle(`${name}@9999.0.0`, options) + note(`unavailable-done ${name}`) + const unchangedAfterUnavailable = beforeUnavailable === await readFile(join(config.profile, 'package.json'), 'utf8') + const off = await ctx.pluginManager.setBundleEnabled(name, false) + applied(`disable ${name}`, off) + const disabled = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name) + const on = await ctx.pluginManager.setBundleEnabled(name, true) + applied(`enable ${name}`, on) + const enabled = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name) + note(`remove ${name}`) + const removed = await bounded(`remove ${name}`, () => ctx.pluginManager.removeBundle(name), 90_000, config.profile) + note(`removed ${name}`) + applied(`remove ${name}`, removed) + const absent = (await ctx.pluginManager.listBundles()).find(bundle => bundle.name === name) + steps.push({ name, install, savedVersion: manifest.dependencies?.[name], installed, localDependency, providerEnabled, nativeArtifact, + presets, unavailable, unchangedAfterUnavailable, off, disabled, on, enabled, removed, absent }) + } + return { initial, steps, sessionCount: ctx.sessions.list().length } +} diff --git a/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml b/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml index 7db9691a6c..3778da03de 100644 --- a/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/goal-snapshot.patch.yml @@ -24,6 +24,9 @@ - insert: - id: llm-replay name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official - id: session-log-deepseek config: diff --git a/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml b/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml index e41c1f97f2..d347a9cfe4 100644 --- a/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/subagent-inheritance-snapshot.patch.yml @@ -29,6 +29,9 @@ - id: replay name: '@deepseek-ai/dsh-llm-replay' + - id: await-subagents + name: '../../../../../packages/test-support/session-snapshot/tests/fixtures/subagent-settlement-barrier.ts' + # Publish the persisted agent before the driver selects the single root. - id: resumed-agent name: './tests/fixtures/subagent-inheritance-agent.ts' diff --git a/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml b/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml index 498c600d7b..2b4b636ecb 100644 --- a/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/subagent-settlement-snapshot.patch.yml @@ -24,6 +24,9 @@ - insert: - id: llm-replay name: '@deepseek-ai/dsh-llm-replay' + config: + providers: + - id: deepseek-official # Hold the parent's second step until the manager notice enters its inbox. - id: settlement-fence diff --git a/apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts b/apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts index bb0b4297b8..531510393e 100644 --- a/apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts @@ -55,7 +55,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('coding task: fix a failing test expect(before.status).not.toBe(0) ctx = await codingHarness(workdir, { personaPrefix: SYSTEM_PROMPT }) - const agent = await ctx.agentLoop.create(SessionId('e2e-task'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = await ctx.agentLoop.create(SessionId('e2e-task'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, { cwd: workdir }) agent.followup(createUserMessage({ content: [{ diff --git a/apps/cli/tests/profiles/headless/tests/compaction.e2e.ts b/apps/cli/tests/profiles/headless/tests/compaction.e2e.ts index bf2d550bcc..b3e5cdb79e 100644 --- a/apps/cli/tests/profiles/headless/tests/compaction.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/compaction.e2e.ts @@ -49,7 +49,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('compaction: a long session compa }, persistenceRoot: join(workdir, '.sessions'), }) - const agent = await ctx.agentLoop.create(SessionId('e2e-compaction'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = await ctx.agentLoop.create(SessionId('e2e-compaction'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, { cwd: workdir }) // Completed read turns cannot collapse into one retained parallel tool group. for (let i = 1; i <= 4; i++) { diff --git a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl index 36fe8fd2d4..ab06b02706 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/goal-tools/stream-json.expected.jsonl @@ -4,11 +4,11 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Probe strict-schema fillers against miss","messageSeqs":[8],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Probe strict-schema fillers against missing-goal revision 1, then create a durable goal to finish the snapshot proof and inspect it.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":14,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":15,"outputTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"call_goal_probe","name":"update_goal","args":["{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":15,"outputTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":15,"time":0,"data":{"turn":1,"step":1,"callId":"call_goal_probe","name":"update_goal","arguments":"{\"goal_id\":\"missing-goal\",\"revision\":1,\"action\":\"pause\",\"objective\":\"\",\"max_goal_rounds\":0,\"blocked_reason\":\"\"}"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":16,"time":0,"data":{"title":"Goal tool snapshot","messageSeqs":[8],"source":{"kind":"provider","provider":"session-title-first-prompt-llm","model":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl index cdbefb5d8f..708e720d48 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl @@ -1,26 +1,26 @@ -{"type":"session","id":"{{session:1}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} +{"type":"session","version":4,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","isSeeded":false,"delegationDepth":0} {"type":"permission/preset","data":{"preset":"danger-full-access"}} {"type":"sandbox/mode","data":{"mode":"danger-full-access"}} {"type":"approval/policy","data":{"policy":"never"}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:2}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:3}}"},"surfaceOp":"append"} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Prove the product headless profile path with one real tool round trip."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"cli-mock","model":"cli-mock"}} {"type":"session/title","data":{"title":"Prove the product headless profile","messageSeqs":[8],"source":{"kind":"fallback"}}} -{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{message:4}}"}],"maxTokens":64}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Inspecting the task before the tool call."},{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{message:5}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[],"texts":["Inspecting the task before the tool call."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Inspecting the task before the tool call."}}},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[],"id":"cli-smoke-call","name":"bash","args":["{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} +{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}} +{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Inspecting the task before the tool call."},{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[],"texts":["Inspecting the task before the tool call."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Inspecting the task before the tool call."}}},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[],"id":"cli-smoke-call","name":"bash","args":["{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}} -{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}} -{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","terminationConfirmed":true,"detail":"exit code: 0"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{message:6}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"} +{"type":"job/registered","data":{"executionId":"{{sessionId}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}} +{"type":"job/settled","data":{"executionId":"{{sessionId}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","terminationConfirmed":true,"detail":"exit code: 0"}} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{sessionId}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"request/header","data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"off"},"tools":"{{tools}}"},"reason":"change"}} -{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{message:7}}"},"usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} +{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":2}} {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl index 0f7431a36e..6ea0d43a17 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/invalid-credential/stream-json.expected.jsonl @@ -4,11 +4,11 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"say pong","messageSeqs":[8],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/attempt","seq":14,"time":0,"data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: the API key resolved from DEEPSEEK_API_KEY contains characters no HTTP header can carry; set DEEPSEEK_API_KEY to the raw key alone (the web Models page writes it)","code":"INVALID_CREDENTIAL"}}}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl index e8f421ae70..f24d221b96 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/missing-credential/stream-json.expected.jsonl @@ -4,11 +4,11 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"say pong"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":256000,"reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true,"maxTokens":true},"tools":"{{tools}}"},"reason":"initial"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":1000000}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"say pong","messageSeqs":[8],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"say pong\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/attempt","seq":14,"time":0,"data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":15,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":16,"time":0,"data":{"turn":1,"reason":{"kind":"error","error":{"message":"llm-deepseek: no API key for provider route \"deepseek-official\"; store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it), or export DEEPSEEK_API_KEY in the launching environment","code":"MISSING_CREDENTIAL"}}}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl index bce6fb173d..a667224c5f 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/provider-retry/stream-json.expected.jsonl @@ -4,11 +4,11 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"retry the transient provider failure"}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"retry the transient provider failure","messageSeqs":[8],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"retry the transient provider failure\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"retry the transient provider failure\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/attempt","seq":14,"time":0,"data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}}]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry","seq":15,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"provider":"deepseek-official","mode":"normal","policyKey":"[\"normal\",1,[\"RATE_LIMIT\"],1,1,0]","retry":1,"maxRetries":1,"delayMs":1,"failure":{"message":"snapshot transient failure","code":"RATE_LIMIT","status":429}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"llm/retry-started","seq":16,"time":0,"data":{"retryId":"{{sessionId}}","turn":1,"step":1,"retry":1}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl index 3db7f52dd3..2afe21d157 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/semantic-checkpoint/session.expected.jsonl @@ -18,7 +18,7 @@ {"type":"step/start","data":{"turn":2,"step":1}} {"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"sourceEventSeqs":[2],"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}} {"type":"user/message","data":{"content":[{"type":"text","text":"Continue safely from the interrupted operation."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial","startsSeries":true}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Perform one side-effecting remote mutati","messageSeqs":[3],"source":{"kind":"fallback"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl index b210549cfa..5ea5179b2e 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-diagnostic/parent.expected.jsonl @@ -16,7 +16,7 @@ {"type":"step/start","data":{"turn":2,"step":1}} {"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"sourceEventSeqs":[2],"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}} {"type":"user/message","data":{"content":[{"type":"text","text":"Call list_agents once and report what it shows."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial","startsSeries":true}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Start a background job.","messageSeqs":[3],"source":{"kind":"fallback"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl index 70c1834115..00868634d4 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/child.expected.jsonl @@ -1,20 +1,20 @@ -{"type":"session","version":3,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","isSeeded":false,"origin":"subagent","delegationDepth":1} +{"type":"session","version":4,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","isSeeded":false,"origin":"subagent","delegationDepth":1} +{"type":"subagent/descriptor","data":{"version":3,"mode":"continuable","provider":"spawn","label":"Delegated write probe","agentProvider":"deepseek-official","agentModel":"deepseek-v4-flash","agentReasoningEffort":"low"}} {"type":"sandbox/mode","data":{"mode":"read-only","source":"delegation"}} {"type":"approval/policy","data":{"policy":"never","source":"delegation"}} -{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."},{"type":"text","text":"Your parent agent id is \"{{sessionId}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"{{sessionId}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}} {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} -{"type":"subagent/descriptor","data":{"version":3,"mode":"one-shot","provider":"spawn","label":"Delegated write probe"}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE."},{"type":"text","text":"Your parent agent id is \"{{sessionId}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"{{sessionId}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Use the write tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"child-write","name":"write","args":["{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":1,"step":1,"callId":"child-write","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"escaped\"}"}} -{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"child-write"},"content":[{"type":"tool-result","toolCallId":"child-write","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true}],"role":"user","id":"{{sessionId}}"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[14],"surfaceOp":"append"} +{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"child-write"},"toolCallId":"child-write","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true,"id":"{{sessionId}}"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[14],"surfaceOp":"append"} {"type":"step/end","data":{"turn":1,"step":1}} {"type":"step/start","data":{"turn":1,"step":2}} {"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["CHILD_DENIED [sandbox: file access denied under read-only mode]"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl index f4077c4510..1cee326a29 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-inheritance/parent.expected.jsonl @@ -16,17 +16,20 @@ {"type":"step/start","data":{"turn":2,"step":1}} {"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"sourceEventSeqs":[2],"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}} {"type":"user/message","data":{"content":[{"type":"text","text":"Delegate the write probe to a subagent."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"},"tools":["bash","create_goal","edit","exit_plan_mode","get_goal","glob","grep","interrupt_agent","job_kill","job_list","job_output","list_agents","read","read_image","send_message","skill","subagent","subagent_fork","todo_write","update_goal","web_fetch","web_search","workflow","write"]},"reason":"resume","startsSeries":true}} -{"type":"developer/message","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool-registry"},"content":[{"type":"tool-addition","toolName":"bash"},{"type":"tool-addition","toolName":"create_goal"},{"type":"tool-addition","toolName":"edit"},{"type":"tool-addition","toolName":"exit_plan_mode"},{"type":"tool-addition","toolName":"get_goal"},{"type":"tool-addition","toolName":"glob"},{"type":"tool-addition","toolName":"grep"},{"type":"tool-addition","toolName":"interrupt_agent"},{"type":"tool-addition","toolName":"job_kill"},{"type":"tool-addition","toolName":"job_list"},{"type":"tool-addition","toolName":"job_output"},{"type":"tool-addition","toolName":"list_agents"},{"type":"tool-addition","toolName":"read"},{"type":"tool-addition","toolName":"read_image"},{"type":"tool-addition","toolName":"send_message"},{"type":"tool-addition","toolName":"skill"},{"type":"tool-addition","toolName":"subagent"},{"type":"tool-addition","toolName":"subagent_fork"},{"type":"tool-addition","toolName":"todo_write"},{"type":"tool-addition","toolName":"update_goal"},{"type":"tool-addition","toolName":"web_fetch"},{"type":"tool-addition","toolName":"web_search"},{"type":"tool-addition","toolName":"workflow"},{"type":"tool-addition","toolName":"write"}],"role":"developer","id":"{{sessionId}}"},"headerSeq":18},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: read-only. Any available operation enforced by the DSH file sandbox cannot modify files in the standing mode. Do not refuse a required modification from this policy alone: try an available tool normally and follow any denial and escalation guidance it returns."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash","reasoningEffort":"low"},"tools":["bash","create_goal","edit","exit_plan_mode","get_goal","glob","grep","interrupt_agent","job_kill","job_list","job_output","list_agents","read","read_image","send_message","skill","subagent","subagent_fork","todo_write","update_goal","web_fetch","web_search","workflow","working_directory","write"]},"reason":"resume","startsSeries":true}} +{"type":"developer/message","data":{"turn":2,"step":1,"message":{"source":{"kind":"tool-registry"},"content":[{"type":"tool-addition","toolName":"bash"},{"type":"tool-addition","toolName":"create_goal"},{"type":"tool-addition","toolName":"edit"},{"type":"tool-addition","toolName":"exit_plan_mode"},{"type":"tool-addition","toolName":"get_goal"},{"type":"tool-addition","toolName":"glob"},{"type":"tool-addition","toolName":"grep"},{"type":"tool-addition","toolName":"interrupt_agent"},{"type":"tool-addition","toolName":"job_kill"},{"type":"tool-addition","toolName":"job_list"},{"type":"tool-addition","toolName":"job_output"},{"type":"tool-addition","toolName":"list_agents"},{"type":"tool-addition","toolName":"read"},{"type":"tool-addition","toolName":"read_image"},{"type":"tool-addition","toolName":"send_message"},{"type":"tool-addition","toolName":"skill"},{"type":"tool-addition","toolName":"subagent"},{"type":"tool-addition","toolName":"subagent_fork"},{"type":"tool-addition","toolName":"todo_write"},{"type":"tool-addition","toolName":"update_goal"},{"type":"tool-addition","toolName":"web_fetch"},{"type":"tool-addition","toolName":"web_search"},{"type":"tool-addition","toolName":"workflow"},{"type":"tool-addition","toolName":"working_directory"},{"type":"tool-addition","toolName":"write"}],"role":"developer","id":"{{sessionId}}"},"headerSeq":18},"surfaceOp":"append"} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Tighten this session to read-only.","messageSeqs":[3],"source":{"kind":"fallback"}}} {"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"delegate-write","name":"subagent","args":["{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"} {"type":"tool/call","data":{"turn":2,"step":1,"callId":"delegate-write","name":"subagent","arguments":"{\"description\": \"Delegated write probe\", \"prompt\": \"Use the write tool exactly once with file_path set to exactly the relative path inherited.txt and content escaped. If the write is denied, reply with the single word CHILD_DENIED and the denial marker line; do not retry and do not request escalation. If it succeeds, reply CHILD_WROTE.\"}"}} -{"type":"subagent/catalog","data":{"version":0,"childId":"{{sessionId}}","childCreatedAt":0,"mode":"one-shot","label":"Delegated write probe"}} -{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"delegate-write"},"toolCallId":"delegate-write","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"} +{"type":"subagent/catalog","data":{"version":0,"childId":"{{sessionId}}","childCreatedAt":0,"mode":"continuable","label":"Delegated write probe"}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"}]}} +{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"delegate-write"},"toolCallId":"delegate-write","content":[{"type":"text","text":"started subagent {{sessionId}}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":1}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":2,"step":2}} +{"type":"user/message","data":{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["The delegated child was denied by the sandbox. PARENT_DONE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"The delegated child was denied by the sandbox. PARENT_DONE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} {"type":"step/end","data":{"turn":2,"step":2}} {"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl index 520e96bbc0..1b82a99cf0 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl @@ -1,4 +1,4 @@ -{"type":"session","version":3,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","isSeeded":false,"origin":"subagent","delegationDepth":1} +{"type":"session","version":4,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","parentSession":"{{sessionId}}","isSeeded":false,"origin":"subagent","delegationDepth":1} {"type":"subagent/descriptor","data":{"version":3,"mode":"continuable","provider":"spawn","label":"Return child result","agentProvider":"deepseek-official","agentModel":"deepseek-v4-flash"}} {"type":"sandbox/mode","data":{"mode":"workspace-write","source":"delegation"}} {"type":"approval/policy","data":{"policy":"never","source":"delegation"}} @@ -6,9 +6,9 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} {"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} +{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly CHILD_RESULT and nothing else. Do not call send_message."},{"type":"text","text":"Your parent agent id is \"{{sessionId}}\". Before you finish, send your result to that agent with send_message({ agent_id: \"{{sessionId}}\", message: \"\" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Reply with exactly CHILD_RESULT and","messageSeqs":[8],"source":{"kind":"fallback"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/parent.override.json b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/parent.override.json index 336bcafb3b..91428bb387 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/parent.override.json +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/parent.override.json @@ -33,8 +33,8 @@ "kind": "chunks", "chunks": [ { "type": "block-start", "index": 0, "blockType": "text" }, - { "type": "text-delta", "index": 0, "text": "PARENT_RECEIVED_CHILD_RESULT" }, - { "type": "block-end", "index": 0, "block": { "type": "text", "text": "PARENT_RECEIVED_CHILD_RESULT" } }, + { "type": "text-delta", "index": 0, "text": "PARENT_RECEIVED_SETTLEMENT_NOTICE" }, + { "type": "block-end", "index": 0, "block": { "type": "text", "text": "PARENT_RECEIVED_SETTLEMENT_NOTICE" } }, { "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } }, { "type": "finish", "reason": { "kind": "stop" } } ] diff --git a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl index 3c8e6615eb..f06a75700f 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/stream-json.expected.jsonl @@ -1,19 +1,19 @@ -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Start one continuable subagent and acknowledge its completion notice. Do not call list_agents or send_message."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"}]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"system/message","seq":7,"time":0,"data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Start one continuable subagent and acknowledge its completion notice. Do not call list_agents or send_message."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":9,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"request/context","seq":11,"time":0,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Start one continuable background subagen","messageSeqs":[8],"source":{"kind":"fallback"}}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Start one continuable subagent and","messageSeqs":[8],"source":{"kind":"fallback"}}}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title-llm-request","seq":13,"time":0,"data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Start one continuable subagent and acknowledge its completion notice. Do not call list_agents or send_message.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":14,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call send_message.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"start-child","name":"subagent","args":["{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call send_message.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call send_message.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":15,"time":0,"data":{"turn":1,"step":1,"callId":"start-child","name":"subagent","arguments":"{\"description\":\"Return child result\",\"prompt\":\"Reply with exactly CHILD_RESULT and nothing else. Do not call send_message.\"}"}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"session/title","seq":16,"time":0,"data":{"title":"Subagent settlement","messageSeqs":[8],"source":{"kind":"provider","provider":"session-title-first-prompt-llm","model":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"subagent/catalog","seq":17,"time":0,"data":{"version":0,"childId":"{{sessionId}}","childCreatedAt":0,"mode":"continuable","label":"Return child result"}}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":18,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"start-child"},"content":[{"type":"text","text":"started subagent {{sessionId}}"}],"role":"tool","toolCallId":"start-child","isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":18,"time":0,"data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"start-child"},"toolCallId":"start-child","content":[{"type":"text","text":"started subagent {{sessionId}}"}],"isError":false,"id":"{{sessionId}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":19,"time":0,"data":{"turn":1,"step":1}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":20,"time":0,"data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"}]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":21,"time":0,"data":{"turn":1,"step":2}}} @@ -22,7 +22,7 @@ {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"agent/inbox/spliced","seq":24,"time":0,"data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":25,"time":0,"data":{"turn":1,"step":3}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":26,"time":0,"data":{"content":[{"type":"text","text":"Background subagent {{sessionId}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"CHILD_RESULT"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{sessionId}} finished and will do no further work unless you send it more.","senderSessionId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}} -{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":27,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["PARENT_RECEIVED_CHILD_RESULT"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_RECEIVED_CHILD_RESULT"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}} +{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":27,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_RECEIVED_SETTLEMENT_NOTICE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["PARENT_RECEIVED_SETTLEMENT_NOTICE"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_RECEIVED_SETTLEMENT_NOTICE"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/end","seq":28,"time":0,"data":{"turn":1,"step":3}}} {"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"turn/end","seq":29,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}} -{"type":"result","sessionId":"{{sessionId}}","output":"PARENT_RECEIVED_CHILD_RESULT","usage":{"inputTokens":30,"outputTokens":15}} +{"type":"result","sessionId":"{{sessionId}}","output":"PARENT_RECEIVED_SETTLEMENT_NOTICE","usage":{"inputTokens":30,"outputTokens":15}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl index 7dc7407bff..5ed8464fb7 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/offline-edit/session.expected.jsonl @@ -17,7 +17,7 @@ {"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"sourceEventSeqs":[2],"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}} {"type":"user/message","data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"\nUpdated instructions from: AGENTS.md\n\nThis file changed after it was loaded. Use the following content instead of the previously loaded instructions from this file.\n\nNew workspace instruction after offline edit.\n\n"}],"source":{"kind":"agent-instructions","form":"instructions","changes":[{"action":"replace","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"d8375b516f158718bd3463bc8eb7ed42c011b29f"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial","startsSeries":true}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Remember the workspace instruction.","messageSeqs":[3],"source":{"kind":"fallback"}}} diff --git a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl index 76af22df23..f52d5f06e9 100644 --- a/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl +++ b/apps/cli/tests/profiles/headless/tests/expected/workspace-context-resume/precedence-change/session.expected.jsonl @@ -17,7 +17,7 @@ {"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{sessionId}}"}},"sourceEventSeqs":[2],"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}} {"type":"user/message","data":{"content":[{"type":"text","text":"Acknowledge the current workspace instruction."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"\nThis complete workspace instruction baseline replaces all earlier workspace instruction baselines. The following workspace instructions may be relevant to your work. Use them as guidance when applicable. More specific instructions take precedence over broader ones. They do not override system, developer, or direct user instructions.\n\nInstructions from: AGENTS.md\n\nCurrent AGENTS rule.\n\n\nInstructions from: CLAUDE.md\n\nCurrent CLAUDE rule.\n\n"}],"source":{"kind":"agent-instructions","form":"instructions","baseline":true,"baselineIdentity":"{\"projectRoot\":\"\",\"projectRootMarkers\":[\".git\"],\"maxBytes\":65536,\"maxSourceBytes\":1048576,\"instructionFileCandidates\":[\"AGENTS.md\",\"CLAUDE.md\"],\"localInstructionFileCandidates\":[\"AGENTS.local.md\",\"CLAUDE.local.md\"]}","changes":[{"action":"set","scope":".\u0000AGENTS.md","path":"AGENTS.md","digest":"7f53d2327837129750aef117f9754a001c46cf68"},{"action":"set","scope":".\u0000CLAUDE.md","path":"CLAUDE.md","digest":"5b1e9e3fd759eee6b43ceff899e47fb10c64701a"}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} +{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval policy: ask. Operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial","startsSeries":true}} {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} {"type":"session/title","data":{"title":"Remember the workspace instruction.","messageSeqs":[3],"source":{"kind":"fallback"}}} diff --git a/apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts b/apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts index bbacf9dc15..c8051b05ea 100644 --- a/apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts @@ -29,7 +29,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('full loop: real model + real bas it('runs a bash command on request and reports its output', async () => { workdir = await mkdtemp(join(tmpdir(), 'dsh-full-loop-e2e-')) ctx = await codingHarness(workdir, { personaPrefix: SYSTEM_PROMPT }) - const agent = await ctx.agentLoop.create(SessionId('e2e-loop'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = await ctx.agentLoop.create(SessionId('e2e-loop'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, { cwd: workdir }) agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Run `echo e2e-ok` with the bash tool and tell me its exact output.' }], source: { kind: 'user' } })) await waitForIdle(ctx, agent) diff --git a/apps/cli/tests/profiles/headless/tests/harness.ts b/apps/cli/tests/profiles/headless/tests/harness.ts index c07b4b73fd..cf51dc23a6 100644 --- a/apps/cli/tests/profiles/headless/tests/harness.ts +++ b/apps/cli/tests/profiles/headless/tests/harness.ts @@ -66,6 +66,7 @@ export interface CodingHarnessOptions { export async function codingHarness(workdir: string, options: CodingHarnessOptions = {}): Promise { const ctx = new Context() await mountAgentLoopTestDependencies(ctx, { + workingDirectory: true, systemPrompt: { personaPrefix: options.personaPrefix ?? '' }, }) await ctx.plugin(AgentLoop, { agents: [] }) diff --git a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts index 1c4f3d175e..7a46b1dc89 100644 --- a/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts @@ -47,11 +47,21 @@ const headlessSessionExpected = join(goldensDir, 'headless-profile', 'session.ex const headlessReasoningExpected = join(goldensDir, 'headless-profile', 'reasoning.stderr.expected.txt') const headlessFailureExpected = join(goldensDir, 'headless-profile', 'stderr.expected.txt') const refreshing = process.env.DSH_SNAPSHOT === 'refresh' +/** The shipped base bundle's title output cap, which marks a title request's `max_tokens`. */ +const TITLE_MAX_TOKENS = 4096 interface JsonObject { [key: string]: unknown } +/** Framed user text that distinguishes the auxiliary title request from a main request. */ +const TITLE_PROMPT_MARKER = 'Generate the session title from this JSON array of human messages' + +/** Whether one recorded request body is the auxiliary title request. */ +function isTitleRequest(request: JsonObject): boolean { + return JSON.stringify(request['messages'] ?? null).includes(TITLE_PROMPT_MARKER) +} + interface PersistedLog { readonly content: string readonly header: JsonObject @@ -100,7 +110,7 @@ async function deepseekDefaultsServer( const write = (): void => { // One-shot teardown may cancel background title work after the main response. if (keepAlives-- > 0 - || (options.waitForTitleRequest === true && !requests.some(request => request.max_tokens === 64))) { + || (options.waitForTitleRequest === true && !requests.some(isTitleRequest))) { response.write(': keep-alive\n\n') timer = setTimeout(write, 60) return @@ -258,7 +268,9 @@ describe('headless stream-json snapshots', () => { const actual = logs[0] if (actual === undefined) throw new Error('the headless profile did not persist its session') const context = contextFromLogs([actual.content]) - const session = normalizeSessionSnapshots([actual.content], context)[0]! + const session = normalizeSessionSnapshot(actual.content, context) + const titleRequest = parseJsonl(session).find(event => event.type === 'session/title-llm-request') + expect(titleRequest?.data).toMatchObject({ reasoningEffort: 'off', maxTokens: TITLE_MAX_TOKENS }) if (refreshing) await writeFile(headlessSessionExpected, session) await expectSessionSnapshot(session, context, headlessSessionExpected) expect(session).toContain(task) @@ -610,7 +622,8 @@ describe('headless stream-json snapshots', () => { expect(server.requests).toHaveLength(2) expect(server.paths).toEqual(['/v1/messages', '/v1/messages']) const agentRequest = server.requests.find(request => request.max_tokens === 256_000) - const titleRequest = server.requests.find(request => request.max_tokens === 64) + const titleRequest = server.requests.find(isTitleRequest) + expect(titleRequest?.max_tokens).toBe(TITLE_MAX_TOKENS) expect(agentRequest?.output_config).toEqual({ effort: 'low' }) expect(titleRequest).toBeDefined() const header = (parseJsonl(result.stdout) @@ -659,7 +672,10 @@ describe('headless stream-json snapshots', () => { } const title = await fetch(server.url, { method: 'POST', - body: JSON.stringify({ max_tokens: 64 }), + body: JSON.stringify({ + max_tokens: TITLE_MAX_TOKENS, + messages: [{ role: 'user', content: [{ type: 'text', text: TITLE_PROMPT_MARKER }] }], + }), }) for (;;) { const chunk = await reader.read() @@ -699,10 +715,10 @@ describe('headless stream-json snapshots', () => { expect(result.stderr).toBe('') expect(server.requests).toHaveLength(2) - const agentRequest = server.requests.find(request => request.max_tokens === 1024) - const titleRequest = server.requests.find(request => request.max_tokens === 64) + const agentRequest = server.requests.find(request => !isTitleRequest(request)) + const titleRequest = server.requests.find(isTitleRequest) expect(agentRequest).not.toHaveProperty('max_completion_tokens') - expect(titleRequest).toBeDefined() + expect(titleRequest?.max_tokens).toBe(TITLE_MAX_TOKENS) const header = (parseJsonl(result.stdout) .map(record => record.event) .find((event): event is JsonObject => ( @@ -752,12 +768,12 @@ describe('headless stream-json snapshots', () => { const parent = logs.find(log => typeof log.header.parentSession !== 'string') if (parent === undefined) throw new Error('Agent Teams snapshot did not persist its Lead') const rows = parseJsonl(parent.content) - const workflowChild = logs.find(log => parseJsonl(log.content).some(row => row.type === 'subagent/descriptor' - && (row.data as JsonObject).mode === 'one-shot')) + const workflowChild = logs.find(log => parseJsonl(log.content).some(row => row.type === 'user/message' + && JSON.stringify((row.data as JsonObject).content) === JSON.stringify([{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }]))) if (workflowChild === undefined) throw new Error('Team profile did not persist its workflow child') const workflowRows = parseJsonl(workflowChild.content) expect(workflowRows.find(row => row.type === 'subagent/descriptor')?.data) - .toMatchObject({ mode: 'one-shot', provider: 'spawn' }) + .toMatchObject({ mode: 'continuable', provider: 'spawn' }) expect(workflowRows.filter(row => row.type === 'user/message' && ((row.data as JsonObject).source as JsonObject).kind === 'user').map(row => row.data)) .toEqual([expect.objectContaining({ content: [{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }] })]) @@ -958,13 +974,13 @@ describe('headless stream-json snapshots', () => { await expectHeadlessStream(normalized, streamExpected) }, LOADER_SMOKE_TEST_TIMEOUT_MS) - it('delivers a continuable child result without parent polling', async () => { + it('notifies the parent when a continuable child settles without polling', async () => { const parentReplay = join(settlementScenarioDir, 'parent.replay.jsonl') const parentOverride = join(settlementScenarioDir, 'parent.override.json') const childReplay = join(settlementScenarioDir, 'child.replay.jsonl') const childExpected = join(settlementScenarioDir, 'child.expected.jsonl') const streamExpected = join(settlementScenarioDir, 'stream-json.expected.jsonl') - const task = 'Start one continuable background subagent and answer from its completion notice. Do not call list_agents, send_message, job_output, or job_list.' + const task = 'Start one continuable subagent and acknowledge its completion notice. Do not call list_agents or send_message.' let runCwd = '' const result = await runLoaderSmoke({ label: 'continuable settlement headless stream-json snapshot', @@ -1007,6 +1023,10 @@ describe('headless stream-json snapshots', () => { }) }) expect(notices).toHaveLength(1) + expect(notices[0]).toMatchObject({ + source: { kind: 'subagent-settled', form: 'notice', senderSessionId: child.header.id }, + }) + expect(JSON.stringify(notices[0])).toContain('finished and will do no further work unless you send it more.') expect(JSON.stringify(notices[0])).toContain('CHILD_RESULT') const context = contextFromLogs([parent.content, child.content]) @@ -1022,7 +1042,7 @@ describe('headless stream-json snapshots', () => { const records = parseJsonl(result.stdout) expect(records.at(-1)).toMatchObject({ type: 'result', - output: 'PARENT_RECEIVED_CHILD_RESULT', + output: 'PARENT_RECEIVED_SETTLEMENT_NOTICE', }) const normalized = normalizeHeadlessStream(result.stdout, runCwd) if (refreshing) await writeFile(streamExpected, normalized) diff --git a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts index 48e8f9fc9f..806f081a77 100644 --- a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts @@ -3,15 +3,15 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' -import LlmRuntime, { createUserMessage, ToolCallId, HarnessError } from '@deepseek-ai/dsh-llm' +import { createUserMessage, ToolCallId, HarnessError } from '@deepseek-ai/dsh-llm' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import type { SessionEvent } from '@deepseek-ai/dsh-session' -import SystemPrompt from '@deepseek-ai/dsh-system-prompt' -import ToolRuntime, { RUN_CODE_NAME, defineTool } from '@deepseek-ai/dsh-tools' +import { RUN_CODE_NAME, defineTool } from '@deepseek-ai/dsh-tools' import type { ToolExecutionResult } from '@deepseek-ai/dsh-tools' -import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' +import type { Agent } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local' import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env' @@ -55,12 +55,11 @@ afterEach(async () => { async function ptcModeHarness(cwd: string): Promise { const harness = new Context() - await harness.plugin(LlmRuntime) - await harness.plugin(SessionStore) - await harness.plugin(SessionProjectionRegistry) - await harness.plugin(SystemPrompt, { personaPrefix: PERSONA }) - await harness.plugin(ToolRuntime, { mode: 'ptc' }) - await harness.plugin(AgentRegistry) + await mountAgentLoopTestDependencies(harness, { + workingDirectory: true, + systemPrompt: { personaPrefix: PERSONA }, + tools: { mode: 'ptc' }, + }) await harness.plugin(AgentLoop, { agents: [] }) await harness.plugin(LlmDeepSeek) if (harness.get('subprocess') === undefined) await harness.plugin(LocalSubprocessRuntime) @@ -73,13 +72,12 @@ async function ptcModeHarness(cwd: string): Promise { async function workspacePtcModeHarness(): Promise { const harness = new Context() - await harness.plugin(LlmRuntime) - await harness.plugin(SessionStore) - await harness.plugin(SessionProjectionRegistry) - await harness.plugin(SystemPrompt, { personaPrefix: PERSONA }) - await harness.plugin(ToolRuntime, { mode: 'ptc' }) - await harness.plugin(AgentRegistry) await harness.plugin(LocalFileSystem, { cwd: '/' }) + await mountAgentLoopTestDependencies(harness, { + workingDirectory: true, + systemPrompt: { personaPrefix: PERSONA }, + tools: { mode: 'ptc' }, + }) await harness.plugin(ToolFs) await harness.plugin(AgentInstructions, { maxBytes: 65536 }) await harness.plugin(AgentLoop, { agents: [] }) @@ -131,8 +129,7 @@ async function mountRuntime(harness: Context): Promise { /** Keyless real-process harness for direct typed-binding acceptance tests. */ async function typedPtcModeHarness(): Promise { const harness = new Context() - await harness.plugin(SystemPrompt) - await harness.plugin(ToolRuntime, { mode: 'ptc' }) + await mountAgentLoopTestDependencies(harness, { workingDirectory: true, tools: { mode: 'ptc' } }) await mountRuntime(harness) return harness } @@ -298,7 +295,8 @@ describe('PTC mode typed values: keyless real-process contracts', () => { const agent = { id: SessionId('ptc-cordis'), session: ctx.sessions.create(SessionId('ptc-cordis'), { meta: { cwd: process.cwd() } }), - } as unknown as Agent + ctx, + } as Agent const value = completion(await runCode(ctx, ` const listed = await tools.cordis_inspect_list({}); @@ -328,7 +326,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('PTC mode: real model writes a pr it('collapses the wire tool list to [run_code], bridges sub-calls, and returns curated output', async () => { workdir = await mkdtemp(join(tmpdir(), 'dsh-ptc-e2e-')) ctx = await ptcModeHarness(workdir) - const agent = await ctx.agentLoop.create(SessionId('e2e-ptc'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = await ctx.agentLoop.create(SessionId('e2e-ptc'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, { cwd: workdir }) agent.followup(createUserMessage({ content: [{ diff --git a/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts index 4b5970cd9f..52df6db0d9 100644 --- a/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/subagent-inheritance.expected.e2e.ts @@ -3,7 +3,7 @@ * its child log and confines a real write under a wider deployment default. */ -import { readFile, readdir, writeFile } from 'node:fs/promises' +import { readFile, readdir, realpath, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import { Context } from '@deepseek-ai/cordis' @@ -111,8 +111,8 @@ describe('parent-only override inheritance snapshot', () => { DSH_SNAPSHOT_CHILD_FILES: childReplay, }, prepare: async (runCwd) => { - cwd = runCwd - await seedReadOnlyParent(join(runCwd, '.sessions'), runCwd) + cwd = await realpath(runCwd) + await seedReadOnlyParent(join(cwd, '.sessions'), cwd) }, inspect: async (runCwd) => { // THE physical fact: the child's write never reached the disk. Under @@ -132,11 +132,12 @@ describe('parent-only override inheritance snapshot', () => { const childRecords = child.trimEnd().split('\n').map( line => JSON.parse(line) as Record, ) - expect(childRecords[1]).toMatchObject({ + expect(childRecords.find(record => record.type === 'sandbox/mode')).toMatchObject({ type: 'sandbox/mode', - seq: SessionSeq(0), data: { mode: 'read-only', source: 'delegation' }, }) + expect(childRecords.findIndex(record => record.type === 'sandbox/mode')) + .toBeLessThan(childRecords.findIndex(record => record.type === 'turn/start')) const runtimeContexts = (content: string): string[] => content.trimEnd().split('\n').flatMap((line) => { const record = JSON.parse(line) as { diff --git a/apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts b/apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts index 4ff5fef99c..ce688b60bd 100644 --- a/apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts @@ -27,7 +27,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('todo_write: real model records a it('appends a todo/write event with the model-produced task list', async () => { workdir = await mkdtemp(join(tmpdir(), 'dsh-todo-write-e2e-')) ctx = await codingHarness(workdir, { personaPrefix: TODO_SYSTEM_PROMPT }) - const agent = await ctx.agentLoop.create(SessionId('e2e-todo'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }) + const agent = await ctx.agentLoop.create(SessionId('e2e-todo'), { provider: 'deepseek-official', model: 'deepseek-v4-flash' }, { cwd: workdir }) agent.followup(createUserMessage({ content: [{ type: 'text', text: diff --git a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts index b873ad7d77..aa6da3ad23 100644 --- a/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts +++ b/apps/cli/tests/profiles/headless/tests/workspace-context-resume.expected.e2e.ts @@ -79,7 +79,6 @@ async function seedVisibleBaseline( content: file.content, })), { maxBytes: 65536 }) const config = resolveConfig({ - dshHome: join(cwd, '.dsh'), maxBytes: 65536, ...options.instructionFileCandidates === undefined ? {} diff --git a/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml b/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml index 6507b366d6..42a63474af 100644 --- a/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/workspace-context-resume-snapshot.patch.yml @@ -11,7 +11,6 @@ - id: agent-instructions config: maxBytes: 65536 - dshHome: !!js process.cwd() + '/.dsh' - id: session-persistence-jsonl config: diff --git a/apps/cli/tests/profiles/official-installer-packed.e2e.ts b/apps/cli/tests/profiles/official-installer-packed.e2e.ts new file mode 100644 index 0000000000..3062b5c5b8 --- /dev/null +++ b/apps/cli/tests/profiles/official-installer-packed.e2e.ts @@ -0,0 +1,199 @@ +/** Install actual packed Official providers through the ordinary manager in a source-independent Web process. */ +import { existsSync, globSync, readdirSync, readFileSync, statSync } from 'node:fs' +import { readFile, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { createInterface } from 'node:readline' +import { fileURLToPath } from 'node:url' +import { execa } from 'execa' +import yaml from 'js-yaml' +import { expect, it, onTestFinished } from 'vitest' +import type { BundleInfo, ChangeResult } from '@deepseek-ai/dsh-plugin-manager' +import { getDshRuntimeVersion } from '../../../../packages/boot/app-boot/src/plugin-compatibility.ts' +import { generateOfficialBundleCatalog } from '../../../../scripts/gen-official-bundle-catalog.ts' +import { + REGISTRY_MANIFEST_FILE, + createRegistryManifest, + hostPlatform, + packBundleArtifacts, + readReleaseMembers, + readWorkspacePackages, + selectBundleDependencies, + sourceEvidence, + startBundleRegistry, + verifyBuiltBundles, + writeRegistryManifest, +} from '../../../../scripts/local-bundle-registry.ts' +import { assertInstalledTree, createPackedInstallation } from './packed-installation.ts' + +const root = fileURLToPath(new URL('../../../..', import.meta.url)) +const packages = ['@deepseek-ai/dsh-subagent-claude-code', '@deepseek-ai/dsh-subagent-codex'] +const built = existsSync(join(root, 'apps/cli/lib/bin.js')) + && packages.every(name => existsSync(join(root, 'packages/subagent', name.slice('@deepseek-ai/dsh-'.length), 'lib/index.js'))) + +interface Observation { + initial: BundleInfo[] + sessionCount: number + steps: Array<{ + name: string + install: ChangeResult + savedVersion: string + installed: BundleInfo + providerEnabled: boolean + nativeArtifact: boolean + localDependency: string + presets: Array<{ id: string; broken?: string }> + unavailable: ChangeResult + unchangedAfterUnavailable: boolean + off: ChangeResult + disabled: BundleInfo + on: ChangeResult + enabled: BundleInfo + removed: ChangeResult + absent: BundleInfo + }> +} + +/** + * Read the tail of the newest plugin-manager pnpm log, for a failure the child cannot report itself. + * @param profile - isolated profile directory. + * @returns The last log lines, or a marker when no run logged anything. + */ +function pnpmLogTail(profile: string): string { + const logs = join(profile, '.plugin-manager', 'logs') + const newest = existsSync(logs) + ? readdirSync(logs, { withFileTypes: true }).filter(entry => entry.isDirectory()) + .map(entry => join(logs, entry.name, 'pnpm.log')) + .filter(path => existsSync(path)) + .sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs)[0] + : undefined + if (newest === undefined) return '--- plugin-manager log: none ---' + return `--- ${newest} ---\n${readFileSync(newest, 'utf8').split('\n').slice(-40).join('\n')}` +} + +it.skipIf(!built)('installs the exported registry\'s local closure and switches Official bundles through the generic manager', { + // Real packing of two dependency closures, an isolated product installation, and an isolated plugin installation + // own this budget; no native model request runs. + timeout: 900_000, + retry: 0, +}, async () => { + // The product under test is a real source-independent installation that ships neither provider, so both stay + // available to the Official catalog and only the local registry can supply them. + const fixture = await createPackedInstallation('dsh-official-packed-', []) + const { temporary, installation, home, profile, environment } = fixture + + // The registry packs and serves the catalog's own local dependency closure; no checkout link or file: override + // stands in for a package the registry should answer. + const entries = generateOfficialBundleCatalog(root).map(entry => entry.packageName) + const workspace = readWorkspacePackages(root) + verifyBuiltBundles({ entries, packages: workspace }) + const selection = selectBundleDependencies({ entries, packages: workspace, platform: hostPlatform() }) + const runDirectory = join(temporary, 'registry') + const artifacts = await packBundleArtifacts({ + root, destination: runDirectory, members: selection.members, release: readReleaseMembers(root), + }) + const version = artifacts.find(artifact => artifact.name === packages[0])?.version + if (version === undefined) throw new Error('the local registry packed no claude-code provider') + for (const name of packages) expect(artifacts.find(artifact => artifact.name === name), name).toMatchObject({ version }) + await writeRegistryManifest(join(runDirectory, REGISTRY_MANIFEST_FILE), createRegistryManifest({ + buildId: 'packed-e2e', + source: sourceEvidence(root, getDshRuntimeVersion()), + entries, + workspaceNames: selection.workspaceNames, + artifacts, + })) + const registry = await startBundleRegistry({ + directory: runDirectory, artifacts, workspaceNames: selection.workspaceNames, + }) + onTestFinished(() => registry.close()) + + await writeFile(join(profile, 'package.json'), JSON.stringify({ name: 'packed-official-profile', private: true, dependencies: {}, + dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] } }, + })) + // Only the settings a real profile gets. No override, ignore list, or scoped .npmrc may route a package around the + // registry, so every dependency of the packed bundles is answered by this run or by its fixed upstream redirect. + await writeFile(join(profile, 'pnpm-workspace.yaml'), 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n') + const userConfig = join(temporary, 'empty.npmrc') + await writeFile(userConfig, '') + environment.npm_config_userconfig = userConfig + const observer = join(temporary, 'official-installer-observer.mjs') + await writeFile(observer, await readFile(new URL('./fixtures/official-installer-observer.mjs', import.meta.url), 'utf8')) + const patch = join(temporary, 'official-installer.patch.yml') + await writeFile(patch, yaml.dump([{ insert: [{ id: 'official-installer-observer', name: observer, + config: { + registry: registry.url, + profile, + packages, + localDependency: '@deepseek-ai/dsh-brand', + nativePackages: { + // The Agent SDK publishes a musl variant of its Linux artifact, which is what pnpm installs there. + '@deepseek-ai/dsh-subagent-claude-code': `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}${hostPlatform().libc?.[0] === 'musl' ? '-musl' : ''}`, + '@deepseek-ai/dsh-subagent-codex': `@openai/codex-${process.platform}-${process.arch}`, + }, + }, + }] }])) + const child = execa(process.execPath, [join(installation, 'node_modules/@deepseek-ai/dsh/lib/bin.js'), + 'web', '--patch', patch, '--no-open', '--port', '0'], { + cwd: temporary, env: environment, extendEnv: false, reject: false, timeout: 240_000, + }) + if (child.stdout === null) throw new Error('installed Web process has no stdout') + const lines = createInterface({ input: child.stdout }) + // A failure in the body must stay the reported one: teardown asserts only when the body + // passed, so a Web process that does not exit cannot replace the real error. + let failure: unknown + try { + const observed = await new Promise((resolveObservation, reject) => { + lines.on('line', (line) => { + if (!line.startsWith('PACKED_OFFICIAL_RESULT=')) return + const report = JSON.parse(line.slice('PACKED_OFFICIAL_RESULT='.length)) as { value?: Observation; error?: string } + if (report.error !== undefined) reject(new Error(report.error)) + else if (report.value !== undefined) resolveObservation(report.value) + }) + void child.then((result) => { reject(new Error(`Web process exited before observation:\n${result.stdout}\n${result.stderr}`)) }) + }) + for (const name of packages) expect(observed.initial.find(entry => entry.name === name)).toMatchObject({ + official: true, availability: 'missing', installed: false, enabled: false, installTarget: { spec: `${name}@${version}`, version }, + }) + expect(observed.steps).toHaveLength(packages.length) + for (const step of observed.steps) { + expect(step.install, JSON.stringify(step.install)).toMatchObject({ application: 'applied', enabled: true, version }) + expect(step.savedVersion).toBe(version) + expect(step.installed).toMatchObject({ official: true, availability: 'profile', enabled: true, installed: true, version }) + expect(step.installed.rows.filter(row => row.preset !== undefined).map(row => row.preset).sort()) + .toEqual(['preset-cordis', 'preset-ptc', 'preset-standard']) + expect(step.providerEnabled).toBe(true) + expect(step.nativeArtifact).toBe(true) + // A transitive repository package the closure needs can only have come from this run's artifacts. + expect(step.localDependency).toBe(version) + expect(step.presets.every(preset => preset.broken === undefined)).toBe(true) + expect(step.unavailable).toMatchObject({ application: 'failed', stage: 'install' }) + expect(step.unchangedAfterUnavailable).toBe(true) + expect(step.off).toMatchObject({ application: 'applied' }) + expect(step.disabled).toMatchObject({ official: true, installed: true, enabled: false }) + expect(step.on).toMatchObject({ application: 'applied' }) + expect(step.enabled).toMatchObject({ official: true, installed: true, enabled: true }) + expect(step.removed, JSON.stringify(step.removed)).toMatchObject({ application: 'applied' }) + expect(step.absent).toMatchObject({ official: true, availability: 'missing', enabled: false, installed: false }) + } + expect(observed.sessionCount).toBe(0) + expect(globSync('sessions/**/*.jsonl*', { cwd: home })).toEqual([]) + await assertInstalledTree(profile) + // The completed run records every package it served, so a failure can be matched to the exact checkout. + const record = JSON.parse(await readFile(join(runDirectory, REGISTRY_MANIFEST_FILE), 'utf8')) as { buildId: string; packages: unknown[] } + expect(record.buildId).toBe('packed-e2e') + expect(record.packages).toHaveLength(selection.members.length) + expect(record.packages).toEqual(expect.arrayContaining([expect.objectContaining({ name: packages[0], version })])) + } catch (error) { + // The manager's own pnpm output names a stalled or refused operation, which the child's stdout cannot show. + failure = error instanceof Error ? new Error(`${error.message}\n\n${pnpmLogTail(profile)}`) : error + } finally { + lines.close() + child.kill('SIGTERM') + const result = await child + if (failure === undefined) { + // The installed Web process stops on SIGTERM within its own budget; a timed-out child is a teardown defect. + expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false) + expect(result.exitCode === 0 || result.signal === 'SIGTERM', result.stderr).toBe(true) + } + } + if (failure !== undefined) throw failure +}) diff --git a/apps/cli/tests/profiles/packed-installation.ts b/apps/cli/tests/profiles/packed-installation.ts new file mode 100644 index 0000000000..6cfcd2ebb0 --- /dev/null +++ b/apps/cli/tests/profiles/packed-installation.ts @@ -0,0 +1,190 @@ +/** Pack and install a source-independent product for idle Web-profile integration tests. */ +import { createRequire } from 'node:module' +import { existsSync, globSync } from 'node:fs' +import { lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { dirname, join, relative, resolve, sep } from 'node:path' +import { fileURLToPath } from 'node:url' +import { execa } from 'execa' +import yaml from 'js-yaml' +import { expect, onTestFinished } from 'vitest' +import { pnpmCommand } from '../../../../scripts/release/process.ts' +import { packedWorkspaceClosure, type WorkspacePackage } from '../../../../packages/sandbox/sandbox-local/tests/packed-workspace-closure.ts' + +const root = fileURLToPath(new URL('../../../..', import.meta.url)) + +/** Capture workspace dependency links without following their targets. */ +async function workspaceLinks(directories: string[]): Promise> { + const links: Record = {} + for (const directory of directories) { + const modules = join(directory, 'node_modules') + links[modules] = existsSync(modules) ? 'directory' : 'absent' + if (!existsSync(modules)) continue + for (const entry of await readdir(modules, { withFileTypes: true })) { + const path = join(modules, entry.name) + const paths = entry.name.startsWith('@') && entry.isDirectory() + ? (await readdir(path)).map(name => join(path, name)) : [path] + for (const dependency of paths) { + const stat = await lstat(dependency) + links[dependency] = stat.isSymbolicLink() ? await readlink(dependency) : stat.isDirectory() ? 'directory' : 'file' + } + } + } + return links +} + +/** Reject workspace links throughout an installed dependency tree. + * @param directory - Installation or profile whose links must remain inside its own tree. + */ +export async function assertInstalledTree(directory: string): Promise { + const installedRoot = await realpath(directory) + const visited = new Set() + const visit = async (path: string): Promise => { + const resolved = await realpath(path) + expect(resolved === installedRoot || resolved.startsWith(installedRoot + sep), `${path} escapes its installation`).toBe(true) + if (visited.has(resolved)) return + visited.add(resolved) + for (const entry of await readdir(path, { withFileTypes: true })) { + const child = join(path, entry.name) + if (entry.isSymbolicLink()) { + // Removed packages can leave dangling .bin shims; those links must also stay inside the profile. + const target = existsSync(child) ? await realpath(child) : resolve(dirname(child), await readlink(child)) + expect(target.startsWith(installedRoot + sep), `${child} escapes its installation`).toBe(true) + } else if (entry.isDirectory()) await visit(child) + } + } + await visit(directory) +} + +/** Actual tarballs and an isolated installed CLI, with an initially empty Web profile. */ +export interface PackedInstallation { + temporary: string + installation: string + home: string + profile: string + archives: string + environment: NodeJS.ProcessEnv + tarballs: Map + members: Map + externalVersions: Record + packageCommand: (args: string[], cwd: string) => Promise<{ stdout: string; stderr: string }> +} + +/** Build the packed product closure and additional packages without changing workspace dependency links. + * Native confinement and Session-writing operations are outside this idle-profile fixture. + * @param temporaryPrefix - Unique temporary-directory prefix identifying the owning test. + * @param extraPackages - Additional workspace packages to pack without installing in the product. + * @returns Isolated installation paths, tarballs, environment, and checked pnpm invocation. + */ +export async function createPackedInstallation(temporaryPrefix: string, extraPackages: readonly string[]): Promise { + const temporary = await realpath(await mkdtemp(join(tmpdir(), temporaryPrefix))) + onTestFinished(() => rm(temporary, { recursive: true, force: true })) + const installation = join(temporary, 'installation') + const home = join(temporary, 'home') + const profile = join(home, 'profiles', 'web') + const archives = join(temporary, 'archives') + await mkdir(profile, { recursive: true }) + await mkdir(archives) + const environment: NodeJS.ProcessEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => ( + !/KEY|SECRET|TOKEN|PASSWORD/.test(key) && key !== 'NODE_OPTIONS' && key !== 'NODE_PATH' + ))) + Object.assign(environment, { + DSH_HOME: home, + DSH_AGENTS_HOME: join(temporary, 'agents'), + DSH_TELEMETRY_DISABLED: '1', + CI: 'true', + }) + const [pnpm, ...prefix] = pnpmCommand() + const packageCommand = async (args: string[], cwd: string) => { + const result = await execa(pnpm, [...prefix, ...args], { + cwd, env: environment, extendEnv: false, timeout: 180_000, reject: false, + }) + expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false) + expect(result.signal, result.stderr).toBeUndefined() + expect(result.exitCode, `${result.stdout}\n${result.stderr}`).toBe(0) + return result + } + const packages = new Map() + for (const path of globSync([ + 'apps/*/package.json', 'packages/*/*/package.json', 'vendor/*/package.json', 'native/system/packages/*/package.json', + ], { cwd: root })) { + const manifest = JSON.parse(await readFile(join(root, path), 'utf8')) as Record + if (typeof manifest.name !== 'string') throw new Error(`${path} has no package name`) + packages.set(manifest.name, { name: manifest.name, manifest, directory: dirname(join(root, path)) }) + } + const sourceDirectories = [root, ...[...packages.values()].map(member => member.directory)] + const initialLinks = await workspaceLinks(sourceDirectories) + onTestFinished(async () => { expect(await workspaceLinks(sourceDirectories)).toEqual(initialLinks) }) + const closure = packedWorkspaceClosure('@deepseek-ai/dsh', packages) + // These idle-profile tests create no Session or confined process. The native + // platform packages load lazily for those operations and have separate smokes. + const supportsIdleProfile = (member: WorkspacePackage) => ( + !member.name.startsWith('@deepseek-ai/node-addon-system-') + && (!Array.isArray(member.manifest.os) || member.manifest.os.includes(process.platform)) + && (!Array.isArray(member.manifest.cpu) || member.manifest.cpu.includes(process.arch)) + ) + const runtimeMembers = closure.filter(supportsIdleProfile) + const members = new Map(runtimeMembers.map(member => [member.name, member])) + for (const name of extraPackages) { + for (const member of packedWorkspaceClosure(name, packages).filter(supportsIdleProfile)) members.set(member.name, member) + } + // Packing reads package-owned files; installation never runs in the workspace. + await packageCommand([ + 'pack', '--recursive', '--workspace-concurrency=4', + ...[...members.keys()].map(name => `--filter=${name}`), '--pack-destination', archives, + ], root) + const tarballs = new Map([...members.values()].map((member) => { + if (typeof member.manifest.version !== 'string') throw new Error(`${member.name} has no version`) + const name = member.name.replaceAll('/', '-').replace('@', '') + const archive = join(archives, `${name}-${member.manifest.version}.tgz`) + expect(existsSync(archive), `missing packed payload for ${member.name}`).toBe(true) + return [member.name, archive] + })) + const workspaceSettings = yaml.load(await readFile(join(root, 'pnpm-workspace.yaml'), 'utf8')) as { + allowBuilds: Record + patchedDependencies: Record + minimumReleaseAgeExclude: string[] + overrides: Record + } + const externalVersions: Record = {} + for (const member of members.values()) { + const require = createRequire(join(member.directory, 'package.json')) + for (const section of ['dependencies', 'optionalDependencies', 'peerDependencies']) { + const dependencies = member.manifest[section] as Record | undefined + for (const [name, range] of Object.entries(dependencies ?? {})) { + if (range.startsWith('workspace:')) continue + const manifest = require.resolve.paths(name)?.map(path => join(path, name, 'package.json')).find(path => existsSync(path)) + if (manifest === undefined) continue + const installed = JSON.parse(await readFile(manifest, 'utf8')) as { version: string } + externalVersions[`${name}@${range}`] = installed.version + } + } + } + await mkdir(installation) + for (const path of Object.values(workspaceSettings.patchedDependencies)) { + await mkdir(dirname(join(installation, path)), { recursive: true }) + await writeFile(join(installation, path), await readFile(join(root, path))) + } + await writeFile(join(installation, 'package.json'), JSON.stringify({ + name: 'packed-product-installation', private: true, + dependencies: Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])), + })) + await writeFile(join(installation, 'pnpm-workspace.yaml'), yaml.dump({ + packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false, allowUnusedPatches: true, + ignoredOptionalDependencies: closure.filter(member => !members.has(member.name)).map(member => member.name), + allowBuilds: { + ...workspaceSettings.allowBuilds, + [`@deepseek-ai/dsh-subprocess-local@file:${relative(installation, tarballs.get('@deepseek-ai/dsh-subprocess-local')!).split(sep).join('/')}`]: true, + }, + patchedDependencies: workspaceSettings.patchedDependencies, + minimumReleaseAgeExclude: workspaceSettings.minimumReleaseAgeExclude, + overrides: { + ...workspaceSettings.overrides, + ...externalVersions, + ...Object.fromEntries(runtimeMembers.map(member => [member.name, `file:${tarballs.get(member.name)!}`])), + }, + })) + await packageCommand(['install', '--prefer-offline', '--no-frozen-lockfile'], installation) + await assertInstalledTree(installation) + return { temporary, installation, home, profile, archives, environment, tarballs, members, externalVersions, packageCommand } +} diff --git a/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts b/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts index e90f5b4b54..3bdc2e1cc5 100644 --- a/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts +++ b/apps/cli/tests/profiles/sdk/dynamic-tool-cache.e2e.ts @@ -40,6 +40,35 @@ function totalInput(usage: TokenUsage): number { return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) } +/** Tool calls the fixture recorded, in execution order. */ +async function recordedCalls(path: string): Promise { + return (await readFile(path, 'utf8')).trim().split('\n') +} + +/** + * Assert the model called `earlier` and then `later`. The model owns its plan beyond the required + * calls, so extra add/remove cycles it chooses to make are legitimate and are not counted here. + */ +function expectCalledBefore(calls: string[], earlier: string, later: string): void { + const earlierIndex = calls.indexOf(earlier) + expect(earlierIndex, `${earlier} was never called`).toBeGreaterThanOrEqual(0) + const laterIndex = calls.indexOf(later, earlierIndex + 1) + expect(laterIndex, `${later} was never called after ${earlier}`).toBeGreaterThan(earlierIndex) +} + +/** + * Net tool-registry change between two consecutive requests, matching how the agent loop derives + * a `developer/message` from the difference between the current and previous request headers. + */ +function registryChanges(previous: ObservedRequest, request: ObservedRequest): { type: string; toolName: string }[] { + const before = new Set(previous.body.tools?.map(tool => tool.name)) + const after = new Set(request.body.tools?.map(tool => tool.name)) + return [ + ...[...after].filter(name => !before.has(name)).map(toolName => ({ type: 'tool-addition', toolName })), + ...[...before].filter(name => !after.has(name)).map(toolName => ({ type: 'tool-removal', toolName })), + ] +} + describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with real DeepSeek', () => { it.each([false, true])('keeps the preceding conversation cached after a tool addition (prompt update: %s)', { retry: 0 }, async (updatePrompt) => { const root = await mkdtemp(join(tmpdir(), 'dsh-sdk-tool-cache-')) @@ -80,9 +109,10 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea await run(`Keep these reference records. Do not call tools. Reply READY.\n${records}`) await run('Keep the reference records. Do not call tools. Reply READY.') expect(await run(`Use cache_tool_control with action add. After it returns, call ${sampleTool} exactly once and report the generated sample label. This is synthetic example data for this run. Call these separately in order.`)).toContain(sampleLabel) - expect(await readFile(callsPath, 'utf8')).toBe('add\nsample\n') - expect(await run('Use cache_tool_control with action remove. After it returns, reply REMOVED and do not call the sample tool.')).toContain('REMOVED') - expect(await readFile(callsPath, 'utf8')).toBe('add\nsample\nremove\n') + expectCalledBefore(await recordedCalls(callsPath), 'add', 'sample') + expect(await run('Use cache_tool_control with action remove. Whether it removes the sample tool or reports that it is not registered, reply REMOVED and do not call the sample tool.')).toContain('REMOVED') + const calls = await recordedCalls(callsPath) + expectCalledBefore(calls, 'sample', 'remove') const requests = (await readFile(evidencePath, 'utf8')).trim().split('\n').map(line => JSON.parse(line) as ObservedRequest) expect(events.filter(event => event.type === 'assistant/attempt')).toHaveLength(0) @@ -113,9 +143,15 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('SDK native tool updates with rea expect(added.body.messages.flatMap(message => message.content)).toContainEqual({ type: 'tool_addition', tool: { type: 'tool_reference', name: sampleTool } }) expect(removed.body.tools?.map(tool => tool.name)).toEqual(['cache_tool_control']) expect(removed.body.messages.flatMap(message => message.content).some(block => block.type === 'tool_addition')).toBe(false) - expect(events.filter(event => event.type === 'developer/message').map(event => event.data.message.content)).toEqual([ - [{ type: 'tool-addition', toolName: sampleTool }], - [{ type: 'tool-removal', toolName: sampleTool }], - ]) + // Transcript registry history is derived from the tool lists the provider saw, not from call + // counts: a model-chosen extra add/remove cycle changes both sides or neither. + const expectedRegistryHistory = requests.slice(1).flatMap((request, index) => { + const previous = requests[index] + if (previous === undefined) throw new Error('Missing preceding request') + const content = registryChanges(previous, request) + return content.length > 0 ? [content] : [] + }) + expect(events.filter(event => event.type === 'developer/message').map(event => event.data.message.content)) + .toEqual(expectedRegistryHistory) }) }) diff --git a/apps/cli/tests/profiles/sdk/fixtures/subagent-dsh-sdk-release-on-job-output.ts b/apps/cli/tests/profiles/sdk/fixtures/subagent-dsh-sdk-release-on-job-output.ts deleted file mode 100644 index 554f62dcb1..0000000000 --- a/apps/cli/tests/profiles/sdk/fixtures/subagent-dsh-sdk-release-on-job-output.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** Release the gated background SDK child only after job_output starts waiting. */ - -import { writeFileSync } from 'node:fs' -import type { Context } from '@deepseek-ai/cordis' - -export const name = 'subagent-dsh-sdk-release-on-job-output' -export const inject = ['tools'] - -/** - * Register the test-only execution-order barrier. - * @param ctx - parent runtime context carrying the tool execution waterfall. - */ -export function apply(ctx: Context): void { - ctx.on('tools/execute', async (exec, next) => { - const delegated = next() - if (exec.name === 'job_output') { - writeFileSync('.dsh-sdk-background-release', 'release\n') - } - return delegated - }, { prepend: true }) -} diff --git a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts index 56dec2af4b..64c1c20ab3 100644 --- a/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts +++ b/apps/cli/tests/profiles/sdk/keyless-smoke.e2e.ts @@ -303,6 +303,7 @@ describe('Python SDK dsh profile keyless smoke', () => { expect(tools.map(tool => tool.name)).toEqual([ process.platform === 'win32' ? 'pwsh' : 'bash', ...(editorEnabled ? ['str_replace_editor'] : []), + 'working_directory', ]) expect(modelRequests).toHaveLength(editorEnabled ? 3 : 1) if (editorEnabled) { diff --git a/apps/cli/tests/profiles/sdk/office-cli.e2e.ts b/apps/cli/tests/profiles/sdk/office-cli.e2e.ts index cc851efd23..d94a88a304 100644 --- a/apps/cli/tests/profiles/sdk/office-cli.e2e.ts +++ b/apps/cli/tests/profiles/sdk/office-cli.e2e.ts @@ -108,7 +108,7 @@ it.skipIf(!process.env.DEEPSEEK_API_KEY || process.platform === 'win32')(`agent expect(existsSync(decoyMarker), 'the agent must not invoke system Office when the bundled CLI is supplied').toBe(false) expect(commandText, 'the skill supplies an entry; searching for other Office binaries is unnecessary').not.toMatch(/(?:which|whereis|command -v|find).{0,100}(?:libreoffice|soffice)/iu) const capabilities = JSON.parse(await readFile(join(root, 'capabilities.json'), 'utf8')) as { runtime: { version: string; cliPath: string } } - expect(capabilities.runtime.version).toBe('0.1.1') + expect(capabilities.runtime.version).toBe('0.1.5') expect(capabilities.runtime.cliPath).toMatch(/lib[/\\]cli\.js$/u) const pdf = await readFile(join(root, 'result.pdf')) expect(pdf.subarray(0, 5).toString()).toBe('%PDF-') diff --git a/apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts b/apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts new file mode 100644 index 0000000000..5ed381e1d0 --- /dev/null +++ b/apps/cli/tests/profiles/web/tests/public-url.expected.e2e.ts @@ -0,0 +1,221 @@ +/** Built Web-profile acceptance for the advertised public root and browser authentication behind a prefix-stripping proxy. */ + +import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { request as httpRequest, type IncomingMessage } from 'node:http' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { createInterface } from 'node:readline' +import { Readable } from 'node:stream' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { execa } from 'execa' +import { describe, expect, it } from 'vitest' +import { startPrefixProxy } from '../../../../../../apps/web/tests/prefix-proxy.ts' +import { PROCESS_SHUTDOWN_TIMEOUT_MS } from '../../../../src/process-shutdown.ts' + +const repoRoot = fileURLToPath(new URL('../../../../../../', import.meta.url)) +const dshBin = join(repoRoot, 'apps/cli/lib/bin.js') +const builtArtifactsExist = existsSync(dshBin) + +/** Browser-facing mount the reference proxy serves and strips. */ +const MOUNT = '/tools/dsh/' + +/** One live Web child, as `execa` types it. */ +type WebChild = ReturnType + +/** Settled result of a stopped child, carrying the stderr a case asserts on. */ +type StoppedWeb = Awaited + +/** Start one keyless Web process with an outer lifetime deadline. */ +function spawnWeb(root: string, patch: string, interrupt: string, flags: string[]) { + return execa(process.execPath, [ + dshBin, + '--profile', 'web', + '--patch', patch, + '--no-open', + '--port', '0', + ...flags, + ], { + cwd: root, + env: { + ...process.env, + DEEPSEEK_API_KEY: 'keyless-web-public-url-no-call', + DSH_AGENTS_HOME: join(root, '.agents'), + DSH_HOME: join(root, 'home'), + DSH_TELEMETRY_DISABLED: '1', + NODE_NO_WARNINGS: '1', + WEB_INTERRUPT_FILE: interrupt, + }, + input: '', + reject: false, + timeout: 90_000, + killSignal: 'SIGKILL', + }) +} + +/** One booted Web process: its child, bound port, printed startup URL, and bounded stop. */ +interface RunningWeb { + child: WebChild + port: number + startup: URL + /** Stop the child within the shutdown deadline; every caller shares the one settlement. */ + stop(): Promise +} + +/** + * Boot the built CLI and wait for its `dsh web:` line and bound listener port. + * The marker plugin also polls for the interrupt file and emits `SIGTERM` on + * the child's own process, because Windows has no deliverable SIGTERM. + */ +async function bootWeb(root: string, flags: string[]): Promise { + const marker = join(root, 'listen-port.mjs') + const interrupt = join(root, 'interrupt') + writeFileSync(marker, [ + "import { existsSync } from 'node:fs'", + "export const name = 'public-url-listen-port'", + "export const inject = ['webServer']", + 'export function apply(ctx) {', + " console.log('public-url-listen-port: ' + ctx.webServer.port)", + ' const heartbeat = setInterval(() => {', + ' if (!existsSync(process.env.WEB_INTERRUPT_FILE)) return', + ' clearInterval(heartbeat)', + " process.emit('SIGTERM')", + ' }, 20)', + ' ctx.effect(() => () => { clearInterval(heartbeat) })', + '}', + '', + ].join('\n')) + const patch = join(root, 'extra.yml') + writeFileSync(patch, `- insert:\n - name: ${JSON.stringify(pathToFileURL(marker).href)}\n`) + const child = spawnWeb(root, patch, interrupt, flags) + const lines = createInterface({ input: child.stdout }) + const ready = Promise.withResolvers() + let stopping: Promise | undefined + let port: number | undefined + let startup: URL | undefined + const announced = (): void => { + if (port === undefined || startup === undefined) return + ready.resolve({ child, port, startup, stop: () => stopping ??= stop(child, interrupt) }) + } + lines.on('line', (line) => { + if (line.startsWith('public-url-listen-port: ')) port = Number(line.slice('public-url-listen-port: '.length)) + if (line.startsWith('dsh web: http')) startup = new URL(line.slice('dsh web: '.length)) + announced() + }) + void child.then((result) => { + ready.reject(new Error(`Web exited before readiness (code ${String(result.exitCode)})\n${result.stderr}`)) + }) + let deadline: NodeJS.Timeout | undefined + const expired = new Promise((_resolve, reject) => { + deadline = setTimeout(() => { reject(new Error('Web profile did not print its URL line and bound port')) }, 60_000) + }) + try { + return await Promise.race([ready.promise, expired]) + } catch (error) { + child.kill('SIGKILL') + await child + throw error + } finally { + clearTimeout(deadline) + lines.close() + } +} + +/** Boot one Web process in a private root, run `scenario`, then always stop it and remove the root. */ +async function withWeb(flags: string[], scenario: (web: RunningWeb) => Promise): Promise { + const root = mkdtempSync(join(tmpdir(), 'dsh-web-public-url-')) + let web: RunningWeb | undefined + try { + web = await bootWeb(root, flags) + await scenario(web) + } finally { + try { + if (web !== undefined) await web.stop() + } finally { + rmSync(root, { recursive: true, force: true }) + } + } +} + +/** Ask the child to shut down through its interrupt marker within a bounded grace period, then force-kill it. */ +async function stop(child: WebChild, interrupt: string) { + writeFileSync(interrupt, '') + const deadline = setTimeout(() => { child.kill('SIGKILL') }, PROCESS_SHUTDOWN_TIMEOUT_MS * 2) + try { + return await child + } finally { + clearTimeout(deadline) + } +} + +/** Assert the child reached its own clean exit rather than the fixture's deadline. */ +function expectCleanExit(stopped: StoppedWeb): void { + expect(stopped.timedOut, stopped.stderr).toBe(false) + expect(stopped.signal, stopped.stderr).toBeUndefined() + expect(stopped.exitCode, stopped.stderr).toBe(0) +} + +/** + * One HTTP request that connects to an explicit transport address while + * presenting an explicit browser-facing authority, as a proxy would. + */ +async function request( + dial: string, port: number, path: string, host: string, method: 'GET' | 'POST', cookie?: string, +): Promise { + const { promise, resolve, reject } = Promise.withResolvers() + const req = httpRequest({ + host: dial, port, path, method, + headers: { host, 'content-type': 'application/json', ...cookie === undefined ? {} : { cookie } }, + }, resolve) + req.once('error', reject) + req.setTimeout(10_000, () => { req.destroy(new Error('HTTP request timed out')) }) + req.end(method === 'POST' + ? JSON.stringify({ type: 'client-request', rpcId: 'public-url', method: 'session/create', payload: { args: { request: {} } } }) + : undefined) + const response = await promise + const headers = new Headers() + for (let i = 0; i < response.rawHeaders.length; i += 2) { + headers.append(response.rawHeaders[i]!, response.rawHeaders[i + 1]!) + } + return new Response(Readable.toWeb(response) as ReadableStream, { status: response.statusCode!, headers }) +} + +/** Establish the browser session needed by the protected API, keeping the minted cookie's own attributes. */ +async function exchange( + dial: string, port: number, path: string, host: string, +): Promise<{ cookie: string; setCookie: string }> { + const response = await request(dial, port, path, host, 'GET') + const setCookie = response.headers.get('set-cookie') + await response.arrayBuffer() + if (setCookie === null) throw new Error(`entry returned ${String(response.status)} without a browser cookie`) + return { cookie: setCookie.split(';', 1)[0]!, setCookie } +} + +describe.skipIf(!builtArtifactsExist)('dsh Web profile advertised public root', () => { + it('serves authenticated requests at the advertised mount and at loopback', async () => { + await withWeb(['--public-url', `http://gateway.example${MOUNT.slice(0, -1)}`, '--trusted-host', 'gateway.example'], async (web) => { + expect(web.startup.origin).toBe('http://gateway.example') + expect(web.startup.pathname).toBe(MOUNT) + expect(web.startup.searchParams.get('token')).not.toBeNull() + const proxy = await startPrefixProxy({ prefix: MOUNT }) + try { + proxy.setTarget(web.port) + const entries = [ + { dial: '127.0.0.1', port: proxy.port, prefix: MOUNT, host: web.startup.host }, + { dial: '127.0.0.1', port: web.port, prefix: '/', host: `127.0.0.1:${String(web.port)}` }, + ] + for (const entry of entries) { + const { cookie } = await exchange(entry.dial, entry.port, `${entry.prefix}${web.startup.search}`, entry.host) + const response = await request(entry.dial, entry.port, `${entry.prefix}api/session/create`, entry.host, 'POST', cookie) + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ result: { ok: true } }) + } + const foreign = await request('127.0.0.1', proxy.port, `${MOUNT}api/session/create`, 'evil.example', 'POST') + expect(foreign.status).toBe(403) + await foreign.arrayBuffer() + } finally { + await proxy.close() + } + expectCleanExit(await web.stop()) + }) + }) +}) diff --git a/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.spec.ts b/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.spec.ts new file mode 100644 index 0000000000..06f0c67f86 --- /dev/null +++ b/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.spec.ts @@ -0,0 +1,38 @@ +/** Runtime evidence keeps retained experimental names visible through installed aliases. */ + +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { expect, it, onTestFinished } from 'vitest' +import { EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS } from '../../../../../../scripts/experimental-package-policy.ts' +import { experimentalRuntimeReferences } from './runtime-roster-isolation.ts' +import type { RuntimeRoster } from './runtime-roster.ts' + +it.each(Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('finds retained experimental package %s in real roster fields', (name) => { + const root = mkdtempSync(join(tmpdir(), 'dsh-runtime-roster-policy-')) + onTestFinished(() => { rmSync(root, { recursive: true, force: true }) }) + const directory = join(root, 'node_modules/ordinary-alias') + mkdirSync(directory, { recursive: true }) + writeFileSync(join(directory, 'package.json'), JSON.stringify({ name })) + const module = pathToFileURL(join(directory, 'index.js')).href + const subpath = `${name}/client` + const external = `${name}/external` + const injected = `${name}/injected` + const batched = `${name}/batch` + const roster: RuntimeRoster = { + entries: [{ id: 'entry', name, state: 1 }], + plugins: [{ name: 'plugin', owner: subpath, state: 1, modules: [module] }], + modules: [module], + client: { + rev: 'fixture', + entries: [{ id: 'ordinary-client', url: '/client.js', rev: 'fixture', inject: [injected], external: [external] }], + batches: [{ phase: 'application', url: '/batch.js', rev: 'fixture', entries: [batched] }], + }, + } + expect(experimentalRuntimeReferences(roster)).toEqual([name, subpath, module, external, injected, batched].sort()) + writeFileSync(join(directory, 'package.json'), JSON.stringify({ name: `${name}-unrelated` })) + expect(experimentalRuntimeReferences({ + entries: [], plugins: [], modules: [module], client: { rev: 'fixture', entries: [], batches: [] }, + })).toEqual([]) +}) diff --git a/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.ts b/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.ts new file mode 100644 index 0000000000..a79e358c2b --- /dev/null +++ b/apps/cli/tests/profiles/web/tests/runtime-roster-isolation.ts @@ -0,0 +1,24 @@ +/** Experimental identity checks over independently observed built Web runtime records. */ + +import { fileURLToPath } from 'node:url' +import { hasExperimentalPackageReference, isExperimentalPackageName } from '../../../../../../scripts/experimental-package-policy.ts' +import { modulePackage } from './runtime-roster.ts' +import type { RuntimeRoster } from './runtime-roster.ts' + +/** + * Locate experimental package identities and experimental source/artifact paths in runtime evidence. + * @param roster - independently collected Web runtime records. + * @returns diagnostic evidence for every forbidden package reference. + */ +export function experimentalRuntimeReferences(roster: RuntimeRoster): string[] { + const references = new Set([ + ...roster.entries.map(entry => entry.name), + ...roster.plugins.flatMap(plugin => [plugin.owner ?? '', ...plugin.modules]), + ...roster.modules, + ...roster.client.entries.flatMap(entry => [entry.id, ...entry.inject ?? [], ...entry.external ?? []]), + ...roster.client.batches.flatMap(batch => batch.entries), + ]) + return [...references].filter(reference => hasExperimentalPackageReference(reference) + || (reference.startsWith('file:') && (fileURLToPath(reference).replaceAll('\\', '/').includes('/packages/experimental/') + || isExperimentalPackageName(modulePackage(reference) ?? '')))).sort() +} diff --git a/apps/cli/tests/profiles/web/tests/runtime-roster.ts b/apps/cli/tests/profiles/web/tests/runtime-roster.ts index f6156fd1da..4e48203d03 100644 --- a/apps/cli/tests/profiles/web/tests/runtime-roster.ts +++ b/apps/cli/tests/profiles/web/tests/runtime-roster.ts @@ -75,21 +75,3 @@ export function runtimeRoster(ctx: Context): RuntimeRoster { client: ctx.clientModules.graph(), } } - -/** - * Locate experimental package identities and experimental source/artifact paths in runtime evidence. - * @param roster - independently collected Web runtime records. - * @returns diagnostic evidence for every forbidden package reference. - */ -export function experimentalRuntimeReferences(roster: RuntimeRoster): string[] { - const references = new Set([ - ...roster.entries.map(entry => entry.name), - ...roster.plugins.flatMap(plugin => [plugin.owner ?? '', ...plugin.modules]), - ...roster.modules, - ...roster.client.entries.flatMap(entry => [entry.id, ...entry.inject ?? [], ...entry.external ?? []]), - ...roster.client.batches.flatMap(batch => batch.entries), - ]) - return [...references].filter(reference => reference.includes('@deepseek-ai/dsh-experimental-') - || (reference.startsWith('file:') && (fileURLToPath(reference).replaceAll('\\', '/').includes('/packages/experimental/') - || modulePackage(reference)?.startsWith('@deepseek-ai/dsh-experimental-')))).sort() -} diff --git a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts index ef0f10cc1d..7563a323aa 100644 --- a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts +++ b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts @@ -3,7 +3,8 @@ import { FiberState } from '@deepseek-ai/cordis' import type { WebBootGraph } from '@deepseek-ai/dsh-client-modules/client' import { expect, it } from 'vitest' -import { experimentalRuntimeReferences, modulePackage } from './runtime-roster.ts' +import { modulePackage } from './runtime-roster.ts' +import { experimentalRuntimeReferences } from './runtime-roster-isolation.ts' import { withDefaultWeb, webGet } from './default-web-process.ts' const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-agent-team' diff --git a/apps/cli/tests/profiles/webhook-packed.e2e.ts b/apps/cli/tests/profiles/webhook-packed.e2e.ts new file mode 100644 index 0000000000..3844ceb22b --- /dev/null +++ b/apps/cli/tests/profiles/webhook-packed.e2e.ts @@ -0,0 +1,104 @@ +/** Exercise the published webhook example from an isolated installed Web profile. */ +import { createHmac } from 'node:crypto' +import { existsSync, globSync } from 'node:fs' +import { readFile, writeFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { execa } from 'execa' +import yaml from 'js-yaml' +import { expect, it } from 'vitest' +import { assertInstalledTree, createPackedInstallation } from './packed-installation.ts' + +const root = fileURLToPath(new URL('../../../..', import.meta.url)) +const adapter = '@deepseek-ai/dsh-webhook-github' +const runtime = '@deepseek-ai/dsh-webhook' +const built = existsSync(join(root, 'apps/cli/lib/bin.js')) + && existsSync(join(root, 'packages/experimental/webhook-github/lib/index.js')) +const secret = 'packed-webhook-test-secret' + +it.skipIf(!built)('loads the packed GitHub example with its explicitly installed runtime peer', { + // Packing and installing the complete product closure owns this budget; no model request runs. + timeout: 360_000, + retry: 0, +}, async () => { + const { temporary, installation, home, profile, environment, tarballs, members, externalVersions, packageCommand } + = await createPackedInstallation('dsh-webhook-packed-', [runtime, adapter]) + Object.assign(environment, { DSH_GITHUB_WEBHOOK_SECRET: secret, DSH_GITHUB_WEBHOOK_PORT: '0' }) + expect(existsSync(join(installation, 'node_modules', runtime))).toBe(false) + expect(existsSync(join(installation, 'node_modules', adapter))).toBe(false) + await writeFile(join(profile, 'package.json'), JSON.stringify({ + name: 'packed-webhook-profile', private: true, dependencies: {}, + dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] } }, + })) + const profileDependencies = new Set([runtime, adapter]) + for (const name of profileDependencies) { + const member = members.get(name) + if (member === undefined) throw new Error(`missing packed dependency ${name}`) + for (const [dependency, range] of Object.entries(member.manifest.dependencies as Record | undefined ?? {})) { + if (range.startsWith('workspace:')) profileDependencies.add(dependency) + } + } + await writeFile(join(profile, 'pnpm-workspace.yaml'), yaml.dump({ + packages: ['.'], nodeLinker: 'hoisted', autoInstallPeers: false, + overrides: { + ...externalVersions, + ...Object.fromEntries([...profileDependencies].map(name => [name, `file:${tarballs.get(name)!}`])), + }, + })) + const adapterArchive = tarballs.get(adapter) + const runtimeArchive = tarballs.get(runtime) + if (adapterArchive === undefined || runtimeArchive === undefined) throw new Error('webhook tarballs were not packed') + await packageCommand(['add', runtimeArchive, adapterArchive, '--prefer-offline', '--ignore-scripts'], profile) + const manifest = JSON.parse(await readFile(join(profile, 'package.json'), 'utf8')) as { dependencies: Record } + expect(Object.keys(manifest.dependencies).sort()).toEqual([runtime, adapter].sort()) + await assertInstalledTree(profile) + + const example = join(profile, 'node_modules', adapter, 'examples/github-review/cordis.yml') + expect(existsSync(example)).toBe(true) + expect(existsSync(join(dirname(example), 'github-ready-review-rule.mjs'))).toBe(true) + const observer = join(temporary, 'observe-ingress.mjs') + await writeFile(observer, `export const name = 'observe-packed-ingress' +export const inject = ['webServer', 'webhookRuntime'] +export function apply(ctx) { + process.stdout.write('PACKED_WEBHOOK_READY=http://127.0.0.1:' + ctx.webServer.port + '\\n') +} +`) + const patch = join(temporary, 'test.patch.yml') + await writeFile(patch, yaml.dump([ + { id: 'session-telemetry-otel', disabled: true }, + { id: 'github-webhook-ingress', insert: [{ id: 'observe-packed-ingress', name: observer }] }, + ])) + const child = execa(process.execPath, [ + join(installation, 'node_modules/@deepseek-ai/dsh/lib/bin.js'), 'web', '--patch', example, '--patch', patch, '--no-open', '--port', '0', + ], { cwd: temporary, env: environment, extendEnv: false, reject: false, timeout: 90_000 }) + try { + const ready = new Promise((resolveReady, rejectReady) => { + let output = '' + child.stdout?.on('data', (chunk: Buffer) => { + output += chunk.toString() + const origin = /PACKED_WEBHOOK_READY=(http:\/\/127\.0\.0\.1:\d+)/.exec(output)?.[1] + if (origin !== undefined && output.includes('dsh web: ')) resolveReady(origin) + }) + void child.then((result) => { + rejectReady(new Error(`Web profile exited before readiness:\n${result.stdout}\n${result.stderr}`)) + }) + }) + const origin = await ready + expect((await fetch(`${origin}/api`, { signal: AbortSignal.timeout(10_000) })).status).toBe(404) + const body = JSON.stringify({ zen: 'A signed ping does not start an Agent.' }) + const response = await fetch(`${origin}/github`, { + method: 'POST', body, signal: AbortSignal.timeout(10_000), + headers: { + 'content-type': 'application/json', 'x-github-event': 'ping', 'x-github-delivery': 'packed-ping', + 'x-hub-signature-256': `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`, + }, + }) + expect(response.status, await response.text()).toBe(202) + } finally { + child.kill('SIGTERM') + const result = await child + expect(result.timedOut, `${result.stdout}\n${result.stderr}`).toBe(false) + expect(result.exitCode === 0 || result.signal === 'SIGTERM', result.stderr).toBe(true) + } + expect(globSync('sessions/**/*.jsonl*', { cwd: home })).toEqual([]) +}) diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index 079aa6dbce..6fa2861ce0 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -13,15 +13,17 @@ import { loadProfile, PluginPackages, type Profile, + type ProfilePatch, } from '@deepseek-ai/dsh-app-boot' import { provideCmdline } from '@deepseek-ai/dsh-cmdline' import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session' import type { Agent } from '@deepseek-ai/dsh-agent' import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, describe, expect, it, onTestFinished, vi } from 'vitest' import { dump, load } from 'js-yaml' import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' import { bundlePatchPaths, composeEntries } from '@deepseek-ai/dsh-app-boot' +import { createPluginProfile } from '../../desktop/src/project-manager.ts' /** Profile entry ids whose volatile fields these scenarios edit through Settings. */ const SETTINGS_NAMESPACE = 'agent-preset-registry' const SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE = 'subagent-model-selection-settings' @@ -61,9 +63,10 @@ const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell */ async function bootWeb( profileHome: string, - extra: PatchOptions[] = [], + extra: ProfilePatch[] = [], profilePackages: readonly string[] = [], profileBundles?: readonly string[], + surface: 'web' | 'desktop' | 'spec' = 'spec', ): Promise { const storageRoot = join(profileHome, 'storages') const overrides: PatchOptions[] = [ @@ -90,10 +93,12 @@ async function bootWeb( // agent's capabilities, which is all this file asserts. { id: 'web-runtime', disabled: true }, { id: 'session-telemetry-otel', disabled: true }, + { id: 'desktop-product-telemetry', disabled: true }, + { id: 'product-analytics', disabled: true }, // A deployment-level skill on the host registry's GLOBAL layer — the same // registration shape a repository plugin's skill root uses. The layered // skills test below proves it reaches preset-composed agents. - { id: 'skill-badge', disabled: false }, + { insert: [{ id: 'skill-badge', name: import.meta.resolve('@deepseek-ai/dsh-skill-badge') }] }, { id: 'modules', disabled: true }, // The physical Connection row owns the disabled HTTP server. bootWeb // supplies only its in-process registries so Host services still prove @@ -120,9 +125,10 @@ async function bootWeb( ...extra, ] const home = profileHome - const profileDir = join(home, 'profiles', 'spec') + const profileDir = join(home, 'profiles', surface) await mkdir(profileDir, { recursive: true }) - if (profileBundles === undefined) initProfile(profileDir, ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app']) + if (surface === 'desktop') createPluginProfile(profileDir) + else if (profileBundles === undefined) initProfile(profileDir, ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app']) // Product Bundles are installed into the Profile, not the dsh app. Model // pnpm's package link for only the selected products; their own production // dependencies resolve from the linked workspace packages, while shared @@ -134,7 +140,7 @@ async function bootWeb( await symlink(packageDir, link, 'junction') } let profile: Profile = { skippedBundles: [], - name: 'spec', + name: surface, dir: profileDir, layers: [], patchPath: join(profileDir, 'cordis.patch.yml'), @@ -150,7 +156,7 @@ async function bootWeb( dependencies: Object.fromEntries(profileBundles.map(name => [name, 'workspace:*'])), dsh: { profile: { bundles: profileBundles } }, }, null, 2) + '\n') - profile = loadProfile('dsh-test', 'spec', INSTALL_ANCHOR, home, { userLayer: false }) + profile = loadProfile('dsh-test', surface, INSTALL_ANCHOR, home, { userLayer: false }) bundlePatches = profile.layers.flatMap(layer => layer.patches) } // Deployment defaults live in a bundle beneath the profile patch, so Settings writes are not shadowed by overlays. @@ -166,7 +172,7 @@ async function bootWeb( const rootConfig = join(profileDir, 'cordis.yml') await writeFile(rootConfig, '[]\n') return await boot('dsh-test', rootConfig, [...bundlePatches, ...overrides], async (bootCtx) => { - bootCtx.provide('profileContext', { name: 'spec', dir: profileDir, patchPath: profile.patchPath, + bootCtx.provide('profileContext', { name: surface, dir: profileDir, patchPath: profile.patchPath, installAnchor: INSTALL_ANCHOR, home, cwd: home, startedBundles: profileBundles ?? ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'], overlays: [], telemetryDisabledEnv: '1' }) @@ -192,27 +198,15 @@ function toolParameterNames(ctx: Context, agent: Agent, toolName: string): strin return Object.keys(properties).sort() } -function enablePresetTool(composition: string, id: string): string { - const rows = load(composition, { schema: entryListSchema }) as import('@deepseek-ai/cordis-plugin-loader').EntryOptions[] - const visit = (entries: typeof rows): boolean => entries.some((row) => { - if (row.id === id) { row.disabled = false; return true } - return row.group === true && visit(row.config as typeof rows) - }) - if (!visit(rows)) throw new Error(`missing preset row ${id}`) - return dump(rows, { schema: entryListSchema }) -} - let ctx: Context beforeAll(async () => { ctx = await bootWeb(await mkdtemp(join(tmpdir(), 'dsh-web-presets-'))) }, 120_000) describe('the shipped Web composition', () => { - it('leaves the global tool layer empty', () => { - // Every model-facing tool belongs to a preset, `ask_user_question` - // included: a tool in the global layer reaches EVERY agent regardless of - // which preset composed it, expanding that preset's tool list. - expect(toolNames(ctx)).toEqual([]) + it('exposes only the directory tool in the global layer', () => { + // Directory selection is global; action tools belong to their Agent presets. + expect(toolNames(ctx)).toEqual(['working_directory']) }) it('keeps the token meter and its context-meter projections on the host plane', async () => { @@ -267,7 +261,7 @@ describe('the shipped Web composition', () => { 'schedule_create', 'schedule_delete', 'schedule_list', 'schedule_update', 'send_message', 'skill', 'subagent', 'subagent_fork', 'todo_write', 'update_goal', 'web_fetch', 'web_search', - 'workflow', 'write', + 'workflow', 'working_directory', 'write', ]) expect(ctx.commands.find(handle.agent, 'goal')).toBeDefined() } finally { @@ -319,7 +313,7 @@ describe('the shipped Web composition', () => { expect(assembly.sections).toEqual([ { name: 'deployment:persona-prefix', text: MINIMAL_PROMPT }, ]) - expect(assembly.tools.map(tool => tool.name)).toEqual(['bash']) + expect(assembly.tools.map(tool => tool.name)).toEqual(['bash', 'working_directory']) expect(assembly.tools.find(tool => tool.name === 'bash')?.description).toBe(MINIMAL_BASH_DESCRIPTION) expect(ctx.commands.find(handle.agent, 'goal')).toBeUndefined() // serviceFor reports preset-owned providers; unisolated consumers inherit the host fs. @@ -343,14 +337,14 @@ describe('the shipped Web composition', () => { setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined), }) try { - expect(toolNames(ctx, minimal.agent)).toEqual(['bash']) + expect(toolNames(ctx, minimal.agent)).toEqual(['bash', 'working_directory']) expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10) await minimal.dispose() // Tearing the minimal session down leaves the full one whole. expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10) - expect(toolNames(ctx)).toEqual([]) + expect(toolNames(ctx)).toEqual(['working_directory']) } finally { await full.dispose() } @@ -509,7 +503,7 @@ describe('the shipped Web composition', () => { // stays the preset's choice — minimal mounts no `tool-skill`, so its // tool table has no loader even though the global layer is readable. expect((await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)).toContain('dsh-badge') - expect(toolNames(ctx, handle.agent)).toEqual(['bash']) + expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'working_directory']) } finally { await handle.dispose() } @@ -549,14 +543,19 @@ describe('product Bundle and user-preset intersection', () => { const standardConfig = composeEntries([webPatches('test')]).find(row => row.id === 'preset-standard')!.config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition const standard = dump(standardConfig.plugins, { schema: entryListSchema }) for (const id of presetIds) { - let composition = standard + const plugins = load(standard, { schema: entryListSchema }) as import('@deepseek-ai/cordis-plugin-loader').EntryOptions[] + // User presets declare their own delegation rows; provider bundles target only the three full shipped presets. if (id === 'products-codex' || id === 'products-both') { - composition = enablePresetTool(composition, 'tool-subagent-codex') + plugins.push({ id: 'custom-tool-subagent-codex', name: '@deepseek-ai/dsh-tool-subagent', config: { + provider: 'codex', toolName: 'subagent_codex', maxDepth: 'provider-managed', + } }) } if (id === 'products-claude' || id === 'products-both') { - composition = enablePresetTool(composition, 'tool-subagent-claude-code') + plugins.push({ id: 'custom-tool-subagent-claude-code', name: '@deepseek-ai/dsh-tool-subagent', config: { + provider: 'claude-code', toolName: 'subagent_claude_code', maxDepth: 'provider-managed', + } }) } - definitions.push({ id: `preset-${id}`, name: '@deepseek-ai/dsh-agent-preset', config: { id, plugins: load(composition, { schema: entryListSchema }) } }) + definitions.push({ id: `preset-${id}`, name: '@deepseek-ai/dsh-agent-preset', config: { id, plugins } }) } const packageDir = (product: Product): string => ( product === 'codex' ? CODEX_PACKAGE_DIR : CLAUDE_CODE_PACKAGE_DIR @@ -611,7 +610,7 @@ describe('product Bundle and user-preset intersection', () => { expect(tools).toEqual(expect.arrayContaining(['job_kill', 'job_list', 'job_output'])) for (const productTool of productTools) { expect(toolParameterNames(productCtx, handle.agent, productTool)).toEqual([ - 'description', 'prompt', 'run_in_background', + 'cwd', 'description', 'prompt', ]) } } finally { @@ -627,6 +626,75 @@ describe('product Bundle and user-preset intersection', () => { }, 120_000) }) +describe.each(['web', 'desktop'] as const)('%s native bundle adoption', (surface) => { + const products = ['codex', 'claude-code'] as const + const bundleNames = products.map(product => `@deepseek-ai/dsh-subagent-${product}`) + + async function bootSelected(extra: ProfilePatch[]): Promise { + const root = await mkdtemp(join(tmpdir(), `dsh-${surface}-native-adoption-`)) + onTestFinished(async () => { await rm(root, { recursive: true, force: true }) }) + return await bootWeb(root, extra, [CODEX_PACKAGE_DIR, CLAUDE_CODE_PACKAGE_DIR], [ + '@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', ...bundleNames, + ], surface) + } + + it('keeps selected provider identities and later tool overrides without duplicating custom preset tools', async () => { + const definition = composeEntries([webPatches('adoption')]).find(row => row.id === 'preset-cordis')! + .config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition + const config = { ...definition, plugins: [...definition.plugins, ...products.map(product => ({ + id: `tool-subagent-${product}`, name: '@deepseek-ai/dsh-tool-subagent', disabled: product === 'codex', + config: { provider: product, toolName: `custom_${product.replaceAll('-', '_')}`, maxDepth: 'provider-managed' }, + }))] } + const providerConfig = { model: 'adoption-model', env: { DSH_NATIVE_ADOPTION: 'preserved' } } + const productCtx = await bootSelected([ + ...products.map(product => ({ id: `subagent-${product}`, config: providerConfig })), + { preset: 'preset-standard', id: 'tool-subagent-codex', disabled: true }, + { preset: 'preset-standard', id: 'tool-subagent-claude-code', config: { + provider: 'claude-code', toolName: 'custom_claude_code', maxDepth: 'provider-managed', + } }, + { id: 'preset-cordis', config }, + ]) + const spawn = vi.spyOn(productCtx.subprocess, 'spawn') + try { + expect(productCtx.subagents.list()).toEqual(expect.arrayContaining([...products])) + for (const product of products) { + expect([...productCtx.loader.entries()].find(entry => entry.options.id === `subagent-${product}`)?.options.config) + .toMatchObject(providerConfig) + } + for (const [preset, expected] of [ + ['standard', ['custom_claude_code']], ['cordis', ['custom_claude_code']], + ['ptc', ['subagent_claude_code', 'subagent_codex']], ['minimal', []], + ] as const) { + const handle = await productCtx.agents.create({ + sessionId: SessionId(`adoption-${surface}-${preset}-${randomUUID()}`), + setup: owner => productCtx.agentPresets.mount(owner, preset).then(() => undefined), + }) + try { + expect(toolNames(productCtx, handle.agent).filter(name => /(?:subagent|custom)_(?:codex|claude_code)/.test(name))) + .toEqual(expected) + } finally { await handle.dispose() } + } + expect(spawn).not.toHaveBeenCalled() + } finally { + spawn.mockRestore() + await productCtx.fiber.dispose() + } + }, 120_000) + + it('reports a redundant manual tool instead of silently replacing the bundle contribution', async () => { + const productCtx = await bootSelected([{ preset: 'preset-standard', insert: [{ + id: 'manual-codex', name: '@deepseek-ai/dsh-tool-subagent', config: { + provider: 'codex', toolName: 'subagent_codex', maxDepth: 'provider-managed', + }, + }] }]) + try { + expect((await productCtx.agentPresets.list()).find(preset => preset.id === 'standard')?.broken) + .toContain('already registered') + expect(productCtx.profileContext.startedBundles).toEqual(expect.arrayContaining(bundleNames)) + } finally { await productCtx.fiber.dispose() } + }, 120_000) +}) + describe('a user preset declared from the shipped cordis rows', () => { /** The JSON text a `cordis_inspect_*` result renders. */ function resultText(result: { content: ReadonlyArray<{ type: string; text?: string }> }): string { @@ -862,9 +930,8 @@ describe('the default preset as a user setting', () => { setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined), }) try { - // `mount()` with no id resolves the effective default. One tool, not - // `standard`'s catalog: the setting decided the composition. - expect(toolNames(ctx, handle.agent)).toEqual(['bash']) + // An omitted preset id resolves the stored default instead of the composed default. + expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'working_directory']) } finally { await handle.dispose() } @@ -910,7 +977,7 @@ describe('a profile patch stored before Developer tools owned preset selection', setup: agentCtx => legacy.agentPresets.mount(agentCtx).then(() => undefined), }) try { - expect(toolNames(legacy, handle.agent)).toEqual(['bash']) + expect(toolNames(legacy, handle.agent)).toEqual(['bash', 'working_directory']) } finally { await handle.dispose() } @@ -927,7 +994,7 @@ describe('a session keeps the preset it was created with', () => { try { // The api-proxy guard reads exactly this: the header records what the // session runs, so naming anything else is a caller error rather than a - // switch. Its history was produced under `minimal`'s single tool. + // switch. expect(handle.agent.session.header.agentPreset).toBe('minimal') } finally { await handle.dispose() diff --git a/apps/desktop-host/package.json b/apps/desktop-host/package.json index fe870e6308..f2a75bfa51 100644 --- a/apps/desktop-host/package.json +++ b/apps/desktop-host/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop-host", "description": "Private Node-mode host process for the Electron desktop application", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "private": true, "license": "MIT", "type": "module", @@ -12,7 +12,7 @@ ], "dependencies": { "koffi": "^3.1.0", - "@deepseek-ai/libreoffice-kit": "^0.1.1", + "@deepseek-ai/libreoffice-kit": "^0.1.5", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", diff --git a/apps/desktop-host/src/index.ts b/apps/desktop-host/src/index.ts index 4a47a92ce8..dd8f57c54c 100644 --- a/apps/desktop-host/src/index.ts +++ b/apps/desktop-host/src/index.ts @@ -27,7 +27,7 @@ async function main(): Promise { profile: 'desktop', resolvedProfile: { profile, installAnchor }, patchFiles: [], - args: ['--no-open', '--port', '19387'], + args: ['--no-open', '--port', '0'], ...(process.argv[5] === undefined ? {} : { packageManager: { command: process.execPath, diff --git a/apps/desktop-host/src/office-engine.ts b/apps/desktop-host/src/office-engine.ts index d8f0bd1d5f..f58019d116 100644 --- a/apps/desktop-host/src/office-engine.ts +++ b/apps/desktop-host/src/office-engine.ts @@ -29,7 +29,11 @@ export function installOfficeEngineResolution(runtimeDir: string): ModuleHooks | return registerHooks({ resolve(specifier, context, nextResolve) { const resolved = nextResolve(specifier, context) - if (!/^@deepseek-ai\/libreoffice-kit-(?:darwin|win32|linux)-/u.test(specifier)) return resolved + if (!resolved.url.startsWith('file:')) return resolved + const engineRequest = /^@deepseek-ai\/libreoffice-kit-(?:darwin|win32|linux)-/u.test(specifier) + const engineTarget = /\/node_modules\/@deepseek-ai\/libreoffice-kit-(?:darwin|win32|linux)-[^/]+\//u + .test(new URL(resolved.url).pathname) + if (!engineRequest && !engineTarget) return resolved const canonical = pathToFileURL(realpathSync(fileURLToPath(resolved.url))).href if (!canonical.startsWith(source)) { if (canonical.startsWith(pathToFileURL(archive + '/').href)) { diff --git a/apps/desktop-host/tests/office-engine.spec.ts b/apps/desktop-host/tests/office-engine.spec.ts index 716aef756d..83c544dc01 100644 --- a/apps/desktop-host/tests/office-engine.spec.ts +++ b/apps/desktop-host/tests/office-engine.spec.ts @@ -1,19 +1,26 @@ import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' import { createRequire, type ModuleHooks } from 'node:module' import { tmpdir } from 'node:os' -import { dirname, join } from 'node:path' +import { dirname, join, sep } from 'node:path' import { afterEach, expect, it } from 'vitest' import { installOfficeEngineResolution } from '../src/office-engine.ts' +import { PackageManifests } from '../../../packages/boot/hmr/src/package-manifest.ts' const roots: string[] = [] const hooks: ModuleHooks[] = [] +const disposers: Array<() => void> = [] afterEach(() => { + for (const dispose of disposers.splice(0).reverse()) dispose() for (const hook of hooks.splice(0)) hook.deregister() + const cache = createRequire(import.meta.url).cache + for (const path of Object.keys(cache)) { + if (roots.some(root => path.startsWith(root + sep))) Reflect.deleteProperty(cache, path) + } for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) }) function fixture(runtimeName = 'dsh') { - const root = mkdtempSync(join(tmpdir(), 'desktop-office-resolution-')) + const root = realpathSync(mkdtempSync(join(tmpdir(), 'desktop-office-resolution-'))) roots.push(root) const runtime = join(root, 'app.asar', runtimeName) const manifest = 'node_modules/@deepseek-ai/libreoffice-kit-darwin-arm64/package.json' @@ -40,6 +47,45 @@ it('resolves engine manifests to physical directories and leaves unrelated modul expect(f.require('@deepseek-ai/libreoffice-kit/package.json')).toEqual({ name: '@deepseek-ai/libreoffice-kit' }) }) +it('resolves an absolute engine manifest to its unpacked copy', () => { + const f = fixture() + expect(f.require(join(f.runtime, f.manifest))) + .toMatchObject({ path: realpathSync(dirname(join(f.root, 'app.asar.unpacked', 'dsh', f.manifest))) }) +}) + +it.each(['exports', 'main'] as const)('refreshes a cached plugin %s while Office resolution stays active', (field) => { + const f = fixture() + const packageRoot = join(f.root, 'plugin') + mkdirSync(packageRoot) + const manifest = join(packageRoot, 'package.json') + const a = join(packageRoot, 'a.cjs') + const b = join(packageRoot, 'b.cjs') + writeFileSync(manifest, JSON.stringify({ name: 'plugin', [field]: './a.cjs' })) + writeFileSync(a, 'module.exports = { marker: "a" }') + writeFileSync(b, 'module.exports = { marker: "b" }') + symlinkSync(packageRoot, join(f.runtime, 'node_modules', 'plugin'), process.platform === 'win32' ? 'junction' : 'dir') + const require = createRequire(join(f.runtime, 'package.json')) + const original: unknown = require('plugin') + const next: unknown = require(b) + const originalModule = require.cache[a] + const nextModule = require.cache[b] + const engine = f.require('@deepseek-ai/libreoffice-kit-darwin-arm64/package.json') + expect(original).toEqual({ marker: 'a' }) + expect(next).toEqual({ marker: 'b' }) + expect(require('plugin')).toBe(original) + const manifests = new PackageManifests() + disposers.push(() => { manifests.dispose() }) + writeFileSync(manifest, JSON.stringify({ name: 'plugin', [field]: './b.cjs' })) + manifests.invalidate(manifest) + expect(require('plugin')).toBe(next) + expect(require.resolve('plugin')).toBe(b) + expect(require.cache[a]).toBe(originalModule) + expect(require.cache[b]).toBe(nextModule) + expect(require(a)).toBe(original) + expect(f.require('@deepseek-ai/libreoffice-kit-darwin-arm64/package.json')).toBe(engine) + expect(f.require(join(f.runtime, f.manifest))).toBe(engine) +}) + it('rejects an engine missing from the unpacked tree instead of using its archived copy', () => { const f = fixture() rmSync(join(f.root, 'app.asar.unpacked'), { recursive: true }) diff --git a/apps/desktop/.env.macos.example b/apps/desktop/.env.macos.example index d21463ef9d..da5502ce5d 100644 --- a/apps/desktop/.env.macos.example +++ b/apps/desktop/.env.macos.example @@ -1,3 +1,4 @@ +# Release configuration only; local --unsigned builds ignore this file. # Copy to .env.macos (Git ignored). Use UTF-8 and quote secrets containing # or spaces. # Paths may be absolute or relative to apps/desktop. Values are not shell-expanded. DSH_DESKTOP_APP_ID=com.deepseek.harness @@ -20,7 +21,7 @@ DSH_DESKTOP_MACOS_DOWNLOAD_PROXY= # Requires permission to change system proxies; affects other applications during notarization. DSH_DESKTOP_MACOS_NOTARIZATION_PROXY= -# The selected mandatory-update origin is required, including unsigned and preparation builds. +# The selected mandatory-update origin is required for release and preparation builds. DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN= DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN= # Set approved login origins for test builds; omit allowedAuthOrigins for production. diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index f412464ae6..9c95cd3111 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/desktop/README.md /deepseek-harness-desktop: - en: eccac345d51428fa - zh: 4a448b3d63cb8117 + en: 0f0f6ea914eff51e + zh: 4934245b04e9f1c0 /deepseek-harness-desktop/terminal-command: en: bef92953c237363f zh: d21d6effd21b8c00 @@ -15,23 +15,23 @@ en: 069fdfd72d2ea26a zh: 76ae549d43c99bb4 /deepseek-harness-desktop/key-technical-decisions/bundled-workspace-dependencies: - en: 320733f93ede61a4 - zh: 33206c3c4753c802 + en: 0f9b6c1d6761d108 + zh: 77127063f244c361 /deepseek-harness-desktop/bundled-command-runtime: - en: beecf076aa2f0796 - zh: c9001ce0f22d2379 + en: 09f0b75af9f0ed14 + zh: 60bbc2e67d5e87a8 /deepseek-harness-desktop/installation-ownership: en: 22153a298a6662e1 zh: 3600803c8c9d0274 /deepseek-harness-desktop/installation-ownership/runtime-and-plugin-activation: - en: 0d11af8639977a6d - zh: 86b086b12d2210d3 + en: a1a47b09f4a72ba4 + zh: 7b1cf27f0b72cf31 /deepseek-harness-desktop/installation-ownership/host-environment: en: 84a9cc7ca9455aa8 zh: 4091b8aa8231b74a /deepseek-harness-desktop/develop: - en: 40d2ad4014bf445b - zh: 6a08de06df9ed042 + en: fe6d23e6a3d3bc66 + zh: d60c8d3a55747916 /deepseek-harness-desktop/develop/startup-onboarding: en: a51346b8d880ddba zh: 7913214c1c66063c @@ -42,29 +42,35 @@ en: 4cd8a8da6977928d zh: 9f624855a2edac37 /deepseek-harness-desktop/package/release-versions: - en: f461b255133384cc - zh: 2b9db718f22fdd04 + en: 99607dc36badb7f1 + zh: 5df47873fe8cdea8 +/deepseek-harness-desktop/package/local-macos-dmg-without-release-signing: + en: 57869a5e3885f843 + zh: 7cfc01b2dcbebd78 +/deepseek-harness-desktop/package/local-official-bundles-from-a-loopback-registry: + en: 5f05072ec82cc62d + zh: a3d63b58d1ef2db7 /deepseek-harness-desktop/package/runtime-file-selection: - en: f0e345fd89868803 - zh: fd2c775ad23fa2f0 + en: e7c1eb8e78fe46e3 + zh: b7ea497dd75be524 /deepseek-harness-desktop/package/upload-updates: - en: 72073b2e72590067 - zh: 63b1f9790395c37d + en: f48baa680e841b0d + zh: 25ded1aeaac6dad1 /deepseek-harness-desktop/package/unsigned-windows-test-installer: en: cfd5780dd26a8e46 zh: f7a16b1ede793b7b /deepseek-harness-desktop/package/windows-installer-interface: - en: f176a8b1c7de3064 - zh: 6ea2dbe6bb808c34 + en: 1fc1db8f54475c8f + zh: dd834ed206070396 /deepseek-harness-desktop/package/windows-ev-signing: - en: 8efe3498dbe8686d - zh: a88c16f20a9430f3 + en: be1379e4b4703e14 + zh: 244ccd2874bb155a /deepseek-harness-desktop/updates: - en: d0df867b69cbffa4 - zh: eeb47b79b86fe5ee + en: 10d58aa5bbffbd67 + zh: d9c2476e744a6537 /deepseek-harness-desktop/updates/mandatory-update-policy: - en: 2f789a4949fafb12 - zh: d8b28c01cfee11b7 + en: 1527dceec540649e + zh: 543782c36f91f893 /deepseek-harness-desktop/updates/local-updater-qualification: en: bc50fd8a4d96c227 zh: fb0a6ce80c31cda6 diff --git a/apps/desktop/README.md b/apps/desktop/README.md index fa00c27e08..57eb2cb333 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -4,7 +4,7 @@ English | [中文](README.zh.md) Desktop analytics follows the [product collection policy](../../packages/client/product-analytics/README.md), including its live application setting. Web usage is excluded. Update installation waits for its local analytics intake before locking API admission and stopping the Host. Intake has a one-second deadline; failure does not prevent installation, and collector delivery is not awaited. -The desktop application is an Electron shell around the complete dsh Web application. An Electron RunAsNode child starts the shared profile runner, and Electron immediately loads the packaged Web entry at `dsh-app://app/`. Its shared loading page waits for Host boot injections, then starts the client without navigating to another document. Electron forwards application HTTP requests to the authenticated Web Host, dropping connection-level response headers (`transfer-encoding`, `connection`, `keep-alive`) that describe the Node fetch rather than the resource, and marking plugin bundle responses `no-store` because their per-launch revisions would only accumulate in Chromium's disk cache; WebSocket streams connect to that Host with credentials attached only for the owned application window. Node IPC carries boot injections, readiness, and shutdown. Desktop defaults to port `19387`, separate from Web’s `3080`; a `webserver.config.port` patch can override it. +The desktop application is an Electron shell around the complete dsh Web application. An Electron RunAsNode child starts the shared profile runner, and Electron immediately loads the packaged Web entry at `dsh-app://app/`. Its shared loading page waits for Host boot injections, then starts the client without navigating to another document. Electron forwards application HTTP requests to the authenticated Web Host, dropping connection-level response headers (`transfer-encoding`, `connection`, `keep-alive`) that describe the Node fetch rather than the resource, and marking plugin bundle responses `no-store` because their per-launch revisions would only accumulate in Chromium's disk cache; WebSocket streams connect to that Host with credentials attached only for the owned application window. Node IPC carries boot injections, readiness, and shutdown. Desktop listens on an OS-assigned port by default, so it cannot collide with Web’s `3080` or a reserved port; a `webserver.config.port` patch can override it. The first application-menu command, **About DeepSeek Harness**, opens Electron's native About panel with the application icon, product name, and installed release version. The menu follows the Desktop shell locale. On macOS, Hide, Hide Others, Show All, and Quit use localized labels; Hide and Quit include the DeepSeek Harness product name. These commands retain their native actions and shortcuts. macOS reads the icon from its application bundle, so an unpackaged development launch displays Electron's icon; Windows receives the packaged PNG. @@ -14,7 +14,7 @@ Creator and the Web Plugin Manager use Desktop’s bundled pnpm under Electron N Embedded Platform documents use a persistent WebContentsView partition keyed by a hash of the Platform origin and stable account ID. localStorage preferences, including dismissed notices, survive view closure and application restart; different accounts and origins use separate storage. The Host supplies the account ID from its latest successful profile read; while no ID is known, the document opens in a disposable partition that keeps no preference across opens. Opening a persistent partition clears cookies, filesystem, IndexedDB, Cache Storage, the HTTP and shader caches, service workers, and HTTP authentication before the document loads; a disposable partition clears all of its storage. Closing a view destroys the document, removes its request interceptors, and schedules the same cleanup, so authentication left by an unclean exit is cleared before the next document loads. The next open and application shutdown await that cleanup, and an update install waits for it before the installer takes over the exit. A failed cleanup rejects that open and blocks the same account until a later cleanup succeeds; other accounts keep opening. Sign-out destroys the document but retains account-scoped preferences for the next sign-in. A document already open in a disposable partition stays mounted when the account ID arrives for the same credential; the next open uses the account-scoped partition. The [storage decision](../../.agents/notes/implemented/architecture/2026-09-22-platform-browser-storage.md) describes the retention policy. Host sends account credentials through private Node IPC; account RPC and the Harness renderer receive no token. Before page scripts execute, the Platform preload performs one synchronous IPC to read the prepared main-process credential. It exposes displayMode, synchronous getAuthToken() and getLocale() getters, and onLocaleChange(listener), which returns an unsubscribe function. Both getters read preload memory without further IPC. The bootstrap includes the resolved Desktop language (`zh_CN` or `en_US`); Settings language changes update the preload cache and notify the open Platform document without reloading. Platform applies this language before its first render and does not persist it as a browser preference. The main-process handler performs only sender validation and an in-memory read; it never waits for Host, disk, or network. Trusted-page initialization failures retain embedded mode and make the getter throw, preventing browser-credential fallback. Only the owned Platform main frame on the configured issuer origin can initialize. Sign-out, credential replacement, Host shutdown, and view closure destroy the document. Cross-origin document navigation is blocked. HTTPS links requesting a new window open in the system browser without the embedded session or token; other schemes and URL credentials are rejected. Native views occupy the viewport below the Account feature’s return bar. -Desktop Host Platform API requests and update-policy requests identify the installed client with the same Platform client headers: platform, client version, locale, timezone offset in seconds, and a bundle id that is intentionally empty. Account operations take the calling UI's identity per call; the account provider owns [API-only header configuration](../../packages/credentials/deepseek-account-platform/README.md#use-this-package). Update policy additionally reports architecture, update channel, and bundled runtime version. +Desktop Host Platform API requests and update-policy requests identify the installed client with the same Platform client headers: platform, client version, locale, timezone offset in seconds, and a bundle id that is intentionally empty. Account operations take the calling UI's identity per call; the account provider owns [API-only header configuration](../../packages/credentials/deepseek-account-platform/README.md#use-this-package). Update policy additionally reports architecture, update channel, and bundled runtime version. The native account-state decoder accepts `no-response` sign-in failures and preserves the error code in the UI-safe projection. A server-expired account credential returns to Welcome when no official API key is configured; an available API key keeps the workspace open. Explicit sign-out follows the same rule. Both Welcome and the workspace display the localized expiry notice. @@ -56,7 +56,7 @@ The macOS PNG uses an inset rounded background for legacy ICNS packaging, with r ### Bundled workspace dependencies -electron-builder copies only the manifest's `dependencies` into `app.asar/node_modules`, so the Electron main bundle `lib/main.js` inlines its workspace devDependencies and leaves `electron`, Node builtins, and those `dependencies` as its only bare imports; a sandboxed preload may leave only `electron`, `events`, `timers`, and `url`, the modules its `require` polyfill resolves. The main bundle resolves the inlined packages from their `lib/` output, so the root `build:lib:host` bundles `apps/desktop` after the concurrent workspace tsdown pass, and [`desktop-bundle-imports`](scripts/desktop-bundle-imports.mjs) fails any Desktop bundle whose static, dynamic, or `require()` import the packaged application cannot resolve. Without that check an import rolldown could not resolve ships as an external specifier and fails at launch with `ERR_MODULE_NOT_FOUND`. The [bundle-order decision](../../.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md) records the alternatives. +electron-builder copies only the manifest's `dependencies` into `app.asar/node_modules`, so the Electron main bundle `lib/main.js` inlines its workspace devDependencies and leaves `electron`, Node builtins, and those `dependencies` as its only bare imports; a sandboxed preload may leave only `electron`, `events`, `timers`, and `url`, the modules its `require` polyfill resolves. The main bundle resolves the inlined packages from their `lib/` output, so the root `build:lib:host` bundles `apps/desktop` after the concurrent workspace tsdown pass, and [`desktop-bundle-imports`](scripts/desktop-bundle-imports.mjs) fails any Desktop bundle whose static, dynamic, or `require()` import the packaged application cannot resolve. Without that check an import rolldown could not resolve ships as an external specifier and fails at launch with `ERR_MODULE_NOT_FOUND`. The [archived bundle-order decision](../../.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md) records the alternatives. Signed Windows packaging scans the primary runtime and application production dependencies for PE content, including files without a conventional extension. The final scan covers the entire unpacked application. Directory links, malformed `MZ` files and non-PE `.exe`, `.dll` or `.pyd` files stop packaging; data files beginning with `MZ` are also rejected unless they contain a valid PE header. It preserves valid vendor signatures and signs unsigned code before sealing runtime hashes or executing smoke checks. Public-key inspection uses batches of 32 files with at most four processes; hardware-token signing remains serial, and each new signature must match the configured certificate and carry a timestamp. Hardware signing and verification failures stop the run; isolated timestamp requests follow the bounded policy below. Electron-builder preserves copied runtime executables only after signature and exact-byte verification. A final PE-signature audit and a fresh-cache ASAR payload and Host smoke must pass before the release completion record is written. Development, preparation-only and unsigned builds do not use the hardware token and can be blocked by Windows code-integrity policy; no build mode disables that policy. Passing smoke checks does not establish compatibility with every enterprise policy. @@ -84,9 +84,11 @@ The [thin-wrapper decision](../../.agents/notes/implemented/architecture/2026-09 Welcome loads the shared Toast palette and shadow tokens, with system typography for body-mounted notifications. + + ## Bundled command runtime -The installed `resources/runtime/cli/bin/dsh` shell script (`dsh.cmd` on Windows) runs the ordinary CLI dispatcher using Desktop's Electron executable and bundled pnpm. It works while Desktop is closed and retains the [Electron runtime limitations](../../.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.md). Ordinary profile, configuration, and plugin commands use the same implementation as npm dsh; the command does not open Desktop. +The installed `resources/runtime/cli/bin/dsh` shell script (`dsh.cmd` on Windows) runs the ordinary CLI dispatcher using Desktop's Electron executable and bundled pnpm. It works while Desktop is closed and retains the Electron runtime limitations. Ordinary profile, configuration, and plugin commands use the same implementation as npm dsh; the command does not open Desktop. To manage Desktop's plugins, launch Desktop once to initialize its profile, fully quit the application, then run `dsh plugin --profile desktop add `, `list`, or `remove `. Reopen Desktop to use the changes. Package operations retain the shared profile write lock and compatibility checks. The installed command refuses an uninitialized Desktop profile instead of creating an ordinary CLI profile in its place. @@ -116,7 +118,7 @@ On macOS the custom menu retains Electron's standard Window menu and application ### Runtime and plugin activation -The signed `resources/app.asar/dsh/desktop-runtime.json` binds the shell version, Electron's Node version, platform, architecture, shared package versions, and final file inventory. Startup reads the metadata and checks shared package records. Release schema, shell version, target compatibility, and file integrity are verified during packaging. Core packages are never copied into profile storage or installed by pnpm at first launch. +The signed `resources/app.asar/dsh/desktop-runtime.json` binds the shell version, Electron's Node version, platform, architecture, shared package versions, and final file inventory. Startup reads the metadata and checks shared package records. Release schema, shell version, target compatibility, and file integrity are verified during packaging. Reintroducing startup release comparisons requires a concrete installation failure that packaging validation and actual Host diagnostics cannot adequately explain. Core packages are never copied into profile storage or installed by pnpm at first launch. 1. The main window loads the shared Web loading page offscreen from packaged static assets before profile preparation or backend startup. Shared profile initialization creates missing manifest, empty user patch, and pnpm workspace files without overwriting existing files. 2. Before Host startup, Desktop validates the runtime descriptor and prepares the profile without modifying installed packages, declarations, or the lockfile; it removes only the `.dsh-module-fallback` projections earlier link-backend launches wrote, and startup never runs pnpm. Packages inside the profile keep native precedence, and installation package names resolve through the runtime resolution ([lookup order](../../.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.md); [cleanup removal](../../.agents/notes/implemented/simplification/2026-09-19-remove-desktop-profile-core-cleanup.md)). @@ -160,6 +162,8 @@ The Web counterparts are `pnpm run dev:web` and `pnpm run start:web`, documented The [native/renderer keyboard tests](tests/keyboard.spec.ts) compile as part of the repository Client typecheck. Their Desktop imports are limited to Cordis-free input, persistence, IPC, browser-guest, and overlay modules. +Dependency installation does not run the locked Electron package's separate binary installer. Before running the [Windows console-signal tests](tests/windows-cli-signals.spec.ts) directly, prepare the locked Electron binary with `pnpm --filter @deepseek-ai/dsh-desktop exec install-electron` from the repository root. For downloads through a proxy, enable `ELECTRON_GET_USE_PROXY=1` for that installer process. Windows coverage aggregates require successful provisioning before the instrumented unit inventory starts. + ### Startup onboarding The API-key input starts empty and uses `autocomplete="new-password"` to ask Chromium not to autofill saved login passwords. @@ -205,11 +209,11 @@ pnpm --dir apps/desktop run package:win:x64 --build-version 0.1.6-alpha.1.202609 A production release publishes the product version and takes no `--build-version`. Uploading one tags the packaged commit as `desktop-v` once the artifacts are public; a build from a modified checkout is not tagged, and a tagging failure prints the command to run by hand rather than failing a completed upload. Test and local builds are deliberately left untagged, and every artifact records `dshBuildCommit` and `dshBuildDirty` in its manifest so a build handed over directly remains traceable. -Version derivation does not change the fixed update channel or `nightly.yml` / `nightly-mac.yml` filenames. SemVer orders `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`, and a stable base's test version precedes that stable release. Clients only accept a greater version: replacing a feed cannot move an installed higher version to a lower corrected version. Such clients need manual installation; keep automatic downgrade disabled. The [version decision](../../.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.md) explains why the channel does not supply the prerelease identifier. +Version derivation does not change the fixed update channel or `nightly.yml` / `nightly-mac.yml` filenames. SemVer orders `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`, and a stable base's test version precedes that stable release. Clients only accept a greater version: replacing a feed cannot move an installed higher version to a lower corrected version. Such clients need manual installation; keep automatic downgrade disabled. The [archived version decision](../../.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.md) explains why the channel does not supply the prerelease identifier. -Packaging, upload, and manual macOS signature verification read `apps/desktop/.env.windows` or `.env.macos`, selected by target platform. Copy the [Windows template](.env.windows.example) or [macOS template](.env.macos.example) and fill in the local settings; Git ignores both local files, and packaged artifacts exclude them. Release fields come only from the target file, without fallback to system or shell variables; `PATH`, proxies, and build-tool settings remain inherited. The published version is an argument rather than a release field, and upload reads it from the completion record the packaging run wrote. Files use UTF-8 with optional BOM; relative certificate, SignTool, Apple API key, and keychain paths resolve from `apps/desktop`, values are not shell-expanded, and passwords containing `#` or spaces need quotes. CI also creates the target file before invoking packaging. +Except for local unsigned macOS builds, packaging, upload, and manual macOS signature verification read `apps/desktop/.env.windows` or `.env.macos`, selected by target platform. Copy the [Windows template](.env.windows.example) or [macOS template](.env.macos.example) and fill in the local settings; Git ignores both local files, and packaged artifacts exclude them. Release fields come only from the target file, without fallback to system or shell variables; `PATH`, proxies, and build-tool settings remain inherited. The published version is an argument rather than a release field, and upload reads it from the completion record the packaging run wrote. Files use UTF-8 with optional BOM; relative certificate, SignTool, Apple API key, and keychain paths resolve from `apps/desktop`, values are not shell-expanded, and passwords containing `#` or spaces need quotes. CI also creates the target file before invoking packaging. -Every package command checks the application ID, update origin, and mode-specific signing configuration before building or downloading, then probes the external tools the run will use: the archive reader, and on a Windows target the installer compiler. macOS checks the identity, Team ID, one complete notarization strategy, readable local `CSC_LINK` p12 file, explicit `CSC_KEY_PASSWORD`, and referenced API key and keychain files; Windows checks the public code-signing certificate, SignTool file, container name, and PIN format. Windows preparation-only and explicit unsigned builds do not require signing credentials. Configuration checks do not authenticate the PIN, log in to the token, unlock a keychain, or contact Apple; actual signing and notarization perform those checks. `--build-version auto` does contact the destination bucket, including under `--check`. Run the same checks separately: +Every package command checks the application ID and its mode-specific update and signing configuration before building or downloading, then probes the external tools the run will use: the archive reader, and on a Windows target the installer compiler. macOS checks the identity, Team ID, one complete notarization strategy, readable local `CSC_LINK` p12 file, explicit `CSC_KEY_PASSWORD`, and referenced API key and keychain files; Windows checks the public code-signing certificate, SignTool file, container name, and PIN format. Windows preparation-only builds and explicit unsigned builds on either platform do not require signing credentials. Configuration checks do not authenticate the PIN, log in to the token, unlock a keychain, or contact Apple; actual signing and notarization perform those checks. `--build-version auto` does contact the destination bucket, including under `--check`. Run the same checks separately: ```sh pnpm --dir apps/desktop run check:package @@ -233,13 +237,42 @@ The macOS arm64 command requires Apple Silicon. The macOS x64 command runs on In Each target owns its packed package inputs, prepared runtime, package set, dsh tree, pnpm preparation state, unpacked application, update metadata, and final artifacts under `apps/desktop/.desktop-build/targets//`. The Electron archive cache remains shared under `.desktop-build/downloads` because every archive name includes its version, platform, and architecture and is verified before extraction. A target build never consumes another target's mutable preparation state. +### Local macOS DMG without release signing + +After `pnpm install`, run one command from the repository root. No `.env.macos`, Apple account, certificate, or update service configuration is required: + +```sh +pnpm run package:desktop:mac:arm64:unsigned +pnpm run package:desktop:mac:x64:unsigned +``` + +Add `--check` to validate local configuration and tools without building, or `--dir` to stop at the unpacked application. The DMG is `apps/desktop/.desktop-build/targets//unsigned-artifacts/deepseek-harness--mac--unsigned.dmg`. After all checks pass, the command prints `DMG:` followed by its absolute path. Open it and copy the application to a local test directory. + +These commands ignore `.env.macos` and inherited release settings, use `com.deepseek.harness` as the app ID, and omit automatic-update and mandatory-update configuration. Build tools, standard network proxies, and `DSH_DESKTOP_NPM_REGISTRY` remain available from the shell. They skip Developer ID signing, Apple notarization, temporary signing keychains, and notarization proxy changes. Electron receives a local ad-hoc signature without certificates so the modified executable can run; the DMG is unsigned. Both preparation and the assembled application still run runtime integrity and smoke checks. Local builds omit update feeds, ZIPs, and release completion records, and the upload commands cannot publish them. macOS may require approval in System Settings → Privacy & Security to open an unnotarized application. + +### Local Official bundles from a loopback registry + +The test DMG excludes the provider runtimes the Official on-demand catalog advertises, and a release publishes matching bundle versions to npm. To install locally built bundles before that release, start a loopback registry that packs and serves the catalog's complete local dependency closure: + +```sh +pnpm run dev:bundle-registry +``` + +The command validates the catalog and the workspace inventory, runs `pnpm run build:official`, packs the closure into a new `dist/test-bundles//` directory, writes `bundle-registry.json` last as the completion marker, and only then prints the artifact path, the registry URL, and the exact `name@version` specs. It serves `127.0.0.1` on an allocated port under a fresh namespace until Ctrl+C or SIGTERM, and stopping it leaves both the artifacts and the installed packages on disk. It needs no `.env.macos`, Apple credentials, or npm credentials, and it does not change `pnpm run package:desktop:unsigned`. + +In the packaged application, open Plugins → Add plugin, paste one printed spec, choose **Custom address**, paste the printed registry URL, and install. A custom registry is asked alone, so this process must answer every dependency: repository-owned packages come from the run's own archives, and third-party packages keep npm's real metadata and bytes through a fixed redirect to `registry.npmjs.org`, which still requires network access. The installed entry stays Official because its package name is in the catalog. The registry choice is remembered in this browser; choose an offered registry again to switch back. + +A run's artifacts are immutable, so a rebuild serves a new namespace and a new URL, and the Official **Update** control does not appear while the installed and running versions match. The profile records the previous run's tarball URL in `pnpm-lock.yaml`, and pnpm verifies recorded entries, so installing or reinstalling that bundle against a new URL is refused with `ERR_PNPM_TARBALL_URL_MISMATCH` and can leave the earlier files in place. To test changed code at the same version, clear the stale resolution first — `pnpm clean --lockfile` in `$DSH_HOME/profiles/`, or install into a fresh profile — then install again from the new URL. `bundle-registry.json` records each archive's version, filename, size, and SHA-512 integrity together with the packing checkout's commit and dirty flag. + +The served `time` is the real packing time, so pnpm 11's default 24-hour `minimumReleaseAge` records the exemption it granted in the profile's `pnpm-workspace.yaml` (`minimumReleaseAgeExclude`); an explicitly stricter policy there fails the installation with pnpm's own message instead of being overridden. A `@scope:registry` route in `~/.npmrc` or the profile's `.npmrc` overrides `--registry` for that scope, so it must not route the served scope. + ### Runtime file selection Desktop packs workspace packages locally and installs external dependencies through the target's bundled Node and pnpm. [Desktop's file policy](scripts/runtime-file-policy.ts) then filters the immutable `resources/app.asar/dsh/node_modules` copy before signing and integrity sealing. It omits TypeScript declarations, recognized JavaScript/CSS/TypeScript source maps, TypeScript build caches, Domino's test directory, selected native compiler outputs, and node-pty prebuilds for other platforms. It preserves runtime JavaScript, native modules and their DLL/EXE helpers, WASM, unknown assets, licenses, and notices. Dependency manifests pass through electron-builder's metadata cleanup before integrity sealing, so archiving preserves their recorded bytes. The policy does not alter npm tarballs, the bundled package manager, or user-installed plugin files. The [Office conversion provider](../../packages/document/office-to-pdf/README.md) carries the target’s declared native engine, or WASM when the kit declares no matching native target. Preparation rejects a missing target engine before packaging. The complete Office dependencies, including CLI, JavaScript libraries, and the selected engine’s executables, data, licenses, and notices, are unpacked under `resources/app.asar.unpacked/dsh/node_modules/`. The Desktop Host resolves engine manifests to these physical directories and supplies loaded skills with absolute standalone Node and unpacked CLI paths. Node lives in `resources/runtime/primary-runtime/dependencies/node/bin/`; the CLI is the unpacked `@deepseek-ai/libreoffice-kit/lib/cli.js`. On macOS, the native helper receives the JIT entitlement required by [LibreOffice’s UNO bridge](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in). -The packaged application runs compiled JavaScript and pre-generated Typert metadata; it does not compile TypeScript plugins. Source-level debugger navigation and editor declarations remain available in development packages. [Copy-policy tests](tests/runtime-file-policy.spec.ts) cover exclusions and retained assets; The [payload smoke](tests/fixtures/runtime-payload-smoke.mjs) runs under Electron RunAsNode before the Host smoke and final inventory verification. The payload smoke resolves the search tool’s ripgrep executable and verifies text search and file enumeration. Signed Windows builds run these checks after dependency signing; other builds run them during `prepare:dsh`. The [Host smoke](scripts/smoke-runtime.ts) creates DOCX, XLSX, and PPTX inputs with bundled Python, converts each through the real Office provider, and checks PDF output. Every assembled application, including directory and unsigned Windows builds, repeats the payload and Host checks against ASAR. Archive integrity checks compare the complete archived descriptor with preparation, file bytes and membership in both archive and unpacked trees, recorded executable flags for archived files, and physical permissions for unpacked files. The Host smoke also obtains CLI paths from the loaded skill and runs capabilities and DOCX conversion with an empty PATH. Conversion failures stop packaging before a release record is written; macOS DMG/ZIP builds run these checks before notarization. +The packaged application runs compiled JavaScript and pre-generated Typert metadata; it does not compile TypeScript plugins. Source-level debugger navigation and editor declarations remain available in development packages. [Copy-policy tests](tests/runtime-file-policy.spec.ts) cover exclusions and retained assets; The [payload smoke](tests/fixtures/runtime-payload-smoke.mjs) runs under Electron RunAsNode before the Host smoke and final inventory verification. The payload smoke resolves the search tool’s ripgrep executable and verifies text search and file enumeration. Signed Windows builds run these checks after dependency signing; other builds run them during `prepare:dsh`. The [Host smoke](scripts/smoke-runtime.ts) creates DOCX, XLSX, and PPTX inputs with bundled Python, converts each through the real Office provider, and checks PDF output. Every assembled application, including directory and unsigned builds, repeats the payload and Host checks against ASAR. Archive integrity checks compare the complete archived descriptor with preparation, file bytes and membership in both archive and unpacked trees, recorded executable flags for archived files, and physical permissions for unpacked files. The Host smoke also obtains CLI paths from the loaded skill and runs capabilities and DOCX conversion with an empty PATH. Conversion failures stop packaging before a release record is written; macOS DMG/ZIP builds run these checks before notarization. Windows release qualification also runs [directory and replacement checks](scripts/smoke-windows.ps1) manually after the Desktop build. Set `$Makensis`, `$SevenZip`, and `$PluginDir` to the pinned builder’s NSIS compiler, 7-Zip executable, and x86-unicode NSIS plugin directory; pass the prepared `window-frame.dll` as `-FrameLibrary` to exercise the native extraction path and its failure report as well. From the repository root, run the command below. It verifies directory replacement and rollback, and both locked-file replacement modes; it is not part of the unit-test lane. @@ -289,7 +322,7 @@ The target dotenv file for internal-test packaging explicitly sets `DSH_DESKTOP_ Early internal-test packages use the `test` deployment. Select `production` explicitly only for a production release; changing upload credentials does not retarget an existing package. Packaging requires no COS credentials, disables electron-builder publishing, strips COS credentials from child processes, and records completion only after signing and notarization succeed. Upload validates this record, deployment, target, shared version, filenames, sizes, and SHA-512 before reading credentials. Packages and blockmaps upload before YAML; historical objects are retained. Every release publishes `nightly.yml` or `nightly-mac.yml`; stable releases also publish `latest.yml` or `latest-mac.yml` pointing at the same artifacts. Published YAML uses absolute binary URLs. The uploader leaves Cache-Control unset, including the empty header the COS SDK would otherwise add: deployment infrastructure owns cache policy, with uncached feeds and separately configurable binary caching. Serialize publication per target and verify public artifacts and feed contents before release qualification. -The macOS configuration uses the required release environment instead of accepting whichever certificate appears first in a keychain. It rejects empty values, a malformed Team ID, a signing identity that includes electron-builder's unsupported `Developer ID Application:` prefix, and incomplete notarization credentials. macOS packaging requires the configured identity and its private key. Runtime preparation applies that identity, a secure timestamp, and hardened runtime to every embedded Mach-O file; after signing the application, a deep strict check rejects any other leaf authority or Team ID before artifact creation. The fixed-target macOS installer commands create separate copies of the signed application and run two artifact lanes concurrently. One lane notarizes and staples the App before generating the ZIP and its update metadata. The other encloses its signed App copy in a signed DMG, then notarizes, staples, and verifies the DMG; its inner App has no individually stapled ticket. Both lanes must finish successfully before their artifacts reach the final directory and the release completion record is written. Directory-only commands also require notarization credentials and wait for Apple notarization and App stapling. The [parallel notarization decision](../../.agents/notes/implemented/process/2026-09-09-parallel-macos-notarization.md) owns copy isolation and container ticket semantics. `CSC_LINK` must name a local p12 containing the Developer ID Application certificate and private key; URLs and Base64 inputs are not supported. `CSC_KEY_PASSWORD` is its export password, not an Apple account or login password; an explicitly empty value supports an unencrypted p12. Before building, packaging creates and unlocks a private temporary keychain, imports the p12, authorizes signing, and signs a small probe. Runtime and App signing use this keychain explicitly; existing login keychains require no setup or manual unlocking. Child processes receive its path without the p12 password. The keychain is deleted after success or ordinary failure; CI must clean temporary credentials after forced termination. CI writes the certificate and `.env.macos` from its secret store, restricts file access, and deletes both after the job. Ambient `CSC_NAME` and certificate discovery order do not select the release owner. Notary credentials may instead use electron-builder's complete Apple ID or keychain-profile strategy. The two macOS identity variables are also required when repeating the application check manually with `pnpm --dir apps/desktop run verify:mac-signature -- `. +The signed macOS configuration uses the required release environment instead of accepting whichever certificate appears first in a keychain. It rejects empty values, a malformed Team ID, a signing identity that includes electron-builder's unsupported `Developer ID Application:` prefix, and incomplete notarization credentials. Signed macOS packaging requires the configured identity and its private key. Runtime preparation applies that identity, a secure timestamp, and hardened runtime to every embedded Mach-O file; after signing the application, a deep strict check rejects any other leaf authority or Team ID before artifact creation. The signed fixed-target macOS installer commands create separate copies of the signed application and run two artifact lanes concurrently. One lane notarizes and staples the App before generating the ZIP and its update metadata. The other encloses its signed App copy in a signed DMG, then notarizes, staples, and verifies the DMG; its inner App has no individually stapled ticket. Both lanes must finish successfully before their artifacts reach the final directory and the release completion record is written. Signed directory-only commands also require notarization credentials and wait for Apple notarization and App stapling. The [parallel notarization decision](../../.agents/notes/implemented/process/2026-09-09-parallel-macos-notarization.md) owns copy isolation and container ticket semantics. `CSC_LINK` must name a local p12 containing the Developer ID Application certificate and private key; URLs and Base64 inputs are not supported. `CSC_KEY_PASSWORD` is its export password, not an Apple account or login password; an explicitly empty value supports an unencrypted p12. Before building, packaging creates and unlocks a private temporary keychain, imports the p12, authorizes signing, and signs a small probe. Runtime and App signing use this keychain explicitly; existing login keychains require no setup or manual unlocking. Child processes receive its path without the p12 password. The keychain is deleted after success or ordinary failure; CI must clean temporary credentials after forced termination. CI writes the certificate and `.env.macos` from its secret store, restricts file access, and deletes both after the job. Ambient `CSC_NAME` and certificate discovery order do not select the release owner. Notary credentials may instead use electron-builder's complete Apple ID or keychain-profile strategy. The two macOS identity variables are also required when repeating the application check manually with `pnpm --dir apps/desktop run verify:mac-signature -- `. macOS signing visits real files without following Framework symlink aliases. PAK resources retain all shipped languages and are sealed by the enclosing Framework or application signature instead of receiving individual signatures. The [release policy](../../.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.md) owns the dependency patch and verification requirements. @@ -323,11 +356,11 @@ The command requires `DSH_DESKTOP_APP_ID` and the normal build dependencies, inc ### Windows installer interface -The Windows installer uses native NSIS pages with light and dark palettes, system shadows, an editable installation directory, and a finish page whose launch checkbox is selected by default. Installation is restricted to the current user. Clicking Install or pressing Enter validates the current path; new destinations must be empty, and nonempty destinations must be registered installations. Running executables at the affected installation path produce a native prompt and remain running; same-named applications in other directories do not block installation. Silent updates wait up to ten seconds for the affected application to exit, then stop with exit code 2 if it is still running. +The Windows installer uses native NSIS pages with light and dark palettes, system shadows, an editable installation directory, and a finish page whose launch checkbox is selected by default. Installation is restricted to the current user. Clicking Install or pressing Enter validates the current path; drive roots remain invalid after normalization, including repeated trailing separators. New destinations must be empty, and nonempty destinations must be registered installations. Running executables at the affected installation path produce a native prompt and remain running; same-named applications in other directories do not block installation. Silent updates wait up to ten seconds for the affected application to exit, then stop with exit code 2 if it is still running. The theme follows Windows at startup; `/THEME=light`, `/THEME=dark`, and `/THEME=auto` select a palette explicitly. The window appears after its branded controls are ready. When the welcome page first appears, the installer moves above ordinary windows once and flashes its taskbar button if another window has focus; it does not remain always on top. Progress reads the pinned 7-Zip extractor’s percentage; directory promotion, registration, and cleanup retain bounded estimates. The weighted percentage does not predict remaining time. After NSIS reports success, the bar fills over 600 ms and displays 100% briefly before the finish page appears; the transition targets 750 ms. The finish page preserves the window position. Finish dismisses the installer before launching the installed executable; a launch failure restores the page for retry. Directory replacement and failure recovery follow the installation flow described above. First-launch profile preparation remains a separate Desktop operation. -Windows packaging compiles an x86 Win32/GDI+ helper with Visual C++ Build Tools and a Windows SDK; signed builds sign this helper through the configured Windows signer. The preparation hook leaves production dependency collection to electron-builder on every platform. The [installer decision](../../.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.md) records the NSIS integration and release checks. +Windows packaging compiles an x86 Win32/GDI+ helper with Visual C++ Build Tools and a Windows SDK; signed builds sign this helper through the configured Windows signer. The preparation hook leaves production dependency collection to electron-builder on every platform. Run `pnpm --dir apps/desktop run test:installer` from the repository root on an interactive Windows x64 desktop to build and exercise a small native test payload through the production installer configuration. Each run uses a unique product identity and sequentially exercises English-only and Chinese-only installer variants, selecting test labels from the displayed welcome button. Both variants install into private directories and uninstall after testing; screenshots and results remain under `.desktop-build/installer-tests/`. The checks include upgrades to registered paths with trailing separators and rejection of drive roots. The optional `--signed` flag uses the Windows EV configuration below to sign test executables and the helper before embedding them; it does not enable an update feed. @@ -390,9 +423,9 @@ pnpm run prepare:desktop This diagnostic command is an alternative stopping point, not the first half of a two-command build. A later `package:desktop*` command repeats the official build and preparation so it cannot consume stale dsh packages, runtime files, or dsh content. -Every package command builds the repository, packs the first-party production closures rooted at dsh and the private Desktop Host, and prepares the target Electron distribution and pnpm CLI. `prepare:dsh` installs the production graph once at build time, prepares materialized packages for electron-builder to archive under `app.asar/dsh`, removes package-manager metadata, and writes `desktop-runtime.json` with shared package versions and final file hashes. On macOS it signs and verifies native files before inventory generation; electron-builder excludes this already-signed tree from nested re-signing. Resource mappings explicitly include `dsh/node_modules`, which the default root-directory filter omits; the prepared runtime inventory is checked after native signing. Native executables and libraries are unpacked beside ASAR; Python, standalone Node and pnpm remain in external runtime resources. Windows packaging checks every prepared PE against its ASAR unpacked entry and byte-identical disk copy, including unsigned builds. Builder glob rules match braces in PE filenames as single-character wildcards, so a matching neighboring file may also be unpacked. Prepared runtime smoke uses the verified target descriptor rather than the build host architecture. Signed installer, notarization, installed upgrade, and target-specific native-module qualification require the release environment. +Every package command builds the repository, packs the first-party production closures rooted at dsh and the private Desktop Host, and prepares the target Electron distribution and pnpm CLI. `prepare:dsh` installs the production graph once at build time, prepares materialized packages for electron-builder to archive under `app.asar/dsh`, removes package-manager metadata, and writes `desktop-runtime.json` with shared package versions and final file hashes. Signed macOS builds sign and verify native files before inventory generation; electron-builder excludes this already-signed tree from nested re-signing. Resource mappings explicitly include `dsh/node_modules`, which the default root-directory filter omits; the prepared runtime inventory is checked after native signing. Native executables and libraries are unpacked beside ASAR; Python, standalone Node and pnpm remain in external runtime resources. Windows packaging checks every prepared PE against its ASAR unpacked entry and byte-identical disk copy, including unsigned builds. Builder glob rules match braces in PE filenames as single-character wildcards, so a matching neighboring file may also be unpacked. Prepared runtime smoke uses the verified target descriptor rather than the build host architecture. Signed installer, notarization, installed upgrade, and target-specific native-module qualification require the release environment. -macOS packaging writes `Contents/Resources/app-update.yml` while assembling the App and before code signing, including the directory build that feeds the parallel ZIP and DMG lanes. The signing hook verifies the exact feed and updater cache directory. Both lane copies and the promoted App are checked again before the release completion record is written; a missing or mismatched configuration prevents artifact promotion and therefore prevents upload. +Signed macOS packaging writes `Contents/Resources/app-update.yml` while assembling the App and before code signing, including the directory build that feeds the parallel ZIP and DMG lanes. The signing hook verifies the exact feed and updater cache directory. Both lane copies and the promoted App are checked again before the release completion record is written; a missing or mismatched configuration prevents artifact promotion and therefore prevents upload. An unpacked artifact contains Electron, the materialized dsh production tree, pnpm, and the shell application. Installer size and filesystem size differ; release qualification measures both, plus the profile’s plugin storage and first-launch latency. The runtime trades more application files for eliminating core package installation on the user’s machine. @@ -412,9 +445,11 @@ If task teardown fails after confirmed Host exit, installation is refused and th Confirmed Host exit without successful task teardown displays localized recovery guidance in both ordinary and mandatory update dialogs. A typed preparation cause selects that guidance in each locale; changing translated wording cannot reclassify the failure. “View technical details” is collapsed by default and exposes only exit status, signal, shutdown acknowledgement, and deadline facts, not plugin stderr. Expanding it neither retries nor authorizes installation. + + ### Mandatory update policy -The [mandatory client decision](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.md) owns policy polling and the blocking window. Packaging reads `.env.windows` or `.env.macos`: `DSH_DESKTOP_AUTO_UPDATE_ENV=test` (the default) selects `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`; `production` selects `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`. The templates leave both origins blank; fill in the selected deployment origin in the ignored local dotenv file. The selected origin is required before preparation or signing, including unsigned and preparation-only builds; the unselected origin is optional. These settings never fall back to the parent environment or the other deployment. Packaging embeds the selected policy with the application ID; packaged applications ignore runtime overrides. +Packaging reads `.env.windows` or `.env.macos`: `DSH_DESKTOP_AUTO_UPDATE_ENV=test` (the default) selects `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`; `production` selects `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`. The templates leave both origins blank; fill in the selected deployment origin in the ignored local dotenv file. Local unsigned macOS builds omit this policy and do not read either file. Other modes require the selected origin before preparation or signing, including unsigned Windows and preparation-only builds; the unselected origin is optional. These settings never fall back to the parent environment or the other deployment. Packaging embeds the selected policy with the application ID; packaged applications ignore runtime overrides. `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON supplies test login origins and optional polling and download-page options; packaging rejects `origin` and `authentication` inside it. The page allowlist defaults to the selected service origin; explicitly allow other approved download-page origins when needed. Test builds select `feishu-test` and require `allowedAuthOrigins` in `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG`; production selects `anonymous` and rejects that field. Each login origin must be an HTTPS origin without credentials, path, query, or fragment. The login window permits document navigation only to the selected policy origin and these configured origins. Policy requests reject redirects; only test authentication carries gateway cookies. Unpackaged development instead reads a complete policy JSON from this variable and requires `DSH_DESKTOP_APP_ID`; absent JSON disables development policy queries, and only anonymous development permits HTTP `127.0.0.1`. A user-initiated ordinary check triggers policy work concurrently but never waits for or reports a policy failure. Only a confirmed blocking decision cancels ordinary dialogs. Test authentication waits until the active ordinary dialog finishes, and cancellation or failure does not discard the updater result. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index 61caaf038d..d8256885dc 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -4,7 +4,7 @@ 桌面埋点遵循[产品采集策略](../../packages/client/product-analytics/README.zh.md)及其动态应用配置,不包含 Web 使用情况。安装更新会等待该操作的本地埋点接收请求结束,再锁定 API 准入并停止 Host。接收请求的时限为一秒,失败不会阻止安装,也不等待收集端完成发送。 -桌面应用是完整 dsh Web 应用外的一层 Electron 壳。Electron RunAsNode 子进程启动共享 profile runner,Electron 立即从 `dsh-app://app/` 加载打包内的 Web 入口。共享加载页等待 Host 启动注入,然后在同一文档中启动客户端。Electron 将应用 HTTP 请求转发给已认证的 Web Host,转发时丢弃描述 Node fetch 连接而非资源本身的响应头(`transfer-encoding`、`connection`、`keep-alive`),并把插件 bundle 响应标记为 `no-store`,因为其每次启动都变化的 revision 只会在 Chromium 磁盘缓存中累积;WebSocket 流连接到该 Host,仅为归属的应用窗口附加凭据。Node IPC 承载启动注入、就绪与关闭。Desktop 默认使用端口 `19387`,与 Web 的 `3080` 分开;可通过 `webserver.config.port` patch 覆盖。 +桌面应用是完整 dsh Web 应用外的一层 Electron 壳。Electron RunAsNode 子进程启动共享 profile runner,Electron 立即从 `dsh-app://app/` 加载打包内的 Web 入口。共享加载页等待 Host 启动注入,然后在同一文档中启动客户端。Electron 将应用 HTTP 请求转发给已认证的 Web Host,转发时丢弃描述 Node fetch 连接而非资源本身的响应头(`transfer-encoding`、`connection`、`keep-alive`),并把插件 bundle 响应标记为 `no-store`,因为其每次启动都变化的 revision 只会在 Chromium 磁盘缓存中累积;WebSocket 流连接到该 Host,仅为归属的应用窗口附加凭据。Node IPC 承载启动注入、就绪与关闭。Desktop 默认监听系统分配的端口,因此不会与 Web 的 `3080` 或系统保留端口冲突;可通过 `webserver.config.port` patch 覆盖。 应用菜单第一项“**关于 DeepSeek Harness**”打开 Electron 原生关于面板,展示应用图标、产品名称和当前安装的发布版本。菜单文案跟随桌面壳的语言。macOS 的隐藏、隐藏其他、显示全部和退出条目使用本地化文案,隐藏和退出条目包含 DeepSeek Harness 产品名称。这些条目保留原生动作和快捷键。macOS 从应用包读取图标,因此未打包的开发启动会显示 Electron 图标;Windows 使用随包分发的 PNG。 @@ -14,7 +14,7 @@ Creator 和 Web Plugin Manager 在 Electron Node 模式下使用 Desktop 内置 Platform 内嵌文档使用持久化 WebContentsView 分区,分区名由 Platform 来源和稳定账号 ID 的哈希决定。localStorage 中的页面偏好(包括已关闭的通知)在关闭视图和重启应用后保留;不同账号和来源使用独立存储。账号 ID 来自 Host 最近一次成功的资料读取;尚无该 ID 时,文档在一次性分区中打开,该分区不跨次保留偏好。打开持久分区会先清理 Cookie、文件系统、IndexedDB、Cache Storage、HTTP 与着色器缓存、Service Worker 及 HTTP 认证状态;一次性分区则清理其全部存储。关闭视图会销毁文档、移除请求拦截器并安排相同的清理,因此异常退出遗留的认证会在下一个文档加载前被清除。下次打开和应用退出都会等待该清理完成,更新安装也会在安装器接管退出前等待。清理失败会使该次打开失败,并在后续清理成功前阻止同一账号打开;其他账号不受影响。退出登录会销毁文档,但保留账号偏好供下次登录使用。同一凭证下账号 ID 迟到时,已以一次性分区打开的文档保持挂载;下次打开使用账号分区。[存储决策](../../.agents/notes/implemented/architecture/2026-09-22-platform-browser-storage.zh.md)说明保留策略。Host 通过私有 Node IPC 发送账号凭证;账号 RPC 和 Harness 渲染进程不接收 token。Platform preload 在页面脚本执行前通过一次同步 IPC 读取主进程中已准备的凭证。它暴露 displayMode、同步的 getAuthToken() 和 getLocale() getter,以及返回取消订阅函数的 onLocaleChange(listener)。两个 getter 都只读取 preload 内存,不再调用 IPC。bootstrap 包含 Desktop 已解析的语言(`zh_CN` 或 `en_US`);Settings 语言变更会更新 preload 缓存并通知已打开的 Platform 文档,无需重载。Platform 在首屏渲染前应用该语言,且不将其持久化为浏览器偏好。主进程处理器仅校验调用来源并读取内存,不等待 Host、磁盘或网络。可信页面初始化失败时保留内嵌模式,由 getter 抛错,避免回退到浏览器凭证。只有受控 Platform 页面中、位于所配置签发来源的主 frame 能完成初始化。退登、凭证替换、Host 关闭及视图关闭都会销毁文档。跨来源文档导航被阻止。请求新窗口的 HTTPS 链接在系统浏览器中打开,不携带内嵌会话或 token;其他协议及带 URL 凭证的链接被拒绝。原生视图占据 Account 功能返回栏下方的视口。 -Desktop Host 的 Platform API 请求与更新策略请求用相同的 Platform 客户端请求头标识已安装客户端:平台、客户端版本、语言、以秒为单位的时区偏移,以及有意保持为空的 bundle id。账号操作按调用逐次传入调用界面的身份;账号 provider 管理[仅 API 使用的请求头配置](../../packages/credentials/deepseek-account-platform/README.zh.md#use-this-package)。更新策略额外上报架构、更新通道和内置运行时版本。 +Desktop Host 的 Platform API 请求与更新策略请求用相同的 Platform 客户端请求头标识已安装客户端:平台、客户端版本、语言、以秒为单位的时区偏移,以及有意保持为空的 bundle id。账号操作按调用逐次传入调用界面的身份;账号 provider 管理[仅 API 使用的请求头配置](../../packages/credentials/deepseek-account-platform/README.zh.md#use-this-package)。更新策略额外上报架构、更新通道和内置运行时版本。 原生账号状态解析器接受 `no-response` 登录失败,并在供 UI 使用的安全投影中保留该错误码。 账号凭据被服务端判定失效后,未配置官方 API key 时返回 Welcome;有可用 API key 时保持工作区打开。主动退出登录遵循相同规则。Welcome 和工作区均显示本地化的登录失效提示。 @@ -58,7 +58,7 @@ macOS PNG 使用带留白的圆角底板,供传统 ICNS 打包使用,包含 ### 内置工作区依赖 -electron-builder 只把清单中的 `dependencies` 复制进 `app.asar/node_modules`,因此 Electron 主进程 bundle `lib/main.js` 内联其工作区 devDependencies,裸导入只剩 `electron`、Node 内置模块与这些 `dependencies`;沙箱 preload 只能留下 `electron`、`events`、`timers` 与 `url`,即其 `require` polyfill 能解析的模块。主进程 bundle 从被内联包的 `lib/` 产物解析它们,所以根 `build:lib:host` 在并发的工作区 tsdown 阶段之后才为 `apps/desktop` 打 bundle,并由 [`desktop-bundle-imports`](scripts/desktop-bundle-imports.mjs) 让任何静态、动态或 `require()` 导入无法在打包应用内解析的 Desktop bundle 直接失败。没有这项检查时,rolldown 无法解析的导入会作为外部说明符进入产物,并在启动时以 `ERR_MODULE_NOT_FOUND` 失败。[bundle 顺序决策](../../.agents/notes/implemented/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.zh.md)记录了备选方案。 +electron-builder 只把清单中的 `dependencies` 复制进 `app.asar/node_modules`,因此 Electron 主进程 bundle `lib/main.js` 内联其工作区 devDependencies,裸导入只剩 `electron`、Node 内置模块与这些 `dependencies`;沙箱 preload 只能留下 `electron`、`events`、`timers` 与 `url`,即其 `require` polyfill 能解析的模块。主进程 bundle 从被内联包的 `lib/` 产物解析它们,所以根 `build:lib:host` 在并发的工作区 tsdown 阶段之后才为 `apps/desktop` 打 bundle,并由 [`desktop-bundle-imports`](scripts/desktop-bundle-imports.mjs) 让任何静态、动态或 `require()` 导入无法在打包应用内解析的 Desktop bundle 直接失败。没有这项检查时,rolldown 无法解析的导入会作为外部说明符进入产物,并在启动时以 `ERR_MODULE_NOT_FOUND` 失败。[已归档的bundle 顺序决策](../../.agents/notes/archived/process/2026-09-22-desktop-main-bundle-after-workspace-tsdown.md)记录了备选方案。 Windows 签名打包按 PE 文件内容扫描第一方运行时和应用生产依赖,包括没有常规扩展名的文件。最终扫描覆盖整个解包应用。目录链接、格式错误的 `MZ` 文件以及非 PE 的 `.exe`、`.dll` 或 `.pyd` 文件会使打包停止;以 `MZ` 开头的数据文件也会被拒绝,除非包含有效 PE 头。它保留有效的上游签名,并在记录运行时哈希或执行冒烟检查前为未签名代码补签。公钥验签每个进程处理最多 32 个文件,同时最多运行四个进程;硬件令牌签名仍串行执行,每个新签名必须匹配配置的证书且带时间戳。硬件签名或验签失败会停止本轮执行;独立的时间戳请求遵循下文的有界重试规则。electron-builder 只有在验签和逐字节比对通过后,才保留复制后运行时可执行文件的签名。写入发布完成记录前,必须通过最终 PE 签名检查,以及使用全新缓存的 ASAR 载荷和 Host 冒烟检查。开发、仅准备和未签名构建不使用硬件令牌,可能被 Windows 代码完整性策略阻止;任何构建模式都不会关闭该策略。冒烟检查通过不代表兼容所有企业策略。 @@ -86,9 +86,11 @@ Node 准备内置解释器和 Python 库,无需系统 Python 或 pip。[下载 Welcome 加载共享 Toast 的配色和阴影变量,挂载在 body 下的通知使用系统字体。 + + ## 内置命令运行时 -安装后的 `resources/runtime/cli/bin/dsh` shell 脚本(Windows 为 `dsh.cmd`)使用 Desktop 的 Electron 可执行文件和内置 pnpm 运行普通 CLI 分派入口。Desktop 关闭时也可使用,并保留 [Electron 运行时限制](../../.agents/notes/implemented/architecture/2026-09-11-desktop-electron-node-runtime.zh.md)。普通 profile、配置和插件命令与 npm dsh 使用相同实现;该命令不会打开 Desktop。 +安装后的 `resources/runtime/cli/bin/dsh` shell 脚本(Windows 为 `dsh.cmd`)使用 Desktop 的 Electron 可执行文件和内置 pnpm 运行普通 CLI 分派入口。Desktop 关闭时也可使用,并保留 Electron 运行时限制。普通 profile、配置和插件命令与 npm dsh 使用相同实现;该命令不会打开 Desktop。 管理 Desktop 插件前,先启动一次 Desktop 以初始化其 profile,完全退出应用,再运行 `dsh plugin --profile desktop add `、`list` 或 `remove `。重新打开 Desktop 后使用更改。包操作保留共享的 profile 写锁和兼容性检查。内置命令拒绝未初始化的 Desktop profile,不会在其位置创建普通 CLI profile。 @@ -118,7 +120,7 @@ macOS 上自定义菜单保留 Electron 的标准 Window 菜单及应用隐藏 ### 运行时与插件激活 -签名资源中的 `resources/app.asar/dsh/desktop-runtime.json` 绑定 shell 版本、Electron 的 Node 版本、平台、架构、共享包版本和最终文件清单。启动读取元数据,并检查共享包记录。发布 schema、shell 版本、目标兼容性和文件完整性在打包时验证。首次启动不会把核心包复制到 profile 存储或通过 pnpm 安装核心包。 +签名资源中的 `resources/app.asar/dsh/desktop-runtime.json` 绑定 shell 版本、Electron 的 Node 版本、平台、架构、共享包版本和最终文件清单。启动读取元数据,并检查共享包记录。发布 schema、shell 版本、目标兼容性和文件完整性在打包时验证。只有出现打包校验与实际 Host 诊断无法充分解释的具体安装故障,才重新引入启动时的发布信息比较。首次启动不会把核心包复制到 profile 存储或通过 pnpm 安装核心包。 1. 主窗口在 profile 准备或后端启动前,从打包静态资源于屏幕外加载共享 Web 加载页。共享 profile 初始化创建缺失的 manifest、空用户 patch 与 pnpm workspace 文件,不覆盖现有文件。 2. 启动 Host 前,Desktop 校验运行时描述符并准备 profile,不改动已安装的包、依赖声明与锁文件;只删除早期 Link 后端启动写下的 `.dsh-module-fallback` 投影,启动从不运行 pnpm。profile 内的包保持原生优先级,本体包名通过 runtime resolution 解析([查找顺序](../../.agents/notes/implemented/architecture/2026-09-19-profile-resolution-lookup-order.zh.md);[清理移除](../../.agents/notes/implemented/simplification/2026-09-19-remove-desktop-profile-core-cleanup.zh.md))。 @@ -162,6 +164,8 @@ Web 侧的对应命令是 `pnpm run dev:web` 与 `pnpm run start:web`,见[开 [原生输入与渲染进程键盘测试](tests/keyboard.spec.ts)直接纳入仓库 Client 类型检查。它只导入不依赖 Cordis 的 Desktop 输入、持久化、IPC、浏览器 guest 和蒙层模块。 +安装工作区依赖不会自动运行锁定版本 Electron 提供的独立二进制安装程序。直接运行 [Windows 控制台信号测试](tests/windows-cli-signals.spec.ts)前,在仓库根目录执行 `pnpm --filter @deepseek-ai/dsh-desktop exec install-electron`,准备锁定版本的 Electron 二进制。通过代理下载时,为该安装进程启用 `ELECTRON_GET_USE_PROXY=1`。Windows 覆盖率检查要求安装成功后,才启动带覆盖率插桩的单元测试集。 + ### 启动引导 API Key 输入框初始为空,并通过 `autocomplete="new-password"` 请求 Chromium 不要自动填入已保存的登录密码。 @@ -207,11 +211,11 @@ pnpm --dir apps/desktop run package:win:x64 --build-version 0.1.6-alpha.1.202609 production 发布使用产品版本本身,不传 `--build-version`。其上传成功后会把打包所用 commit 打成 `desktop-v<版本>` 标签;来自有改动工作区的构建不打标签,打标签失败也只打印手工命令,不会让已完成的上传变成失败。test 与本地构建有意不留标签,而所有产物的清单都记录 `dshBuildCommit` 与 `dshBuildDirty`,直接分发的构建同样可溯源。 -版本派生不改变固定更新通道,也不改变 `nightly.yml` / `nightly-mac.yml` 文件名。SemVer 排序为 `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`,稳定基础版本的测试版低于该稳定版。客户端只接受更高版本:替换 feed 无法让已安装的较高版本更新到较低的纠正版。这类客户端需要手动安装;保持自动降级关闭。[版本决策](../../.agents/notes/implemented/process/2026-09-16-desktop-release-version-derivation.zh.md)解释为什么不能用通道名替换预发布标识。 +版本派生不改变固定更新通道,也不改变 `nightly.yml` / `nightly-mac.yml` 文件名。SemVer 排序为 `0.1.6-alpha.1 < 0.1.6-alpha.1.20260916.1 < 0.1.6-alpha.2`,稳定基础版本的测试版低于该稳定版。客户端只接受更高版本:替换 feed 无法让已安装的较高版本更新到较低的纠正版。这类客户端需要手动安装;保持自动降级关闭。[已归档的版本决策](../../.agents/notes/archived/process/2026-09-16-desktop-release-version-derivation.md)解释为什么不能用通道名替换预发布标识。 -打包、上传以及手动 macOS 签名检查使用 `apps/desktop/.env.windows` 或 `.env.macos`,由目标平台选择。复制对应的 [Windows 模板](.env.windows.example) 或 [macOS 模板](.env.macos.example),填写本机配置;Git 忽略这两个本地文件,安装产物也不包含它们。发布字段只从目标文件读取,不回退到系统或 shell 中的同名变量;`PATH`、代理和构建工具环境仍保留。发布版本是命令参数而非发布字段,上传从打包写下的完成记录中读取它。文件使用 UTF-8,支持 BOM;相对证书、SignTool、Apple API Key 和钥匙串路径以 `apps/desktop` 为基准,变量值不做 shell 展开,包含 `#` 或空格的密码需要引号。CI 同样在运行前生成目标文件。 +除本地 macOS 未签名构建外,打包、上传以及手动 macOS 签名检查使用 `apps/desktop/.env.windows` 或 `.env.macos`,由目标平台选择。复制对应的 [Windows 模板](.env.windows.example) 或 [macOS 模板](.env.macos.example),填写本机配置;Git 忽略这两个本地文件,安装产物也不包含它们。发布字段只从目标文件读取,不回退到系统或 shell 中的同名变量;`PATH`、代理和构建工具环境仍保留。发布版本是命令参数而非发布字段,上传从打包写下的完成记录中读取它。文件使用 UTF-8,支持 BOM;相对证书、SignTool、Apple API Key 和钥匙串路径以 `apps/desktop` 为基准,变量值不做 shell 展开,包含 `#` 或空格的密码需要引号。CI 同样在运行前生成目标文件。 -每条打包命令在构建与下载前检查应用 ID、更新地址和该模式需要的签名配置,随后探测本次运行要用的外部工具:归档读取工具,以及 Windows 目标的安装器编译器。macOS 检查身份、Team ID、一套完整公证凭据、`CSC_LINK` 指定的可读本地 p12 文件、显式配置的 `CSC_KEY_PASSWORD`,以及引用的 API Key 和钥匙串文件;Windows 检查公开代码签名证书、SignTool 文件、容器名称和 PIN 格式。仅准备 Windows 资源或显式未签名打包不要求签名凭据。配置检查不验证 PIN 是否正确、Token 是否登录、钥匙串是否解锁或 Apple 是否接受凭据;实际签名与公证负责这些检查。`--build-version auto` 会访问目标 bucket,`--check` 下同样如此。单独运行相同检查: +每条打包命令在构建与下载前检查应用 ID 以及该模式需要的更新与签名配置,随后探测本次运行要用的外部工具:归档读取工具,以及 Windows 目标的安装器编译器。macOS 检查身份、Team ID、一套完整公证凭据、`CSC_LINK` 指定的可读本地 p12 文件、显式配置的 `CSC_KEY_PASSWORD`,以及引用的 API Key 和钥匙串文件;Windows 检查公开代码签名证书、SignTool 文件、容器名称和 PIN 格式。仅准备 Windows 资源以及两个平台上的显式未签名打包均不要求签名凭据。配置检查不验证 PIN 是否正确、Token 是否登录、钥匙串是否解锁或 Apple 是否接受凭据;实际签名与公证负责这些检查。`--build-version auto` 会访问目标 bucket,`--check` 下同样如此。单独运行相同检查: ```sh pnpm --dir apps/desktop run check:package @@ -235,13 +239,42 @@ macOS arm64 命令要求 Apple Silicon。macOS x64 命令可以在 Intel macOS 每个目标都在 `apps/desktop/.desktop-build/targets//` 下持有自己的打包输入、已准备运行时、包集合、dsh 依赖树、pnpm 准备状态、未打包应用、更新元数据和最终产物。Electron 归档缓存继续由 `.desktop-build/downloads` 共享,因为每个归档文件名都包含版本、平台和架构,并且在解包前经过验证。目标构建绝不读取其他目标的可变准备状态。 +### 无发布签名的本地 macOS DMG + +执行 `pnpm install` 后,在仓库根目录运行一条命令即可,无需 `.env.macos`、Apple 账号、证书或更新服务配置: + +```sh +pnpm run package:desktop:mac:arm64:unsigned +pnpm run package:desktop:mac:x64:unsigned +``` + +添加 `--check` 仅验证本地配置和工具而不构建,或添加 `--dir` 只生成解包应用。DMG 路径为 `apps/desktop/.desktop-build/targets//unsigned-artifacts/deepseek-harness--mac--unsigned.dmg`。所有检查通过后,命令打印 `DMG:` 及其绝对路径。打开后将应用复制到本地测试目录。 + +这些命令忽略 `.env.macos` 和继承的发布配置,使用 `com.deepseek.harness` 作为应用 ID,并省略自动更新和强制更新配置。构建工具、标准网络代理与 `DSH_DESKTOP_NPM_REGISTRY` 仍可从 shell 提供。它们跳过 Developer ID 签名、Apple 公证、临时签名钥匙串和公证代理修改。Electron 使用无需证书的本地 ad-hoc 签名,以便修改后的可执行文件能够运行;DMG 不签名。准备阶段和组装后的应用仍执行运行时完整性与冒烟检查。本地构建不生成更新源、ZIP 或发布完成记录,上传命令无法发布这些产物。macOS 可能要求在“系统设置 → 隐私与安全性”中批准打开未经公证的应用。 + +### 用回环 registry 安装本地 Official 包 + +测试 DMG 不包含 Official 按需目录所宣传的 provider 运行时,而正式发布才会把对应版本的 bundle 发到 npm。要在发布前安装本地构建的 bundle,请启动一个回环 registry,它会打包并提供该目录的完整本地依赖闭包: + +```sh +pnpm run dev:bundle-registry +``` + +该命令先校验目录与 workspace 清单,运行 `pnpm run build:official`,把闭包打包进新的 `dist/test-bundles//` 目录,最后写入 `bundle-registry.json` 作为完成标记,然后才打印产物路径、registry URL 和精确的 `name@version` 规格。它在分配到的端口上只监听 `127.0.0.1`,使用一次性的命名空间提供服务,直到 Ctrl+C 或 SIGTERM;停止后产物和已安装的包都保留在磁盘上。它不需要 `.env.macos`、Apple 凭据或 npm 凭据,也不改变 `pnpm run package:desktop:unsigned`。 + +在打包应用中打开“插件 → 添加插件”,粘贴一条打印出的规格,选择**自定义地址**,粘贴打印出的 registry URL 后安装。自定义 registry 会被单独询问,因此必须由本进程回答每一个依赖:仓库自有的包取自本次运行的产物,第三方包则通过固定重定向到 `registry.npmjs.org` 保留 npm 的真实元数据与字节,因此仍需要网络访问。安装后的条目仍归类为 Official,因为它的包名在目录中。registry 选择会在此浏览器中记住;再次选择某个提供的 registry 即可切回。 + +一次运行的产物是不可变的,所以重新构建会提供新的命名空间和新 URL;当已安装版本与运行版本一致时,Official 的**更新**控件不会出现。profile 会在 `pnpm-lock.yaml` 中记录上一次运行的 tarball URL,而 pnpm 会校验已记录的条目,因此用新 URL 再次安装或重装该 bundle 会被 `ERR_PNPM_TARBALL_URL_MISMATCH` 拒绝,并且可能继续保留旧文件。要在同一版本上验证改动后的代码,请先清除过期的解析结果——在 `$DSH_HOME/profiles/` 中运行 `pnpm clean --lockfile`,或改用全新的 profile——然后再从新 URL 安装。`bundle-registry.json` 记录每个归档的版本、文件名、大小和 SHA-512 完整性,以及打包所用 checkout 的 commit 和 dirty 标记。 + +所提供的 `time` 是真实的打包时间,因此 pnpm 11 默认的 24 小时 `minimumReleaseAge` 会在 profile 的 `pnpm-workspace.yaml` 中记录它授予的豁免(`minimumReleaseAgeExclude`);如果那里显式配置了更严格的策略,安装会以 pnpm 自己的报错失败,而不会被覆盖。`~/.npmrc` 或 profile 的 `.npmrc` 中的 `@scope:registry` 路由会覆盖该 scope 的 `--registry`,因此不能让它们指向正在提供服务的 scope。 + ### 运行时文件筛选 Desktop 在本地打包工作区包,并通过目标捆绑的 Node 和 pnpm 安装外部依赖。[Desktop 文件策略](scripts/runtime-file-policy.ts)随后在签名和完整性封装前过滤不可变的 `resources/app.asar/dsh/node_modules` 副本。它排除 TypeScript 声明、已识别的 JavaScript/CSS/TypeScript source map、TypeScript 构建缓存、Domino 测试目录、选定的原生编译器输出和其他平台的 node-pty 预构建文件。它保留运行时 JavaScript、原生模块及其 DLL/EXE 辅助文件、WASM、未知资源、许可证和 notices。依赖清单在完整性封装前经过 electron-builder 的元数据清理,确保归档保持已记录的字节。该策略不修改 npm tarball、捆绑的包管理器或用户安装的插件文件。 [Office 转换提供方](../../packages/document/office-to-pdf/README.zh.md)携带目标已声明的原生引擎;kit 未声明匹配原生目标时携带 WASM 引擎。准备阶段在打包前拒绝缺少目标引擎的情况。完整 Office 依赖(CLI、JavaScript 库和选定引擎的可执行文件、数据、许可证及 notices)解包到 `resources/app.asar.unpacked/dsh/node_modules/` 下。Desktop Host 将引擎清单解析到这些物理目录,并向加载的技能提供独立 Node 和解包后 CLI 的绝对路径。Node 位于 `resources/runtime/primary-runtime/dependencies/node/bin/`;CLI 位于解包后的 `@deepseek-ai/libreoffice-kit/lib/cli.js`。macOS 上的原生辅助程序获得 [LibreOffice UNO 桥](https://github.com/LibreOffice/core/blob/master/sysui/desktop/macosx/hardened_runtime.xcent.in)所需的 JIT entitlement。 -打包应用运行编译后的 JavaScript 和预生成的 Typert 元数据,不编译 TypeScript 插件。源码级调试导航和编辑器声明仍可从开发包中获取。[复制规则测试](tests/runtime-file-policy.spec.ts)覆盖排除项和保留资源;[产物 smoke](tests/fixtures/runtime-payload-smoke.mjs) 在 Host smoke 和最终清单验证之前,使用 Electron RunAsNode 执行。产物 smoke 解析搜索工具使用的 ripgrep 可执行文件,并验证文本搜索和文件枚举。Windows 签名构建在依赖签名后运行这些检查;其他构建在 `prepare:dsh` 中运行。[Host smoke](scripts/smoke-runtime.ts) 使用捆绑的 Python 创建 DOCX、XLSX 和 PPTX 输入,通过真实 Office 提供方逐一转换并检查 PDF 输出。每个组装后的应用(包括目录包和 Windows 未签名构建)都会针对 ASAR 重复产物和 Host 检查。归档完整性检查将归档内完整描述符与准备结果比对,并核对归档和解包目录中的文件内容与清单、归档内文件记录的执行标志,以及解包文件的物理权限。转换失败会在写入发布记录前终止打包;macOS DMG/ZIP 构建在公证前执行这些检查。 +打包应用运行编译后的 JavaScript 和预生成的 Typert 元数据,不编译 TypeScript 插件。源码级调试导航和编辑器声明仍可从开发包中获取。[复制规则测试](tests/runtime-file-policy.spec.ts)覆盖排除项和保留资源;[产物 smoke](tests/fixtures/runtime-payload-smoke.mjs) 在 Host smoke 和最终清单验证之前,使用 Electron RunAsNode 执行。产物 smoke 解析搜索工具使用的 ripgrep 可执行文件,并验证文本搜索和文件枚举。Windows 签名构建在依赖签名后运行这些检查;其他构建在 `prepare:dsh` 中运行。[Host smoke](scripts/smoke-runtime.ts) 使用捆绑的 Python 创建 DOCX、XLSX 和 PPTX 输入,通过真实 Office 提供方逐一转换并检查 PDF 输出。每个组装后的应用(包括目录包和未签名构建)都会针对 ASAR 重复产物和 Host 检查。归档完整性检查将归档内完整描述符与准备结果比对,并核对归档和解包目录中的文件内容与清单、归档内文件记录的执行标志,以及解包文件的物理权限。转换失败会在写入发布记录前终止打包;macOS DMG/ZIP 构建在公证前执行这些检查。 Windows 发布验收还需在 Desktop 构建后手动运行[目录和替换检查](scripts/smoke-windows.ps1)。将 `$Makensis`、`$SevenZip` 和 `$PluginDir` 分别设为锁定版本构建器的 NSIS 编译器、7-Zip 可执行文件和 x86-unicode NSIS 插件目录;通过 `-FrameLibrary` 传入已准备好的 `window-frame.dll`,即可同时覆盖原生解压路径及其失败报告。从仓库根目录运行以下命令。它验证 目录替换与回滚和两种文件占用替换方式;不属于单元测试通道。 @@ -291,7 +324,7 @@ pnpm run upload:mac:arm64 前期内测包使用 `test` 部署。只有正式发布才显式选择 `production`;更换上传凭据不会改变已有安装包的更新目标。打包不需要 COS 凭据,会禁用 electron-builder 发布、移除子进程的 COS 凭据,并且仅在签名与公证成功后记录完成状态。上传在读取凭据前验证该记录、部署、目标、共同版本号、文件名、大小与 SHA-512。安装包和 blockmap 先于 YAML 上传;历史对象继续保留。每个版本发布 `nightly.yml` 或 `nightly-mac.yml`;稳定版本还发布指向相同产物的 `latest.yml` 或 `latest-mac.yml`。发布的 YAML 使用安装包绝对 URL。上传器不设置 Cache-Control,包括 COS SDK 否则会添加的空头部:缓存策略由部署基础设施负责,清单不缓存,安装包缓存单独配置。同一目标应串行发布,并在发布验收前验证公网产物与清单内容。 -macOS 配置使用必填发布环境,不会接受钥匙串中最先发现的证书。空值、格式错误的 Team ID、包含 electron-builder 不支持的 `Developer ID Application:` 前缀的签名身份,以及不完整的公证凭据都会被拒绝。macOS 打包要求已配置的身份及其私钥可用。运行时准备会把该身份、安全时间戳与 hardened runtime 应用到每个内嵌 Mach-O 文件;应用签名完成后,深度严格检查会拒绝其他叶证书 Authority 或 Team ID,验证通过才生成发布产物。macOS 固定目标安装包命令为已签名应用创建独立副本,并发执行两条产物流。一路先公证 App 并钉票,再生成 ZIP 及其更新元数据。另一路把已签名 App 副本封装进签名 DMG,再公证 DMG、钉票并验证;其中的 App 不单独附加票据。只有两路均成功结束,产物才会移入最终目录并写入发布完成记录。仅生成目录的命令同样需要公证凭据,并等待 Apple 公证和 App 钉票完成。[并行公证决策](../../.agents/notes/implemented/process/2026-09-09-parallel-macos-notarization.zh.md)负责副本隔离与容器票据语义。`CSC_LINK` 必须指向包含 Developer ID Application 证书及私钥的本地 p12,不支持 URL 或 Base64 输入。`CSC_KEY_PASSWORD` 是其导出密码,不是 Apple 账号或登录密码;未加密的 p12 可显式填写空值。构建前,打包流程自动创建并解锁私有临时钥匙串、导入 p12、授权签名并签署小型探针。运行时与 App 签名显式使用该钥匙串,无需预先配置或手动解锁登录钥匙串。子进程只接收钥匙串路径,不接收 p12 密码。成功或普通失败后删除临时钥匙串;强制终止后由 CI 清理临时凭据。CI 从密钥存储生成证书文件和 `.env.macos`,限制文件访问权限,并在作业结束后删除二者。环境中的 `CSC_NAME` 与证书发现顺序都不能选择发布所有者。公证凭据也可以使用 electron-builder 支持的完整 Apple ID 或钥匙串 profile 方式。手动执行 `pnpm --dir apps/desktop run verify:mac-signature -- ` 重复应用检查时,也必须提供两个 macOS 身份变量。 +macOS 签名配置使用必填发布环境,不会接受钥匙串中最先发现的证书。空值、格式错误的 Team ID、包含 electron-builder 不支持的 `Developer ID Application:` 前缀的签名身份,以及不完整的公证凭据都会被拒绝。macOS 签名打包要求已配置的身份及其私钥可用。运行时准备会把该身份、安全时间戳与 hardened runtime 应用到每个内嵌 Mach-O 文件;应用签名完成后,深度严格检查会拒绝其他叶证书 Authority 或 Team ID,验证通过才生成发布产物。macOS 签名固定目标安装包命令为已签名应用创建独立副本,并发执行两条产物流。一路先公证 App 并钉票,再生成 ZIP 及其更新元数据。另一路把已签名 App 副本封装进签名 DMG,再公证 DMG、钉票并验证;其中的 App 不单独附加票据。只有两路均成功结束,产物才会移入最终目录并写入发布完成记录。签名的仅目录命令同样需要公证凭据,并等待 Apple 公证和 App 钉票完成。[并行公证决策](../../.agents/notes/implemented/process/2026-09-09-parallel-macos-notarization.zh.md)负责副本隔离与容器票据语义。`CSC_LINK` 必须指向包含 Developer ID Application 证书及私钥的本地 p12,不支持 URL 或 Base64 输入。`CSC_KEY_PASSWORD` 是其导出密码,不是 Apple 账号或登录密码;未加密的 p12 可显式填写空值。构建前,打包流程自动创建并解锁私有临时钥匙串、导入 p12、授权签名并签署小型探针。运行时与 App 签名显式使用该钥匙串,无需预先配置或手动解锁登录钥匙串。子进程只接收钥匙串路径,不接收 p12 密码。成功或普通失败后删除临时钥匙串;强制终止后由 CI 清理临时凭据。CI 从密钥存储生成证书文件和 `.env.macos`,限制文件访问权限,并在作业结束后删除二者。环境中的 `CSC_NAME` 与证书发现顺序都不能选择发布所有者。公证凭据也可以使用 electron-builder 支持的完整 Apple ID 或钥匙串 profile 方式。手动执行 `pnpm --dir apps/desktop run verify:mac-signature -- ` 重复应用检查时,也必须提供两个 macOS 身份变量。 macOS 签名遍历真实文件,不跟随 Framework 的软链接别名。PAK 资源保留全部随附语言,由外层 Framework 或应用签名记录完整性,不逐个签名。[发布策略](../../.agents/notes/implemented/architecture/2026-08-25-electron-desktop-packaging-and-updates.zh.md)负责依赖补丁和验证要求。 @@ -325,11 +358,11 @@ pnpm run package:desktop:win:x64:unsigned ### Windows 安装界面 -Windows 安装程序使用原生 NSIS 页面,提供亮暗配色、系统阴影、可编辑的安装目录,以及默认勾选立即启动的完成页。安装仅面向当前用户。点击安装或按 Enter 均校验当前路径;新安装位置必须为空,非空位置必须是已登记的安装目录。受影响安装路径中的程序运行时显示系统提示,并保持应用运行;其他目录中的同名应用不阻止安装。静默更新最多等待受影响应用退出十秒,若仍在运行则以退出码 2 结束。 +Windows 安装程序使用原生 NSIS 页面,提供亮暗配色、系统阴影、可编辑的安装目录,以及默认勾选立即启动的完成页。安装仅面向当前用户。点击安装或按 Enter 均校验当前路径;路径规范化后仍拒绝磁盘根目录,包括带重复末尾分隔符的写法。新安装位置必须为空,非空位置必须是已登记的安装目录。受影响安装路径中的程序运行时显示系统提示,并保持应用运行;其他目录中的同名应用不阻止安装。静默更新最多等待受影响应用退出十秒,若仍在运行则以退出码 2 结束。 主题在启动时跟随 Windows;可用 `/THEME=light`、`/THEME=dark` 和 `/THEME=auto` 显式选择配色。窗口在品牌控件准备完成后显示。欢迎页首次出现时,安装窗口会一次性移到普通窗口前方;若焦点在其他窗口,任务栏按钮会闪烁提示,但安装窗口不会始终置顶。进度读取锁定版本的 7-Zip 解压器百分比;目录替换、注册和清理仍使用有界估算。加权百分比不代表剩余时间。NSIS 报告成功后,进度条用 600 毫秒补满并短暂显示 100%,再显示完成页;切换目标时长为 750 毫秒。完成页保留窗口位置。点击完成后,安装程序先隐藏窗口,再启动已安装的可执行文件;启动失败会恢复页面以供重试。目录替换和失败恢复遵循上文描述的安装流程。首次启动的配置档案准备仍属于独立的 Desktop 操作。 -Windows 打包使用 Visual C++ Build Tools 和 Windows SDK 编译 x86 Win32/GDI+ 辅助库;签名构建通过已配置的 Windows 签名器对该库签名。准备钩子在所有平台上均由 electron-builder 继续负责收集生产依赖。[安装界面决策](../../.agents/notes/implemented/architecture/2026-09-10-windows-native-installer-pages.zh.md)记录 NSIS 接入方式和发布验证要求。 +Windows 打包使用 Visual C++ Build Tools 和 Windows SDK 编译 x86 Win32/GDI+ 辅助库;签名构建通过已配置的 Windows 签名器对该库签名。准备钩子在所有平台上均由 electron-builder 继续负责收集生产依赖。 在有交互式桌面的 Windows x64 上,从仓库根目录运行 `pnpm --dir apps/desktop run test:installer`,可将小型原生测试载荷接入正式安装配置并执行验证。每次运行使用独立产品身份,依次验证仅英文和仅中文的安装器变体,并根据实际显示的欢迎页按钮选择测试文案。两个变体均安装到私有目录并在测试后卸载;截图和结果保留在 `.desktop-build/installer-tests/` 下。检查包含末尾带分隔符的已登记路径升级,以及磁盘根目录拒绝。可选的 `--signed` 标志使用下文的 Windows EV 配置,在嵌入前对测试程序和辅助库签名;它不会启用更新源。 @@ -392,9 +425,9 @@ pnpm run prepare:desktop 这条诊断命令是另一种停止位置,并非两条命令构建流程的前半段。之后执行 `package:desktop*` 时仍会重新完成正式构建与准备,避免使用陈旧的 dsh 包、运行时文件或 dsh 内容。 -每条打包命令都会构建仓库,打包以 dsh 和私有 Desktop Host 为根的第一方生产依赖闭包,并准备目标专用的 Electron 分发包与 pnpm CLI。`prepare:dsh` 在构建时安装一次生产依赖图,准备物化包供 electron-builder 归档到 `app.asar/dsh`,移除包管理器元数据,并生成包含共享包版本和最终文件哈希的 `desktop-runtime.json`。在 macOS 上,它先签名并验证原生文件,再生成清单;electron-builder 不对已签名的此目录重复进行嵌套签名。资源映射明确包含默认根目录过滤器会忽略的 `dsh/node_modules`;准备完成的运行时清单在原生签名后检查。原生可执行文件及库解包到 ASAR 旁;Python、独立 Node 和 pnpm 保留在外部 runtime 资源中。Windows 打包逐项检查准备好的 PE,确认其 ASAR 条目已标记为解包,且磁盘副本字节一致;未签名构建也执行此检查。Builder glob 规则用单字符通配符匹配 PE 文件名中的花括号,因此同目录中名称匹配的文件也可能被解包。准备好的运行时 smoke 沿用已验证的目标描述符,不使用构建宿主的架构。签名安装包、公证、已安装应用升级和各目标原生模块的验收需要发布环境。 +每条打包命令都会构建仓库,打包以 dsh 和私有 Desktop Host 为根的第一方生产依赖闭包,并准备目标专用的 Electron 分发包与 pnpm CLI。`prepare:dsh` 在构建时安装一次生产依赖图,准备物化包供 electron-builder 归档到 `app.asar/dsh`,移除包管理器元数据,并生成包含共享包版本和最终文件哈希的 `desktop-runtime.json`。macOS 签名构建先签名并验证原生文件,再生成清单;electron-builder 不对已签名的此目录重复进行嵌套签名。资源映射明确包含默认根目录过滤器会忽略的 `dsh/node_modules`;准备完成的运行时清单在原生签名后检查。原生可执行文件及库解包到 ASAR 旁;Python、独立 Node 和 pnpm 保留在外部 runtime 资源中。Windows 打包逐项检查准备好的 PE,确认其 ASAR 条目已标记为解包,且磁盘副本字节一致;未签名构建也执行此检查。Builder glob 规则用单字符通配符匹配 PE 文件名中的花括号,因此同目录中名称匹配的文件也可能被解包。准备好的运行时 smoke 沿用已验证的目标描述符,不使用构建宿主的架构。签名安装包、公证、已安装应用升级和各目标原生模块的验收需要发布环境。 -macOS 打包在组装 App 时、代码签名前写入 `Contents/Resources/app-update.yml`,供并行 ZIP 与 DMG 路线使用的目录构建也执行此操作。签名钩子验证准确的更新源和 updater 缓存目录。写入发布完成记录前,流程会再次检查两条路线的副本和最终移入的 App;配置缺失或不匹配会阻止移入产物,因而也会阻止上传。 +macOS 签名打包在组装 App 时、代码签名前写入 `Contents/Resources/app-update.yml`,供并行 ZIP 与 DMG 路线使用的目录构建也执行此操作。签名钩子验证准确的更新源和 updater 缓存目录。写入发布完成记录前,流程会再次检查两条路线的副本和最终移入的 App;配置缺失或不匹配会阻止移入产物,因而也会阻止上传。 未压缩产物包含 Electron、物化后的 dsh 生产依赖树、pnpm,以及壳应用。安装包大小与文件系统占用不同;发布验收需要测量两者,以及 profile 插件存储和首次启动耗时。此布局用更多应用内文件换取消除用户机器上的核心包安装过程。 @@ -414,9 +447,11 @@ Windows 下载完成后的更新确认说明应用会在安装期间关闭、完 已确认 Host 退出但任务未成功收尾时,常规与强更弹窗均展示本地化恢复提示。两种语言都根据类型化的准备失败原因选择提示,翻译文案变化不会改变失败分类。“查看技术详情”默认折叠,仅展示退出状态、信号、关闭确认和截止时间事实,不展示插件 stderr。展开详情既不重试,也不授权安装。 + + ### 强制更新策略 -[强更客户端决策](../../.agents/notes/implemented/feature/2026-09-11-desktop-mandatory-update-client.zh.md)负责策略查询和阻塞窗口。打包读取 `.env.windows` 或 `.env.macos`:`DSH_DESKTOP_AUTO_UPDATE_ENV=test`(默认值)选择 `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`;`production` 选择 `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`。模板将两个源站留空;在 Git 忽略的目标 dotenv 文件中填写所选部署的源站。在准备产物或签名前,所选源站必须配置,包括未签名和仅准备构建;未选环境的源站可不填。这些配置不会回退到父进程环境或另一部署环境。打包将选定策略与应用 ID 写入元数据;打包应用忽略运行时覆盖。 +打包读取 `.env.windows` 或 `.env.macos`:`DSH_DESKTOP_AUTO_UPDATE_ENV=test`(默认值)选择 `DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN`;`production` 选择 `DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN`。模板将两个源站留空;在 Git 忽略的目标 dotenv 文件中填写所选部署的源站。本地 macOS 未签名构建省略此策略,不读取这两个文件。其他模式在准备产物或签名前必须配置所选源站,包括 Windows 未签名和仅准备构建;未选环境的源站可不填。这些配置不会回退到父进程环境或另一部署环境。打包将选定策略与应用 ID 写入元数据;打包应用忽略运行时覆盖。 `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` JSON 提供测试登录源站,以及可选的轮询和下载页面选项;打包拒绝其中的 `origin` 和 `authentication`。页面白名单默认只包含所选服务源站;需要其他已批准下载页面源站时应显式配置。测试包选择 `feishu-test`,且必须在 `DSH_DESKTOP_MANDATORY_UPDATE_CONFIG` 中配置 `allowedAuthOrigins`;正式包选择 `anonymous`,并拒绝该字段。每个登录源站必须是没有凭据、路径、查询或片段的 HTTPS origin。登录窗口仅允许文档导航到所选策略源站和这些已配置源站。策略请求拒绝重定向;仅测试鉴权携带网关 Cookie。未打包开发模式则从此变量读取完整策略 JSON,并要求 `DSH_DESKTOP_APP_ID`;缺少 JSON 会禁用开发模式策略查询,仅匿名开发允许 HTTP `127.0.0.1`。用户发起常规检查时会并发触发策略检查,但不会等待或展示策略失败。只有已确认的强更决定可以关闭常规弹窗。测试环境鉴权会等待当前常规弹窗结束,取消或失败不会丢弃 updater 结果。 diff --git a/apps/desktop/installer/path.nsh b/apps/desktop/installer/path.nsh index 6d5723c196..01aafe135f 100644 --- a/apps/desktop/installer/path.nsh +++ b/apps/desktop/installer/path.nsh @@ -106,7 +106,8 @@ Function ${PREFIX}InstallerValidatePath ${GetParent} $2 $2 ${Loop} System::Call 'kernel32::GetFullPathNameW(w "$InstallerPath", i ${NSIS_MAX_STRLEN}, w .r4, p 0) i.r0' - ${If} $0 == 0 + ; Repeated trailing separators can normalize to a drive root. + ${If} $0 < 4 ${OrIf} $0 >= ${NSIS_MAX_STRLEN} Return ${EndIf} diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3eaf57f56a..0a84d09e6f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-desktop", "description": "Electron desktop shell for a bundled dsh runtime and external plugins", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "private": true, "license": "MIT", "type": "module", @@ -9,7 +9,7 @@ "scripts": { "test:installer": "node scripts/test-windows-installer.mjs", "build": "tsc -b && pnpm run bundle", - "bundle": "tsdown", + "bundle": "tsdown --config-loader native", "dev": "tsx scripts/dev.ts", "start": "tsx scripts/dev.ts --skip-build", "test:updates:local": "pnpm run build && node scripts/test-local-updater.mjs", @@ -27,8 +27,10 @@ "restore:mac-proxy": "tsx scripts/macos-notarization-proxy.ts", "package:dir": "tsx scripts/package-target.ts --dir", "package:mac:arm64": "tsx scripts/package-target.ts mac-arm64", + "package:mac:arm64:unsigned": "tsx scripts/package-target.ts mac-arm64 --unsigned", "package:mac:arm64:dir": "tsx scripts/package-target.ts mac-arm64 --dir", "package:mac:x64": "tsx scripts/package-target.ts mac-x64", + "package:mac:x64:unsigned": "tsx scripts/package-target.ts mac-x64 --unsigned", "package:mac:x64:dir": "tsx scripts/package-target.ts mac-x64 --dir", "package:win:x64": "tsx scripts/package-target.ts win-x64", "package:win:x64:unsigned": "tsx scripts/package-target.ts win-x64 --unsigned", @@ -43,7 +45,7 @@ "dependencies": { "electron-updater": "^6.8.9", "semver": "^7.8.5", - "ws": "^8.21.0", + "ws": "^8.21.3", "@deepseek-ai/dsh-api-gateway": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, @@ -56,17 +58,17 @@ "@electron/get": "^5.1.0", "@electron/notarize": "2.5.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.20.0", + "@types/node": "^22.20.1", "@types/semver": "^7.8.0", "app-builder-lib": "26.15.3", "cos-nodejs-sdk-v5": "3.0.0", "electron": "^44.0.0", "electron-builder": "^26.15.3", "extract-zip": "^2.0.1", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.2", "koffi": "3.1.1", - "pnpm": "11.7.0", - "sharp": "^0.35.3", + "pnpm": "11.28.5", + "sharp": "^0.35.5", "typescript": "^6.0.3", "@deepseek-ai/dsh-deepseek-account": "workspace:*", "@types/ws": "^8.18.1", @@ -75,7 +77,7 @@ "react-dom": "^18.2.0", "@types/react": "~18.3.1", "@types/react-dom": "~18.3.0", - "vite": "^6.0.0", + "vite": "^6.4.3", "@deepseek-ai/dsh-client-ui-theme": "workspace:*", "@deepseek-ai/dsh-client-shortcuts": "workspace:*", "@deepseek-ai/dsh-atomic-write": "workspace:*", diff --git a/apps/desktop/scripts/desktop-build-version-discovery.ts b/apps/desktop/scripts/desktop-build-version-discovery.ts index 6bb7a1b045..fd5a2c9e10 100644 --- a/apps/desktop/scripts/desktop-build-version-discovery.ts +++ b/apps/desktop/scripts/desktop-build-version-discovery.ts @@ -30,7 +30,7 @@ const LISTING_DEADLINE_MS = 8_000 /** Objects one listing page may return. */ const LISTING_PAGE_SIZE = 1000 -/** Artifact name electron-builder writes for one build, on either platform; unsigned Windows builds add a suffix. */ +/** Artifact name electron-builder writes for one build, on either platform; unsigned builds add a suffix. */ const ARTIFACT = /(?:^|\/)deepseek-harness-(?.+)-(?:mac|win)-(?:arm64|x64)(?:-unsigned)?\.(?:exe|dmg|zip)$/u /** Inputs that decide which versions are already taken. */ diff --git a/apps/desktop/scripts/desktop-package-environment.d.mts b/apps/desktop/scripts/desktop-package-environment.d.mts index b0d7bc2dbe..a49ca46eb9 100644 --- a/apps/desktop/scripts/desktop-package-environment.d.mts +++ b/apps/desktop/scripts/desktop-package-environment.d.mts @@ -1,4 +1,11 @@ -/** Load platform-local release settings without changing the caller's process environment. */ +/** Select local test defaults or platform release settings without changing the caller's environment. */ + +/** + * Select local macOS test defaults without reading dotenv files or inheriting release settings. + * @param environment Parent environment, retained for build tools and networking. + * @returns Unsigned build environment with the standard application identifier and no update policy. + */ +export function localMacOSPackageEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv /** * Read the target's required UTF-8 dotenv file; release settings never fall back to ambient values. @@ -14,8 +21,8 @@ export function loadDesktopPackageEnvironment( ): NodeJS.ProcessEnv /** - * Validate release configuration before preparation without invoking a token or Apple's services. - * @param environment File-owned release settings. + * Validate the packaging mode before preparation without invoking a token or Apple's services. + * @param environment Selected local defaults or file-owned release settings. * @param target Selected release target. * @param options Explicit packaging mode. * @returns Nothing. diff --git a/apps/desktop/scripts/desktop-package-environment.mjs b/apps/desktop/scripts/desktop-package-environment.mjs index 3addf3ff65..51b878100a 100644 --- a/apps/desktop/scripts/desktop-package-environment.mjs +++ b/apps/desktop/scripts/desktop-package-environment.mjs @@ -1,4 +1,4 @@ -/** Load platform-local release settings without changing the caller's process environment. */ +/** Select local test defaults or platform release settings without changing the caller's environment. */ import { accessSync, constants, readFileSync, statSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' @@ -19,6 +19,19 @@ const MACOS_SETTING = /^(?:DSH_DESKTOP_MACOS_(?:SIGNING_IDENTITY|TEAM_ID|PACK_CO const AMBIENT_RELEASE_SETTING = /^(?:DSH_DESKTOP_(?:APP_ID|AUTO_UPDATE_ENV|MANDATORY_UPDATE_.*|WINDOWS_.*|MACOS_.*)|APPLE_.*|(?:WIN_)?CSC_.*|DOWNLOAD_(?:TEST|PROD)_.*)$/iu const FILE_SETTINGS = ['DSH_DESKTOP_WINDOWS_CER_FILE', 'DSH_DESKTOP_WINDOWS_SIGNTOOL', 'APPLE_API_KEY', 'APPLE_KEYCHAIN', 'CSC_LINK'] +/** + * Select local macOS test defaults without reading dotenv files or inheriting release settings. + * @param {NodeJS.ProcessEnv} environment Parent environment, retained for build tools and networking. + * @returns {NodeJS.ProcessEnv} Unsigned build environment with the standard application identifier and no update policy. + */ +export function localMacOSPackageEnvironment(environment = process.env) { + return { + ...Object.fromEntries(Object.entries(environment).filter(([name]) => !AMBIENT_RELEASE_SETTING.test(name))), + DSH_DESKTOP_APP_ID: 'com.deepseek.harness', + DSH_DESKTOP_UNSIGNED: '1', + } +} + /** * Read the target's required UTF-8 dotenv file; release settings never fall back to ambient values. * @param {'win32' | 'darwin'} platform Target platform. @@ -70,8 +83,8 @@ function requireReadableFile(environment, name) { } /** - * Validate release configuration before preparation without invoking a token or Apple's services. - * @param {NodeJS.ProcessEnv} environment File-owned release settings. + * Validate the packaging mode before preparation without invoking a token or Apple's services. + * @param {NodeJS.ProcessEnv} environment Selected local defaults or file-owned release settings. * @param {{ platform: 'win32' | 'darwin', arch: string }} target Selected release target. * @param {{ unsigned?: boolean, prepareOnly?: boolean }} options Explicit packaging mode. * @returns {void} @@ -79,7 +92,7 @@ function requireReadableFile(environment, name) { export function validateDesktopPackageEnvironment(environment, target, options = {}) { resolveDesktopAppId(environment) resolveNpmRegistry(environment) - resolveDesktopPolicyEnvironment(environment) + if (target.platform !== 'darwin' || !options.unsigned) resolveDesktopPolicyEnvironment(environment) if (target.platform === 'darwin') resolveMacOSPackageSettings(environment) else resolveWindowsPackageSettings(environment) if (options.unsigned) return diff --git a/apps/desktop/scripts/electron-builder-config.mjs b/apps/desktop/scripts/electron-builder-config.mjs index 1fb787803c..f08b783865 100644 --- a/apps/desktop/scripts/electron-builder-config.mjs +++ b/apps/desktop/scripts/electron-builder-config.mjs @@ -51,7 +51,6 @@ export function createElectronBuilderConfig( preparedRuntimeVersion = undefined, ) { const appId = resolveDesktopAppId(env) - const policy = resolveDesktopPolicyEnvironment(env) const targetPlatform = env.DSH_DESKTOP_TARGET_PLATFORM const resolvedPlatform = targetPlatform ?? hostPlatform const resolvedArch = env.DSH_DESKTOP_TARGET_ARCH ?? hostArch @@ -59,12 +58,12 @@ export function createElectronBuilderConfig( throw new Error('desktop package: DSH_DESKTOP_UNSIGNED must be 0 or 1') } const unsigned = env.DSH_DESKTOP_UNSIGNED === '1' - if (unsigned && resolvedPlatform !== 'win32') throw new Error('desktop package: unsigned builds require Windows') const packagesMacOS = targetPlatform === 'darwin' || (targetPlatform === undefined && hostPlatform === 'darwin') const packagesWindows = resolvedPlatform === 'win32' + const policy = packagesMacOS && unsigned ? undefined : resolveDesktopPolicyEnvironment(env) if (resolvedPlatform === 'win32') installWindowsDirectoryInstaller() - const macOSSigning = packagesMacOS ? resolveMacOSSigningEnvironment(env) : undefined - if (packagesMacOS) resolveMacOSNotarizationEnvironment(env) + const macOSSigning = packagesMacOS && !unsigned ? resolveMacOSSigningEnvironment(env) : undefined + if (packagesMacOS && !unsigned) resolveMacOSNotarizationEnvironment(env) const buildPaths = desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch)) let primaryRuntimeDestination let dshDestination @@ -102,7 +101,7 @@ export function createElectronBuilderConfig( protocols: [{ name: 'DeepSeek Harness', schemes: ['dsh'] }], extraMetadata: { dshDesktopAppId: appId, - dshMandatoryUpdatePolicy: policy, + ...(policy === undefined ? {} : { dshMandatoryUpdatePolicy: policy }), ...buildVersion === productVersion ? {} : { version: buildVersion }, ...packaged === undefined ? {} : { dshBuildCommit: packaged.commit, dshBuildDirty: packaged.dirty }, }, @@ -152,19 +151,20 @@ export function createElectronBuilderConfig( category: 'public.app-category.developer-tools', // macOS matches the application locale against this bundle, not Electron Framework resources. extendInfo: { CFBundleLocalizations: ['en', 'zh_CN'] }, - identity: macOSSigning?.signingIdentity, - forceCodeSigning: true, + // Ad-hoc signing keeps the modified Electron executable runnable without a Developer ID. + identity: unsigned ? '-' : macOSSigning?.signingIdentity, + forceCodeSigning: !unsigned, hardenedRuntime: true, extendInfo: { NSMicrophoneUsageDescription: 'DeepSeek Harness uses your microphone to transcribe speech into message drafts.' }, entitlements: fileURLToPath(new URL('./macos-entitlements.plist', import.meta.url)), entitlementsInherit: fileURLToPath(new URL('./macos-entitlements.plist', import.meta.url)), - // ASAR-unpacked native runtime files are pre-signed; PAK resources are sealed by their enclosing bundle. + // Prepared runtime files retain their signatures; PAK resources are sealed by their enclosing bundle. signIgnore: ['/Contents/Resources/app\\.asar\\.unpacked/dsh(?:/|$)', '/Contents/Resources/runtime/primary-runtime(?:/|$)', '\\.pak$'], - notarize: true, - target: ['dmg', 'zip'], + notarize: !unsigned, + target: unsigned ? ['dmg'] : ['dmg', 'zip'], }, dmg: { - sign: true, + sign: !unsigned, writeUpdateInfo: false, }, beforePack: async context => { @@ -204,7 +204,7 @@ export function createElectronBuilderConfig( }) await verifyWindowsAsarUnpack(buildPaths.dsh, context.packager.getResourcesDir(context.appOutDir), windowsCode) } - if (context.electronPlatformName !== 'darwin') return + if (context.electronPlatformName !== 'darwin' || unsigned) return const appPath = join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`) if (update !== undefined) { await verifyMacOSAppUpdateConfig(appPath, resolveMacOSAppUpdateFeed(context.packager.config.publish), @@ -213,7 +213,7 @@ export function createElectronBuilderConfig( verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env)) }, artifactBuildCompleted: artifact => { - if (!artifact.file.endsWith('.dmg')) return + if (unsigned || !artifact.file.endsWith('.dmg')) return return notarizeMacOSDiskImageArtifact( artifact, env, diff --git a/apps/desktop/scripts/package-target.ts b/apps/desktop/scripts/package-target.ts index 62f4fcbbb2..a133ccdcf8 100644 --- a/apps/desktop/scripts/package-target.ts +++ b/apps/desktop/scripts/package-target.ts @@ -10,7 +10,7 @@ import { } from './desktop-auto-update-environment.mjs' import { desktopTargetBuildPaths } from './desktop-build-paths.mjs' import { packageMacOSArtifacts, type DesktopPrepackagedArtifact } from './package-macos.ts' -import { loadDesktopPackageEnvironment, validateDesktopPackageEnvironment } from './desktop-package-environment.mjs' +import { loadDesktopPackageEnvironment, localMacOSPackageEnvironment, validateDesktopPackageEnvironment } from './desktop-package-environment.mjs' import { createPackagingRun, recordPackagingEvent } from './packaging-run.mjs' import { withWindowsSigningStage } from './windows-signing-stage.mjs' import { prepareWindowsSignatureCacheDirectory, resolveWindowsSignatureCacheDirectory } from './windows-signature-cache-directory.mjs' @@ -93,9 +93,9 @@ export function withoutWindowsSigningEnvironment(environment: NodeJS.ProcessEnv) } /** - * Select signing and NSIS-compatible archive filters for electron-builder. + * Select signing mode and strip unsigned-build credentials for packaging subprocesses. * @param environment - Target packaging environment. - * @param unsigned - Whether to create a local unsigned Windows artifact. + * @param unsigned - Whether to create a local artifact without release signing or notarization. * @returns Packaging environment without certificate inputs for unsigned builds. */ export function desktopElectronBuilderEnvironment(environment: NodeJS.ProcessEnv, unsigned: boolean): NodeJS.ProcessEnv { @@ -105,7 +105,7 @@ export function desktopElectronBuilderEnvironment(environment: NodeJS.ProcessEnv if (!unsigned) return selected return { ...Object.fromEntries(Object.entries(withoutWindowsSigningEnvironment(selected)) - .filter(([name]) => !/^(?:WIN_)?CSC_/iu.test(name))), + .filter(([name]) => !/^(?:(?:WIN_)?CSC_|APPLE_|DSH_DESKTOP_MACOS_(?:SIGNING_|TEAM_ID$))/iu.test(name))), CSC_IDENTITY_AUTO_DISCOVERY: 'false', DSH_DESKTOP_UNSIGNED: '1', } @@ -234,7 +234,6 @@ export function parseDesktopPackageInvocation( }) if (positionals.length > 1) throw new Error('desktop package: expected at most one target') const name = positionals[0] ?? hostTargetName(hostPlatform, hostArch) - if (values.unsigned && name !== 'win-x64') throw new Error('desktop package: --unsigned requires win-x64') if (values.unsigned && values['prepare-only']) throw new Error('desktop package: --unsigned cannot use --prepare-only') const requestedBuildVersion = values['build-version']?.trim() if (values['build-version'] !== undefined && (requestedBuildVersion === undefined || requestedBuildVersion === '')) { @@ -332,7 +331,9 @@ async function resolveRequestedBuildVersion( async function main(): Promise { const invocation = parseDesktopPackageInvocation(process.argv.slice(2)) const { target } = invocation - const environment = loadDesktopPackageEnvironment(target.platform) + const environment = target.platform === 'darwin' && invocation.unsigned + ? localMacOSPackageEnvironment() + : loadDesktopPackageEnvironment(target.platform) const productVersion = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package') // Release settings come from the target dotenv file alone, so the version this run publishes is an // argument; the environment variable below only carries it to the child processes that build. @@ -366,8 +367,9 @@ async function main(): Promise { recordPackagingEvent(run.directory, { type: 'macos-settings', packConcurrency: settings.packConcurrency, downloadProxyConfigured: settings.downloadProxy !== undefined, notarizationProxyConfigured: settings.notarizationProxy !== undefined }) - await packagingStep(run.directory, 'macos-package', () => withMacOSSigningKeychain(environment, - signingEnvironment => packageTarget(invocation, signingEnvironment, run)), secrets) + await packagingStep(run.directory, 'macos-package', () => invocation.unsigned + ? packageTarget(invocation, environment, run) + : withMacOSSigningKeychain(environment, signingEnvironment => packageTarget(invocation, signingEnvironment, run)), secrets) } else { await packagingStep(run.directory, 'windows-package', () => packageTarget(invocation, environment, run), secrets) } @@ -381,6 +383,11 @@ async function main(): Promise { else process.env.DSH_DESKTOP_PACKAGING_RUN_DIR = previousDirectory run.finish(success) } + if (success && target.platform === 'darwin' && !invocation.directory && !invocation.prepareOnly) { + const paths = desktopTargetBuildPaths(target.name) + const filename = `deepseek-harness-${buildVersion}-mac-${target.arch}${invocation.unsigned ? '-unsigned' : ''}.dmg` + console.log(`DMG: ${join(invocation.unsigned ? paths.unsignedArtifacts : paths.artifacts, filename)}`) + } } /** @@ -407,7 +414,8 @@ export async function packageTarget( rmSync(releaseRecordPath, { force: true }) rmSync(`${releaseRecordPath}.tmp`, { force: true }) } - const buildEnv = withoutWindowsSigningEnvironment(withoutDesktopUploadCredentials(environment)) + const buildEnv = withoutWindowsSigningEnvironment(withoutDesktopUploadCredentials( + desktopElectronBuilderEnvironment(environment, invocation.unsigned))) const targetEnv: NodeJS.ProcessEnv = { ...buildEnv, DSH_DESKTOP_TARGET_PLATFORM: target.platform, @@ -473,7 +481,7 @@ export async function packageTarget( await execute(['run', 'prepare:dsh', ...(signPrimaryRuntime ? ['--defer-runtime-smoke'] : [])], downloadEnv) if (signPrimaryRuntime) await execute(['run', 'sign:primary-runtime', '--dsh'], electronBuilderEnv) if (invocation.prepareOnly) return - if (target.platform === 'darwin' && !invocation.directory) { + if (target.platform === 'darwin' && !invocation.unsigned && !invocation.directory) { await execute([ ...desktopElectronBuilderArguments(target, true), '--config.mac.notarize=false', @@ -486,7 +494,7 @@ export async function packageTarget( artifactsRoot: buildPaths.artifacts, environment: electronBuilderEnv, }, artifact => execute(desktopElectronBuilderArguments(target, false, artifact), electronBuilderEnv)), undefined, undefined, proxyEvent) - } else if (target.platform === 'darwin') { + } else if (target.platform === 'darwin' && !invocation.unsigned) { await execute([...desktopElectronBuilderArguments(target, true), '--config.mac.notarize=false'], electronBuilderEnv) await execute(['exec', 'tsx', 'scripts/smoke-packaged-runtime.ts'], targetEnv) const appPath = join(buildPaths.artifacts, target.arch === 'arm64' ? 'mac-arm64' : 'mac', 'DeepSeek Harness.app') @@ -497,7 +505,7 @@ export async function packageTarget( await execute(['exec', 'tsx', 'scripts/smoke-packaged-runtime.ts', ...(invocation.unsigned ? ['--unsigned'] : [])], targetEnv) } if (!invocation.directory && !invocation.unsigned) writeReleaseRecord(target, electronBuilderEnv, buildPaths.artifacts) - if (journal) recordPackagingEvent(journal, { type: 'artifacts', directory: buildPaths.artifacts }) + if (journal) recordPackagingEvent(journal, { type: 'artifacts', directory: invocation.unsigned ? buildPaths.unsignedArtifacts : buildPaths.artifacts }) } if (process.argv[1] !== undefined && import.meta.filename === resolve(process.argv[1])) await main() diff --git a/apps/desktop/scripts/prepare-dsh.ts b/apps/desktop/scripts/prepare-dsh.ts index 442e01757e..4e03f763cb 100644 --- a/apps/desktop/scripts/prepare-dsh.ts +++ b/apps/desktop/scripts/prepare-dsh.ts @@ -153,7 +153,7 @@ async function main(): Promise { if (!existsSync(join(DSH_OUTPUT_ROOT, 'node_modules', '@deepseek-ai', `libreoffice-kit-${officeEngine}`, 'prebuilds.json'))) { throw new Error(`desktop runtime: missing required LibreOffice engine ${officeEngine}`) } - if (process.platform === 'darwin') { + if (process.platform === 'darwin' && process.env.DSH_DESKTOP_UNSIGNED !== '1') { await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'sign:dsh-native', () => signMacOSRuntime(DSH_OUTPUT_ROOT, resolveDesktopAppId(process.env), resolveMacOSSigningEnvironment(process.env), target.arch, join(BUILD_PATHS.root, 'signature-cache'))) await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'sign:primary-native', () => signMacOSRuntime(join(RUNTIME_ROOT, 'primary-runtime'), resolveDesktopAppId(process.env), resolveMacOSSigningEnvironment(process.env), target.arch, join(BUILD_PATHS.root, 'signature-cache'))) } diff --git a/apps/desktop/scripts/smoke-packaged-runtime.ts b/apps/desktop/scripts/smoke-packaged-runtime.ts index 106830b5ad..b91030008b 100644 --- a/apps/desktop/scripts/smoke-packaged-runtime.ts +++ b/apps/desktop/scripts/smoke-packaged-runtime.ts @@ -11,7 +11,6 @@ const paths = resolveDesktopTargetBuildPaths() const { values } = parseArgs({ options: { unsigned: { type: 'boolean', default: false } }, allowPositionals: false }) const target = resolveDesktopBuildTarget() const windows = target === 'win-x64' -if (values.unsigned && !windows) throw new Error('desktop smoke: unsigned artifacts require Windows') const artifacts = values.unsigned ? paths.unsignedArtifacts : paths.artifacts const application = windows ? join(artifacts, 'win-unpacked') : join(artifacts, target === 'mac-arm64' ? 'mac-arm64' : 'mac', 'DeepSeek Harness.app', 'Contents') diff --git a/apps/desktop/src/account-backend.ts b/apps/desktop/src/account-backend.ts index 770d3e4e9d..862b8f81ed 100644 --- a/apps/desktop/src/account-backend.ts +++ b/apps/desktop/src/account-backend.ts @@ -25,7 +25,7 @@ export function accountView(value: unknown): AccountView { || !('phase' in attempt) || !['initializing', 'waiting-browser', 'exchanging', 'committing', 'succeeded', 'cancelled', 'expired', 'failed'].includes(String(attempt.phase)) || ('authorizeUrl' in attempt && typeof attempt.authorizeUrl !== 'string') || ('expiresAt' in attempt && (typeof attempt.expiresAt !== 'number' || !Number.isFinite(attempt.expiresAt))) - || ('errorCode' in attempt && !['network', 'protocol', 'expired', 'storage'].includes(String(attempt.errorCode))))) { + || ('errorCode' in attempt && !['no-response', 'network', 'protocol', 'expired', 'storage'].includes(String(attempt.errorCode))))) { throw new Error('desktop account: invalid attempt') } if (attempt !== null && 'authorizeUrl' in attempt) { diff --git a/apps/desktop/tests/account-backend.spec.ts b/apps/desktop/tests/account-backend.spec.ts index b9cee7b61a..c239336b22 100644 --- a/apps/desktop/tests/account-backend.spec.ts +++ b/apps/desktop/tests/account-backend.spec.ts @@ -17,6 +17,12 @@ it('projects only safe account fields and refuses non-browser authorization sche expect(() => accountView({ ...state, attempt: { ...state.attempt, errorCode: 'raw-server-message' } })).toThrow() }) +it('accepts a no-response login failure without discarding the account projection', () => { + const state = { status: 'signed-out', attempt: { id: 'test', phase: 'failed', errorCode: 'no-response' }, + links: { usageUrl: 'https://platform.deepseek.com/usage', topUpUrl: 'https://platform.deepseek.com/top_up' } } + expect(accountView(state)).toEqual(state) +}) + it('uses account Remote commands without returning additional wire fields', async () => { const requests: unknown[] = [] const backend = desktopAccountBackend('http://127.0.0.1:1234', (request) => { diff --git a/apps/desktop/tests/desktop-package-environment.spec.ts b/apps/desktop/tests/desktop-package-environment.spec.ts index 76129b56f8..a892a8548a 100644 --- a/apps/desktop/tests/desktop-package-environment.spec.ts +++ b/apps/desktop/tests/desktop-package-environment.spec.ts @@ -2,7 +2,7 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { describe, expect, it } from 'vitest' -import { loadDesktopPackageEnvironment, validateDesktopPackageEnvironment } from '../scripts/desktop-package-environment.mjs' +import { loadDesktopPackageEnvironment, localMacOSPackageEnvironment, validateDesktopPackageEnvironment } from '../scripts/desktop-package-environment.mjs' import { resolveWindowsPackageSettings } from '../scripts/windows-package-settings.mjs' const WINDOWS = { platform: 'win32', arch: 'x64' } as const @@ -23,6 +23,25 @@ async function withDirectory(action: (directory: string) => Promise): Prom } describe('Desktop local packaging configuration', () => { + it('selects macOS local defaults while excluding inherited release settings and retaining build networking', () => { + const parent = { + PATH: 'build-tools', HTTPS_PROXY: 'http://proxy.example:8080', DSH_DESKTOP_NPM_REGISTRY: 'https://registry.npmmirror.com', + DSH_DESKTOP_APP_ID: 'com.example.release', DSH_DESKTOP_UNSIGNED: '0', + DSH_DESKTOP_AUTO_UPDATE_ENV: 'production', DSH_DESKTOP_MANDATORY_UPDATE_CONFIG: 'invalid-json', + DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN: 'https://policy.example.com', + DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Release identity', CSC_LINK: 'missing.p12', + APPLE_API_KEY: 'missing.p8', DOWNLOAD_PROD_COS_SECRET_KEY: 'unused-secret', + } + const local = localMacOSPackageEnvironment(parent) + expect(local).toEqual({ + PATH: parent.PATH, HTTPS_PROXY: parent.HTTPS_PROXY, DSH_DESKTOP_NPM_REGISTRY: parent.DSH_DESKTOP_NPM_REGISTRY, + DSH_DESKTOP_APP_ID: 'com.deepseek.harness', DSH_DESKTOP_UNSIGNED: '1', + }) + expect(() => { validateDesktopPackageEnvironment(local, MACOS, { unsigned: true }) }).not.toThrow() + expect(parent.DSH_DESKTOP_UNSIGNED).toBe('0') + expect(parent.DSH_DESKTOP_APP_ID).toBe('com.example.release') + }) + it('takes cache concurrency from the Windows file and defaults to four without ambient overrides', async () => { await withDirectory(async (directory) => { const parent = { DSH_DESKTOP_WINDOWS_SIGNATURE_CACHE_CONCURRENCY: '8' } @@ -139,6 +158,14 @@ describe('Desktop local packaging configuration', () => { }).not.toThrow() }) + it('accepts unsigned macOS configuration without Apple or update policy settings while keeping release validation', () => { + const local = localMacOSPackageEnvironment({}) + expect(() => { validateDesktopPackageEnvironment(local, MACOS, { unsigned: true }) }).not.toThrow() + expect(() => { validateDesktopPackageEnvironment(local, MACOS) }) + .toThrow('DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN') + expect(() => { validateDesktopPackageEnvironment(RELEASE, MACOS) }).toThrow('DSH_DESKTOP_MACOS_SIGNING_IDENTITY') + }) + it('accepts one local npm registry mirror and rejects other registry forms', () => { const release = { ...POLICY, DSH_DESKTOP_APP_ID: RELEASE.DSH_DESKTOP_APP_ID } expect(() => { diff --git a/apps/desktop/tests/desktop-policy-environment.spec.ts b/apps/desktop/tests/desktop-policy-environment.spec.ts index e4d893eca0..be5b4ede25 100644 --- a/apps/desktop/tests/desktop-policy-environment.spec.ts +++ b/apps/desktop/tests/desktop-policy-environment.spec.ts @@ -47,8 +47,13 @@ it('rejects login origins in production', () => { .toThrow('must not configure allowedAuthOrigins') }) -it.each([{ unsigned: true }, { prepareOnly: true }, {}])('fails before signing/preparation when policy is absent in %j', (options) => { +it.each([{ unsigned: true }, { prepareOnly: true }, {}])('requires policy outside local macOS builds in %j', (options) => { for (const platform of ['win32', 'darwin'] as const) { + if (platform === 'darwin' && options.unsigned) { + expect(() => { validateDesktopPackageEnvironment({ DSH_DESKTOP_APP_ID: 'com.example.test' }, { platform, arch: 'x64' }, options) }) + .not.toThrow() + continue + } expect(() => { validateDesktopPackageEnvironment({ DSH_DESKTOP_APP_ID: 'com.example.test' }, { platform, arch: 'x64' }, options) }) .toThrow('DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN') } diff --git a/apps/desktop/tests/expected/windows-installer.json b/apps/desktop/tests/expected/windows-installer.json index 031352a046..a9d08b5a3c 100644 --- a/apps/desktop/tests/expected/windows-installer.json +++ b/apps/desktop/tests/expected/windows-installer.json @@ -2,6 +2,7 @@ "launchCheckboxState": 1, "nativeProgressVisible": false, "cases": [ + "canonical-drive-roots-rejected-before-ownership-check", "enter-validates-current-path-and-unchecked-launch", "completion-preserves-window-position", "welcome-ready-before-first-show", diff --git a/apps/desktop/tests/fixtures/runtime-payload-smoke.mjs b/apps/desktop/tests/fixtures/runtime-payload-smoke.mjs index d0d7bb94f6..503c4bae3c 100644 --- a/apps/desktop/tests/fixtures/runtime-payload-smoke.mjs +++ b/apps/desktop/tests/fixtures/runtime-payload-smoke.mjs @@ -61,7 +61,11 @@ async function checkPty() { // A Windows GUI executable needs a console-owning shell when launched inside ConPTY. const executable = process.platform === 'win32' ? process.env.ComSpec : process.execPath const args = process.platform === 'win32' ? ['/d', '/c', 'node', script] : [script] - const terminal = pty.spawn(executable, args, { cwd: scratch, env, cols: 80, rows: 24 }) + // Mirror the product's Windows allocation: the console host node-pty ships. + const terminal = pty.spawn(executable, args, { + cwd: scratch, env, cols: 80, rows: 24, + ...(process.platform === 'win32' ? { useConptyDll: true } : {}), + }) let output = '' let exited = false let timedOut = false diff --git a/apps/desktop/tests/macos-signature.spec.ts b/apps/desktop/tests/macos-signature.spec.ts index c8d9f859af..d39ee99cb9 100644 --- a/apps/desktop/tests/macos-signature.spec.ts +++ b/apps/desktop/tests/macos-signature.spec.ts @@ -129,10 +129,31 @@ describe('desktop macOS release signature', () => { }) }) - it('rejects unsigned macOS builds and malformed signing modes', async () => { + it.each(['arm64', 'x64'])('builds local macOS %s DMGs without release credentials or update metadata', async (arch) => { + const { createElectronBuilderConfig } = await import('../scripts/electron-builder-config.mjs') + const config = createElectronBuilderConfig({ + DSH_DESKTOP_APP_ID: RELEASE_ENVIRONMENT.DSH_DESKTOP_APP_ID, + DSH_DESKTOP_UNSIGNED: '1', + }, 'darwin', arch) + expect(portablePath(config.directories.output)).toContain(`/targets/mac-${arch}/unsigned-artifacts`) + expect(config.artifactName).toBe('deepseek-harness-${version}-${os}-${arch}-unsigned.${ext}') + expect(config.mac).toMatchObject({ identity: '-', forceCodeSigning: false, notarize: false, target: ['dmg'] }) + expect(config.dmg).toMatchObject({ sign: false, writeUpdateInfo: false }) + expect(config.publish).toBeNull() + expect(config.extraMetadata).not.toHaveProperty('dshMandatoryUpdatePolicy') + await expect(config.afterSign({ electronPlatformName: 'darwin' } as Parameters[0])).resolves.toBeUndefined() + expect(config.artifactBuildCompleted({ file: '/tmp/local-unsigned.dmg' })).toBeUndefined() + }) + + it('omits mandatory-update policy from local macOS builds even when release settings are supplied', async () => { + const { createElectronBuilderConfig } = await import('../scripts/electron-builder-config.mjs') + const config = createElectronBuilderConfig({ ...RELEASE_ENVIRONMENT, DSH_DESKTOP_UNSIGNED: '1' }, 'darwin', 'arm64') + expect(config.extraMetadata).not.toHaveProperty('dshMandatoryUpdatePolicy') + expect(config.publish).toBeNull() + }) + + it('rejects malformed signing modes', async () => { const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs') - expect(() => createElectronBuilderConfig({ ...RELEASE_ENVIRONMENT, DSH_DESKTOP_UNSIGNED: '1' })) - .toThrow(/unsigned builds require Windows/u) expect(() => createElectronBuilderConfig({ ...RELEASE_ENVIRONMENT, DSH_DESKTOP_UNSIGNED: 'yes' })) .toThrow(/must be 0 or 1/u) }) diff --git a/apps/desktop/tests/package-target-stages.spec.ts b/apps/desktop/tests/package-target-stages.spec.ts index 41e1ee8d4e..cde1b0079d 100644 --- a/apps/desktop/tests/package-target-stages.spec.ts +++ b/apps/desktop/tests/package-target-stages.spec.ts @@ -113,6 +113,35 @@ it('checks the assembled macOS runtime before notarizing and recording the relea expect(writeFileSync).toHaveBeenCalledOnce() }) +it.each([ + ['mac-arm64', false], ['mac-arm64', true], ['mac-x64', false], ['mac-x64', true], +] as const)('builds unsigned %s with directory=%s without Apple credentials, notarization, or a release record', async (target, directory) => { + const { run, stages } = supervisor() + await packageTarget(parseDesktopPackageInvocation([target, '--unsigned', ...(directory ? ['--dir'] : [])], 'darwin', 'arm64'), { + ...environment, CSC_LINK: 'unused.p12', CSC_KEY_PASSWORD: 'unused-secret', APPLE_API_KEY: 'unused.p8', + DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Unused identity', DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234', + }, run) + expect(stages.at(-2)).toBe(`exec electron-builder --config electron-builder.config.mjs --mac --${target === 'mac-arm64' ? 'arm64' : 'x64'} --publish never${directory ? ' --dir' : ''}`) + expect(stages.at(-1)).toBe('exec tsx scripts/smoke-packaged-runtime.ts --unsigned') + for (const call of run.run.mock.calls) { + expect(call[3].env).toMatchObject({ DSH_DESKTOP_UNSIGNED: '1', CSC_IDENTITY_AUTO_DISCOVERY: 'false' }) + for (const key of ['CSC_LINK', 'CSC_KEY_PASSWORD', 'APPLE_API_KEY', 'DSH_DESKTOP_MACOS_SIGNING_IDENTITY', 'DSH_DESKTOP_MACOS_TEAM_ID']) { + expect(call[3].env).not.toHaveProperty(key) + } + } + expect(withMacOSNotarizationProxy).not.toHaveBeenCalled() + expect(packageMacOSArtifacts).not.toHaveBeenCalled() + expect(writeFileSync).not.toHaveBeenCalled() +}) + +it('fails unsigned macOS packaging when the assembled runtime cannot run', async () => { + const { run, stages } = supervisor('exec tsx scripts/smoke-packaged-runtime.ts --unsigned') + await expect(packageTarget(parseDesktopPackageInvocation(['mac-arm64', '--unsigned'], 'darwin', 'arm64'), environment, run)) + .rejects.toThrow('stage refused') + expect(stages.at(-1)).toBe('exec tsx scripts/smoke-packaged-runtime.ts --unsigned') + expect(writeFileSync).not.toHaveBeenCalled() +}) + it.each([false, true])('refuses macOS notarization and release records after an assembled-runtime failure (directory=%s)', async (directory) => { const { run } = supervisor('exec tsx scripts/smoke-packaged-runtime.ts') await expect(packageTarget(parseDesktopPackageInvocation(['mac-arm64', ...(directory ? ['--dir'] : [])], 'darwin', 'arm64'), environment, run)) diff --git a/apps/desktop/tests/package-target.spec.ts b/apps/desktop/tests/package-target.spec.ts index c802f668d2..5019841e84 100644 --- a/apps/desktop/tests/package-target.spec.ts +++ b/apps/desktop/tests/package-target.spec.ts @@ -73,14 +73,16 @@ describe('desktop package target', () => { expect(desktopElectronBuilderArguments(target, true)).toContain('--dir') }) - it('accepts unsigned Windows artifacts and rejects other targets or preparation-only use', () => { + it('accepts unsigned supported targets and rejects preparation-only use', () => { expect(parseDesktopPackageInvocation(['win-x64', '--unsigned'], 'win32', 'x64').unsigned).toBe(true) expect(parseDesktopPackageInvocation(['win-x64'], 'win32', 'x64').unsigned).toBe(false) expect(parseDesktopPackageInvocation(['--unsigned', '--dir'], 'win32', 'x64')).toMatchObject({ unsigned: true, directory: true, }) - expect(() => parseDesktopPackageInvocation(['mac-arm64', '--unsigned'], 'darwin', 'arm64')) - .toThrow(/requires win-x64/u) + for (const name of ['mac-arm64', 'mac-x64']) { + expect(parseDesktopPackageInvocation([name, '--unsigned'], 'darwin', 'arm64').unsigned).toBe(true) + expect(parseDesktopPackageInvocation([name, '--unsigned', '--dir'], 'darwin', 'arm64').directory).toBe(true) + } expect(() => parseDesktopPackageInvocation(['--unsigned', '--prepare-only'], 'win32', 'x64')) .toThrow(/cannot use --prepare-only/u) }) @@ -93,10 +95,16 @@ describe('desktop package target', () => { CSC_KEY_PASSWORD: 'secret', WIN_CSC_LINK: 'windows.pfx', CSC_IDENTITY_AUTO_DISCOVERY: 'true', + APPLE_API_KEY: 'private.p8', + APPLE_APP_SPECIFIC_PASSWORD: 'apple-secret', + DSH_DESKTOP_MACOS_SIGNING_IDENTITY: 'Example Company (TEAMID1234)', + DSH_DESKTOP_MACOS_TEAM_ID: 'TEAMID1234', + DSH_DESKTOP_MACOS_PACK_CONCURRENCY: '2', DSH_DESKTOP_UNSIGNED: '1', } expect(desktopElectronBuilderEnvironment(environment, true)).toEqual({ DSH_DESKTOP_APP_ID: 'com.example.desktop', + DSH_DESKTOP_MACOS_PACK_CONCURRENCY: '2', CSC_IDENTITY_AUTO_DISCOVERY: 'false', DSH_DESKTOP_UNSIGNED: '1', }) diff --git a/apps/desktop/tests/windows-installer-smoke.ps1 b/apps/desktop/tests/windows-installer-smoke.ps1 index 67a8aa38df..53404b494d 100644 --- a/apps/desktop/tests/windows-installer-smoke.ps1 +++ b/apps/desktop/tests/windows-installer-smoke.ps1 @@ -142,11 +142,13 @@ try { Click-Control $process $copy.INSTALLER_BROWSE Dismiss $process $copy.INSTALLER_CHOOSE_PATH [void][InstallerCapture]::SendMessage($window, 0x28, $edit, [IntPtr]1) - foreach ($invalidPath in @('C:\Windows\Harness Installer Test', [IO.Path]::GetPathRoot($installPath), ([IO.Path]::GetPathRoot($installPath) + '\'))) { + $driveRoot = [IO.Path]::GetPathRoot($installPath) + foreach ($invalidPath in @('C:\Windows\Harness Installer Test', $driveRoot, ($driveRoot + '\'), ($driveRoot + '\\'))) { [void][InstallerCapture]::SendMessage($edit, 0xC, [IntPtr]::Zero, $invalidPath) [void][InstallerCapture]::PostMessage($edit, 0x100, [IntPtr]13, [IntPtr]::Zero) Dismiss $process $copy.INSTALLER_PATH_INVALID } + $results.Add('canonical-drive-roots-rejected-before-ownership-check') [void][InstallerCapture]::SendMessage($edit, 0xC, [IntPtr]::Zero, $installPath) [InstallerCapture]::MoveBy($window, 73, -41) $bounds = [InstallerCapture]::Bounds($window) diff --git a/apps/web/package.json b/apps/web/package.json index fe4de9e9e1..6dfc75207f 100644 --- a/apps/web/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -46,26 +46,29 @@ "@deepseek-ai/dsh-experimental-webworker-packer": "workspace:*", "@deepseek-ai/dsh-experimental-webworker-runtime": "workspace:*", "@deepseek-ai/dsh-remote-mock": "workspace:*", - "@types/node": "^22.0.0", + "@deepseek-ai/dsh-util-values": "workspace:*", + "@types/node": "^22.20.1", "@types/react": "~18.3.1", "@types/react-dom": "~18.3.0", "@types/ws": "8.18.1", - "@vitejs/plugin-react": "^4.0.0", + "@vitejs/plugin-react": "^4.7.0", "http-server": "^14.1.1", "fflate": "^0.8.2", "playwright": "^1.49.0", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^6.0.3", - "vite": "^6.0.0", - "vitest": "^4.1.8", - "ws": "8.21.0", + "vite": "^6.4.3", + "vitest": "^4.1.11", + "ws": "8.21.3", "@deepseek-ai/dsh-client-ui-theme": "workspace:*", "@deepseek-ai/dsh-launch-environment": "workspace:*", "@deepseek-ai/dsh-workspace-changes": "workspace:*", "@deepseek-ai/dsh-subprocess-local": "workspace:*", "@deepseek-ai/dsh-experimental-auto-review": "workspace:*", "@deepseek-ai/dsh-experimental-speech-to-text": "workspace:*", - "@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:*" + "@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-cot-translation-bundle": "workspace:*", + "@deepseek-ai/dsh-experimental-worktree": "workspace:*" } } diff --git a/apps/web/tests/README.i18n.yaml b/apps/web/tests/README.i18n.yaml index 1c111b2040..524edaeb31 100644 --- a/apps/web/tests/README.i18n.yaml +++ b/apps/web/tests/README.i18n.yaml @@ -6,8 +6,8 @@ en: 3e8ecd3509fa944f zh: 3fd3ff9fcd1e1e90 /apps-web-browser-e2e/completion-observations: - en: 1a93c8d1a0e07ad8 - zh: e7c252f54f19c137 + en: 4a06ba5463b53bb3 + zh: 70863c70e15c6746 /apps-web-browser-e2e/these-are-host-face-tests: en: 8c406d423c8e2137 zh: a66a144fd146f56c diff --git a/apps/web/tests/README.md b/apps/web/tests/README.md index 98d1ced569..0076a3b809 100644 --- a/apps/web/tests/README.md +++ b/apps/web/tests/README.md @@ -16,7 +16,11 @@ Ordinary scenarios begin with no registered Workspace or Session and a durable m ## Completion observations -State-sensitive cases use Workspace, admission, attachment, and model-stream barriers to separate visible intermediate states from completed operations. Model-picker persistence assertions wait for the saved default, independently of menu closure. Details close waits for frame transitions; archive verification assigns an explicit title to the seeded Session and follows that identity across reload. See the [CI fixture synchronization decision](../../../.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.md). +State-sensitive cases use Workspace, admission, attachment, and model-stream barriers to separate visible intermediate states from completed operations. Model-picker persistence assertions wait for the saved default, independently of menu closure. Details close waits for frame transitions; archive verification assigns an explicit title to the seeded Session and follows that identity across reload. The [CI test reliability workflow](../../../.agents/skills/dsh-ci-test-reliability/SKILL.md#synchronize-on-state) owns these synchronization rules. + +[Idle-submission handoff](idle-submission-handoff.e2e.ts) waits up to the scaffold's 30-second turn-completion budget for Host frames and browser placement. Vitest owns scaffold teardown so a replay-consumption error does not replace the failed scenario assertion. + +Plugin-manager switches wait up to `PLUGIN_TOGGLE_SETTLE_MS` (10 seconds) for the Host's profile write and the store's follow-up inventory read, the two steps that republish `aria-checked`; that constant's JSDoc records the measured chain. Explicit scrolling uses `scrollIntoView` from `support.ts`: it resolves the locator again when its old element detaches and checks connection in the same browser task as native scrolling. Scenarios retain their visibility and geometry assertions after scrolling. diff --git a/apps/web/tests/README.zh.md b/apps/web/tests/README.zh.md index e804246f3d..d1968d5080 100644 --- a/apps/web/tests/README.zh.md +++ b/apps/web/tests/README.zh.md @@ -16,7 +16,11 @@ pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install --with-deps ## 完成状态观察 -依赖状态的用例使用 Workspace、接纳、附件和模型流屏障,区分可见中间状态与已完成操作。模型选择器的持久化断言等待默认设置保存完成,不以菜单关闭作为完成信号。详情关闭等待框架过渡结束;归档验证为 seed Session 设置显式标题,并跨重载跟踪该身份。参见 [CI fixture 同步决策](../../../.agents/notes/implemented/testing/2026-09-08-ci-completion-observations.zh.md)。 +依赖状态的用例使用 Workspace、接纳、附件和模型流屏障,区分可见中间状态与已完成操作。模型选择器的持久化断言等待默认设置保存完成,不以菜单关闭作为完成信号。详情关闭等待框架过渡结束;归档验证为 seed Session 设置显式标题,并跨重载跟踪该身份。[CI 测试可靠性工作流](../../../.agents/skills/dsh-ci-test-reliability/SKILL.md#synchronize-on-state)负责这些同步规则。 + +[空闲提交交接](idle-submission-handoff.e2e.ts)对 Host 帧和浏览器中的消息归位的等待使用 scaffold 的 30 秒 Turn 完成预算。Vitest 负责 scaffold 清理,使 replay 消耗检查错误不会覆盖场景中失败的断言。 + +插件管理器的开关为 Host 的 profile 写入与 store 随后的 inventory 读取预留 `PLUGIN_TOGGLE_SETTLE_MS`(10 秒),这两步完成后才会重新发布 `aria-checked`;链路的实测值记录在该常量的 JSDoc 中。 显式滚动使用 `support.ts` 的 `scrollIntoView`:旧元素脱离 DOM 时重新解析 locator,并在同一个浏览器任务中检查连接状态、执行原生滚动。各场景保留滚动后的可见性与几何断言。 diff --git a/apps/web/tests/agent-preset-selection.e2e.ts b/apps/web/tests/agent-preset-selection.e2e.ts index 714407c69e..6fc12aba02 100644 --- a/apps/web/tests/agent-preset-selection.e2e.ts +++ b/apps/web/tests/agent-preset-selection.e2e.ts @@ -18,7 +18,7 @@ import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest' import { SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionHeader, type SessionId, } from '@deepseek-ai/dsh-session' -import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent' +import { SUBAGENT_DESCRIPTOR_VERSION } from '@deepseek-ai/dsh-subagent' import { createSystemMessage, createUserMessage } from '@deepseek-ai/dsh-llm' import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, seedSession, watchConsole, @@ -163,9 +163,10 @@ async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise type: 'subagent/descriptor', seq: 2, time: SEEDED_CHILD_CREATED_AT + 2, - data: snapshotSubagentDescriptor({ + data: { + version: SUBAGENT_DESCRIPTOR_VERSION, mode: 'one-shot', provider: 'spawn', label: 'header order probe', - }), + }, }, { type: 'turn/end', @@ -183,9 +184,10 @@ async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise * seeded session records `minimal` too, so a substring match over the whole * list answers before the switch has landed. * @param scaffold - authenticated Web Host scaffold. + * @param targetId - exact Session to inspect after several Sessions have been created. * @returns the live session's preset, or undefined before it is listed. */ -async function livePreset(scaffold: WebScaffold): Promise { +async function livePreset(scaffold: WebScaffold, targetId?: SessionId): Promise { const response = await scaffold.hostFetch('/api/session/list', { method: 'POST', headers: { 'content-type': 'application/json' }, @@ -204,7 +206,7 @@ async function livePreset(scaffold: WebScaffold): Promise { } } } - const preset = body.result.value?.items.find(item => item.sessionId !== SEED_ID) + const preset = body.result.value?.items.find(item => targetId === undefined ? item.sessionId !== SEED_ID : item.sessionId === targetId) ?.projections?.values.agentPreset return typeof preset === 'string' ? preset : undefined } @@ -355,7 +357,7 @@ describe('web e2e: agent-preset selection', () => { await writeComposerDraft(page, composer, '') }, 90_000) - it('resets the hidden default to Standard while preserving the blank session until the user switches it', async () => { + it('preserves the current mode while New Session uses the reset Standard default', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-preset-disabled')) await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('standard') @@ -379,16 +381,18 @@ describe('web e2e: agent-preset selection', () => { await page.keyboard.press('Escape') await expect.poll(async () => (await scaffold.ctx.agentPresets.remoteExportList()).presets.find(preset => preset.isDefault)?.id).toBe('standard') - const reuse = page.waitForResponse('**/api/session/create') + const creation = page.waitForResponse('**/api/session/create') await page.getByRole('button', { name: 'New session', exact: true }).last().click() - const reused = await reuse - expect(reused.request().postDataJSON()).toHaveProperty('payload.args.request.sessionId') - expect(await reused.json()).toMatchObject({ result: { ok: true, value: { agentPreset: 'minimal' } } }) + const created = await creation + expect(created.request().postDataJSON()).not.toHaveProperty('payload.args.request.sessionId') + const createdBody = await created.json() as { result: { value: { sessionId: SessionId; agentPreset: string } } } + expect(createdBody).toMatchObject({ result: { ok: true, value: { agentPreset: 'standard' } } }) + const freshSessionId = createdBody.result.value.sessionId + const freshConversation = page.locator(`[data-conversation-session="${freshSessionId}"]`) + await freshConversation.waitFor() await page.reload() - await page.getByRole('button', { name: 'Minimal mode', exact: true }).click() - await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('minimal') - await page.getByRole('menuitem', { name: /^Standard mode/ }).click() - await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('standard') + await freshConversation.waitFor() + await page.getByRole('button', { name: 'Standard mode', exact: true }).waitFor() await openSettings(page, 'en') const reopened = page.getByRole('dialog', { name: 'Settings' }) @@ -401,7 +405,7 @@ describe('web e2e: agent-preset selection', () => { await reopened.getByRole('button', { name: 'New task default: Standard mode' }).waitFor({ timeout: 10_000 }) await reopened.getByRole('button', { name: 'Set as new task default: Minimal mode' }).waitFor({ timeout: 10_000 }) await reopened.getByRole('button', { name: 'Close' }).last().click() - await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('standard') + await expect.poll(() => livePreset(scaffold, freshSessionId), { timeout: 15_000 }).toBe('standard') await page.getByRole('button', { name: 'Standard mode' }).waitFor({ timeout: 10_000 }) }) diff --git a/apps/web/tests/assembled-boot.ts b/apps/web/tests/assembled-boot.ts index b7ab90a8e2..e58ee67be0 100644 --- a/apps/web/tests/assembled-boot.ts +++ b/apps/web/tests/assembled-boot.ts @@ -11,7 +11,7 @@ import { globSync, readFileSync } from 'node:fs' import { createRequire } from 'node:module' import { dirname, join, resolve } from 'node:path' import { pathToFileURL } from 'node:url' -import { act, cleanup } from '@testing-library/react' +import { act, cleanup, fireEvent, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, vi } from 'vitest' import { bootInjections, orderByModuleGraph } from '@deepseek-ai/dsh-client-modules' import type { ClientModuleLoaderTarget, WebBootEntry, WebBootGraph } from '@deepseek-ai/dsh-client-modules/client' @@ -310,6 +310,29 @@ export function mountAssembledApp(options: AssembledBootOptions = {}): Assembled return remote } +/** + * Create a fixture Session through the sidebar and wait for its own composer to mount. + * @param remote - fixture transport returned by mountAssembledApp. + * @returns composer inside the Session named by this creation's reply. + */ +export async function createAssembledSessionComposer(remote: AssembledRemote): Promise { + const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 }) + const start = tree.querySelector('button[aria-label="New session in fixture"]') + if (start === null) throw new Error('fixture Workspace new-session action missing') + const previousCalls = remote.mock.log.calls('session/create').length + fireEvent.click(start) + return await waitFor(() => { + const creation = remote.mock.log.calls('session/create')[previousCalls] + if (creation?.state !== 'answered') throw new Error('fixture Session creation has not completed') + const { value: { sessionId } } = creation.result as { value: { sessionId: string } } + const conversation = [...document.querySelectorAll('[data-conversation-session]')] + .find(element => element.getAttribute('data-conversation-session') === sessionId) + const composer = conversation?.querySelector('[data-composer-input][contenteditable="true"]') + if (composer === undefined || composer === null) throw new Error(`composer for Session ${sessionId} has not mounted`) + return composer + }, { timeout: 10_000 }) +} + /** * Match a CSS-module class by its logical name. * Module class names carry a per-build hash in one of two schemes — diff --git a/apps/web/tests/auto-review-child.overlay.yml b/apps/web/tests/auto-review-child.overlay.yml deleted file mode 100644 index a44addc554..0000000000 --- a/apps/web/tests/auto-review-child.overlay.yml +++ /dev/null @@ -1,9 +0,0 @@ -# Add one one-shot binding beside the shipped continuable delegation tool so -# this integration lane covers both in-process lifecycle modes. -- insert: - - id: auto-review-one-shot-subagent - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: spawn - toolName: subagent_one_shot - backgroundMode: one-shot diff --git a/apps/web/tests/background-job-list.e2e.ts b/apps/web/tests/background-job-list.e2e.ts index fa53fe4e0a..673270ae86 100644 --- a/apps/web/tests/background-job-list.e2e.ts +++ b/apps/web/tests/background-job-list.e2e.ts @@ -135,6 +135,55 @@ describe.skipIf(MODE === 'record')('web e2e: background job list', () => { expect(tripwire.warnings).toEqual([]) }, 90_000) + it('keeps the open list above the expanded right sidebar and in the header Tab order', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-background-job-right-sidebar')) + const trigger = page.getByRole('button', { name: '1 background job', exact: true }) + if (await trigger.getAttribute('aria-expanded') === 'true') await trigger.click() + await page.getByRole('button', { name: 'Open right sidebar' }).click() + const sidebar = page.locator('[data-rightbar-col]') + // A toggle holds data-animating on the frame until its tracks settle. + await expect.poll(() => sidebar.evaluate(column => column.parentElement?.hasAttribute('data-animating'))).toBe(false) + await trigger.click() + const list = page.getByRole('list', { name: 'Background jobs' }) + await list.waitFor({ timeout: 10_000 }) + // The list crosses into the sidebar column, so the hit test below covers it. + const [menuBox, sidebarBox] = await Promise.all([list.boundingBox(), sidebar.boundingBox()]) + if (menuBox === null || sidebarBox === null) throw new Error('job list geometry is unavailable') + expect(menuBox.x + menuBox.width).toBeGreaterThan(sidebarBox.x) + // Every corner of the list hit-tests to the list itself; the inset clears its rounded corners. + const covered = await list.evaluate((menu) => { + const rect = menu.getBoundingClientRect() + const inset = 20 + const points: Array<[number, number]> = [ + [rect.left + inset, rect.top + inset], [rect.right - inset, rect.top + inset], + [rect.left + inset, rect.bottom - inset], [rect.right - inset, rect.bottom - inset], + ] + return points.filter(([x, y]) => !menu.contains(document.elementFromPoint(x, y))) + }) + expect(covered).toEqual([]) + + // Keyboard order matches the in-place list: the trigger leads into the + // list, and Tab off its last control reaches the control after the trigger. + const after = await trigger.evaluate((node) => { + const all = [...document.querySelectorAll('button, a[href], [tabindex]:not([tabindex="-1"])')] + const outside = all.filter(el => el.closest('[aria-label="Background jobs"]') === null && el.tabIndex >= 0) + return outside[outside.indexOf(node as HTMLElement) + 1]?.outerHTML.slice(0, 120) + }) + expect(after).toMatch(/^

') + await page.locator('button').evaluate(button => { + button.addEventListener('click', event => { + button.setAttribute('data-click-trusted', String(event.isTrusted)) + const tail = document.createElement('div') + tail.setAttribute('data-chat-flow-key', '9:turn-tail:next') + document.body.append(tail) + }) + }) + const button = page.getByRole('button', { name: 'Load earlier', exact: true }) + const click = button.click.bind(button) + const elementHandles = button.elementHandles.bind(button) + const prepare = async (): Promise => { entered.resolve(undefined); await release.promise } + vi.spyOn(page, 'getByRole').mockReturnValue(button) + // The Locator spy gates preparation in the original selector-inside-timer control. + vi.spyOn(button, 'click').mockImplementation(async options => { await prepare(); await click(options) }) + vi.spyOn(button, 'elementHandles').mockImplementation(async () => { await prepare(); return elementHandles() }) + let now = 0 + operation = measureOlderPage(page, 1, () => now).then(value => ({ value }), (error: unknown) => ({ error })) + await Promise.race([ + entered.promise, + operation.then(outcome => { + if ('error' in outcome) throw outcome.error + throw new Error('Paging completed before the selector preparation barrier') + }), + ]) + expect(await page.locator('button').getAttribute('data-click-trusted')).toBeNull() + expect(await page.locator(TAIL).count()).toBe(1) + now = PAGE_BUDGET_MS + 1 + release.resolve(undefined) + const outcome = await operation + if ('error' in outcome) throw outcome.error + expectEndpointWithinBudget(outcome.value, PAGE_BUDGET_MS) + expect(outcome.value).toBe(0) + expect(await page.locator('button').getAttribute('data-click-trusted')).toBe('true') + expect(await page.locator(TAIL).count()).toBe(2) + } finally { + release.resolve(undefined) + try { await browser.close() } finally { + try { await operation } finally { vi.restoreAllMocks() } + } + } +}) + +it('waits for a named older-page control before starting its clock', async () => { + const browser = await chromium.launch({ headless: true }) + const entered = Promise.withResolvers() + let operation: Promise<{ value: number } | { error: unknown }> | undefined + try { + const page = await browser.newPage() + await page.setContent('
') + await page.locator('button').evaluate(button => { + button.addEventListener('click', event => { + button.setAttribute('data-click-trusted', String(event.isTrusted)) + const tail = document.createElement('div') + tail.setAttribute('data-chat-flow-key', '9:turn-tail:next') + document.body.append(tail) + }) + }) + const locator = page.getByRole('button', { name: 'Load earlier', exact: true }) + const waitFor = locator.waitFor.bind(locator) + vi.spyOn(page, 'getByRole').mockReturnValue(locator) + vi.spyOn(locator, 'waitFor').mockImplementation(options => { entered.resolve(undefined); return waitFor(options) }) + let now = 0 + operation = measureOlderPage(page, 1, () => now).then(value => ({ value }), (error: unknown) => ({ error })) + await Promise.race([ + entered.promise, + operation.then(outcome => { + if ('error' in outcome) throw outcome.error + throw new Error('Paging completed before control readiness') + }), + ]) + expect(await page.locator('button').getAttribute('data-click-trusted')).toBeNull() + now = PAGE_BUDGET_MS + 1 + await page.locator('button').evaluate(button => { + button.textContent = 'Load earlier' + button.removeAttribute('disabled') + }) + const outcome = await operation + if ('error' in outcome) throw outcome.error + expect(outcome.value).toBe(0) + expect(await page.locator('button').getAttribute('data-click-trusted')).toBe('true') + expect(await page.locator(TAIL).count()).toBe(2) + } finally { + try { await browser.close() } finally { + try { await operation } finally { vi.restoreAllMocks() } + } + } +}) + +it('keeps browser deadlines active with a fixed measurement clock', async () => { + const browser = await chromium.launch({ headless: true }) + try { + const page = await browser.newPage() + await expect(measure(page, async () => { + const pending = await page.waitForFunction(() => false, undefined, { timeout: 100 }) + await pending.dispose() + }, () => 0)).rejects.toThrow('Timeout 100ms exceeded') + } finally { + await browser.close() + } +}) + +it('rejects older-page rendering beyond the unchanged paging ceiling', async () => { + const browser = await chromium.launch({ headless: true }) + let operation: Promise<{ value: number } | { error: unknown }> | undefined + try { + const page = await browser.newPage() + await page.setContent('
') + await page.locator('button').evaluate(button => { + button.addEventListener('click', event => { button.setAttribute('data-click-trusted', String(event.isTrusted)) }) + }) + let now = 0 + operation = measureOlderPage(page, 1, () => now).then(value => ({ value }), (error: unknown) => ({ error })) + await page.waitForFunction(() => document.querySelector('button')?.getAttribute('data-click-trusted') === 'true') + now = PAGE_BUDGET_MS + 1 + await page.evaluate(() => { + const tail = document.createElement('div') + tail.setAttribute('data-chat-flow-key', '9:turn-tail:next') + document.body.append(tail) + }) + const outcome = await operation + if ('error' in outcome) throw outcome.error + expect(outcome.value).toBe(PAGE_BUDGET_MS + 1) + expect(() => expectEndpointWithinBudget(outcome.value, PAGE_BUDGET_MS)).toThrow() + } finally { + try { await browser.close() } finally { + try { await operation } finally { vi.restoreAllMocks() } + } + } +}) + +it('rejects ambiguous or replaced older-page buttons before paging', async () => { + const browser = await chromium.launch({ headless: true }) + try { + const page = await browser.newPage() + await page.setContent('') + await expect(measureOlderPage(page, 0)).rejects.toThrow('strict mode violation') + await page.setContent('') + const button = page.getByRole('button', { name: 'Load earlier', exact: true }) + const elementHandles = button.elementHandles.bind(button) + vi.spyOn(page, 'getByRole').mockReturnValue(button) + vi.spyOn(button, 'elementHandles').mockImplementation(async () => { + const handles = await elementHandles() + await button.evaluate(element => { element.replaceWith(element.cloneNode(true)) }) + return handles + }) + await expect(measureOlderPage(page, 0)).rejects.toThrow('Element is not attached') + } finally { + try { await browser.close() } finally { vi.restoreAllMocks() } + } +}) + it('opens, pages, navigates and streams into a 240-turn browser history', async () => { if (webSnapshotMode() !== 'replay') throw new Error('browser benchmarks require keyless replay mode') const samples: { open: number; page: number; trajectory: number; first: number; streamTask: number; streamWall: number; input: number; inputOverlapped: boolean; heapMb: number; nodes: number }[] = [] @@ -165,16 +368,12 @@ it('opens, pages, navigates and streams into a 240-turn browser history', async expect(initialTurns).toBeLessThan(HISTORY_TURNS) let count = initialTurns while (count < HISTORY_TURNS) { - pages.push(await measure(page, async () => { - await page.getByRole('button', { name: 'Load earlier', exact: true }).click() - await page.waitForFunction(({ selector, previous }) => document.querySelectorAll(selector).length > previous, { selector: TAIL, previous: count }) - })) + pages.push(await measureOlderPage(page, count)) count = await page.locator(TAIL).count() } const trajectory = await measure(page, async () => { await page.getByRole('tab', { name: 'Trajectory', exact: true }).click() - await page.getByRole('searchbox', { name: 'Search trajectory', exact: true }).waitFor() - await page.getByRole('row').last().waitFor() + await waitForTrajectory(page) }) await page.getByRole('tab', { name: 'Chat', exact: true }).click() await page.waitForFunction(({ selector, expected }) => document.querySelectorAll(selector).length === expected, { selector: TAIL, expected: HISTORY_TURNS }) diff --git a/benchmarks/package.json b/benchmarks/package.json index e496a148b2..68ce69fa23 100644 --- a/benchmarks/package.json +++ b/benchmarks/package.json @@ -16,6 +16,7 @@ "@deepseek-ai/dsh-agent-loop-testkit": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", "@deepseek-ai/dsh-api-session-controller": "workspace:*", + "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-client-store": "workspace:*", "@deepseek-ai/dsh-client-ui-chat": "workspace:*", "@deepseek-ai/dsh-deque": "workspace:*", @@ -42,7 +43,8 @@ "@deepseek-ai/dsh-tools": "workspace:*", "@deepseek-ai/dsh-token-meter": "workspace:*", "@deepseek-ai/dsh-typert-protocol": "workspace:*", - "@deepseek-ai/dsh-util-values": "workspace:*" + "@deepseek-ai/dsh-util-values": "workspace:*", + "zod": "^4.4.3" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" diff --git a/benchmarks/session-corpus/projection-list.bench.ts b/benchmarks/session-corpus/projection-list.bench.ts new file mode 100644 index 0000000000..26af1e52b3 --- /dev/null +++ b/benchmarks/session-corpus/projection-list.bench.ts @@ -0,0 +1,85 @@ +/** Session-list Host queue responsiveness; no browser input, transport, or paint is measured. */ + +import { mkdtemp, rm } from 'node:fs/promises' +import { cpus, tmpdir } from 'node:os' +import { join } from 'node:path' +import { expect, it } from 'vitest' +import { runBuiltBenchmarkWorker } from '../support/built-worker.ts' +import { ciTimeBudget, PERFORMANCE_BUDGET_HEADROOM } from '../support/calibration.ts' +import type { ProjectionListReport } from './projection-list.worker.ts' + +const WORKER = join(import.meta.dirname, '..', '.dsh-build', 'session-corpus', 'projection-list.worker.js') +const ATTEMPTS = 3 +const WORKER_TIMEOUT_MS = 120_000 +/** Coarse reference-machine throughput allowances, before CI scaling and variance headroom. */ +const LIST_REFERENCE_MS = { modest: 50, tail: 500, cheap: 150 } as const +/** Queue-delay reference allowance above the measured 16 ms work slices and one-row overshoot. */ +const CALLBACK_REFERENCE_MS = 30 +/** Reference retained-heap allowance; memory receives variance headroom but no CPU scaling. */ +const RETAINED_HEAP_REFERENCE_BYTES = 240 * 1024 * 1024 + +function median(values: readonly number[]): number { + return [...values].sort((left, right) => left - right)[Math.floor(values.length / 2)] as number +} + +async function run(workload: ProjectionListReport['workload']): Promise { + const root = await mkdtemp(join(tmpdir(), 'dsh-projection-list-bench-')) + try { + const outcome = await runBuiltBenchmarkWorker({ + worker: WORKER, args: [root, workload], timeoutMs: WORKER_TIMEOUT_MS, exposeGc: true, + }) + if (outcome.timedOut || outcome.signal !== null || outcome.exitCode !== 0 || outcome.report === undefined) { + throw new Error(`projection-list ${workload} failed: exit=${String(outcome.exitCode)}, signal=${String(outcome.signal)}, ` + + `timedOut=${String(outcome.timedOut)}\n${outcome.stderr.trim().split('\n').slice(-20).join('\n')}`) + } + return outcome.report + } finally { + // The shared launcher resolves only after child close, including timeout kills. + await rm(root, { recursive: true, force: true }) + } +} + +it.each(['modest', 'tail', 'cheap'] as const)('serves the %s projection list without monopolizing the Host queue', async (workload) => { + const reports: ProjectionListReport[] = [] + for (let attempt = 0; attempt < ATTEMPTS; attempt++) reports.push(await run(workload)) + const firstMs = reports.map(report => report.samples[0]!.listAndJsonMs) + const repeatMs = reports.map(report => median(report.samples.slice(1).map(sample => sample.listAndJsonMs))) + const heapBytes = reports.map(report => Math.max(...[...report.samples, report.responsiveness].map(sample => sample.memory.heapUsedBytes))) + const medians = { + firstListAndJsonMs: median(firstMs), + repeatListAndJsonMs: median(repeatMs), + firstListMs: median(reports.map(report => report.samples[0]!.listMs)), + repeatListMs: median(reports.map(report => median(report.samples.slice(1).map(sample => sample.listMs)))), + firstListAndJsonCpuMs: median(reports.map(report => report.samples[0]!.listAndJsonCpuMs)), + repeatListAndJsonCpuMs: median(reports.map(report => median(report.samples.slice(1).map(sample => sample.listAndJsonCpuMs)))), + retainedHeapBytes: median(heapBytes), + worstCallbackDelayMs: median(reports.map(report => report.responsiveness.maxCallbackDelayMs)), + repeatYieldCalls: median(reports.map(report => median(report.samples.slice(1).map(sample => sample.yieldCalls)))), + firstCallbackDelayMs: median(reports.map(report => report.samples[0]!.callbackDelayMs)), + repeatCallbackDelayMs: median(reports.map(report => median(report.samples.slice(1).map(sample => sample.callbackDelayMs)))), + firstEventLoopMaxMs: median(reports.map(report => report.samples[0]!.eventLoopMaxMs)), + repeatEventLoopMaxMs: median(reports.map(report => Math.max(...report.samples.slice(1).map(sample => sample.eventLoopMaxMs)))), + } + const budgets = { + listAndJsonMs: ciTimeBudget(LIST_REFERENCE_MS[workload]), + callbackDelayMs: ciTimeBudget(CALLBACK_REFERENCE_MS), + retainedHeapBytes: Math.ceil(RETAINED_HEAP_REFERENCE_BYTES * PERFORMANCE_BUDGET_HEADROOM), + } + console.log(JSON.stringify({ benchmark: `session-corpus/projection-list-${workload}`, + reports, medians, budgets, environment: { node: process.version, platform: process.platform, arch: process.arch, cpu: cpus()[0]?.model } })) + + for (const report of reports) { + expect(report.workload).toBe(workload) + expect(report.fixture.sessions).toBe({ modest: 50, tail: 300, cheap: 3_000 }[workload]) + expect(report.fixture.baseRows).toBe({ modest: 100, tail: 1_000, cheap: 0 }[workload]) + expect(report.samples).toHaveLength(4) + for (const sample of [...report.samples, report.responsiveness]) { + expect(sample.items).toBe(report.fixture.sessions) + expect(sample.wireViews).toBe(report.fixture.sessions) + } + } + expect(medians.worstCallbackDelayMs).toBeLessThanOrEqual(budgets.callbackDelayMs) + expect(medians.firstListAndJsonMs).toBeLessThanOrEqual(budgets.listAndJsonMs) + expect(medians.repeatListAndJsonMs).toBeLessThanOrEqual(budgets.listAndJsonMs) + expect(medians.retainedHeapBytes).toBeLessThanOrEqual(budgets.retainedHeapBytes) +}, ATTEMPTS * WORKER_TIMEOUT_MS + 30_000) diff --git a/benchmarks/session-corpus/projection-list.worker.ts b/benchmarks/session-corpus/projection-list.worker.ts new file mode 100644 index 0000000000..78a60b5c06 --- /dev/null +++ b/benchmarks/session-corpus/projection-list.worker.ts @@ -0,0 +1,309 @@ +/** Plain-Node Session-list measurements with large persisted projections and small wire summaries. */ + +import assert from 'node:assert/strict' +import { mkdir, writeFile } from 'node:fs/promises' +import { join } from 'node:path' +import { monitorEventLoopDelay, performance } from 'node:perf_hooks' +import { scheduler, setTimeout as delay } from 'node:timers/promises' +import { Context } from '@deepseek-ai/cordis' +import AgentRegistry from '@deepseek-ai/dsh-agent' +import SessionController from '@deepseek-ai/dsh-api-session-controller' +import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, type SessionHeader } from '@deepseek-ai/dsh-session' +import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' +import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' +import SessionProjectionCache, { projectionCacheDomainSpec } from '@deepseek-ai/dsh-session-projection-cache' +import SqliteSessionQueryEngine from '@deepseek-ai/dsh-session-query-sqlite' +import Storage from '@deepseek-ai/dsh-storage' +import * as StorageDomain from '@deepseek-ai/dsh-storage-domain' +import * as StorageJson from '@deepseek-ai/dsh-storage-json' +import { z } from 'zod' +import { generationLogPath, sessionDir, toHeaderLine } from '../../packages/session/session-persistence-jsonl/src/format.ts' +import { compressZstdFrame } from '../../packages/session/session-persistence-jsonl/src/zstd.ts' +import { assertBuiltBenchmarkRuntime } from '../support/built-worker.ts' + +const KEY = 'syntheticProjectionList' +const CWD = '/synthetic-projection-benchmark' +const BASE_TIME = 1_700_000_000_000 +const ROW_PERCENTAGES = [50, 75, 100, 175] as const +const WORKLOADS = { + modest: { sessions: 50, baseRows: 100, live: false }, + tail: { sessions: 300, baseRows: 1_000, live: false }, + cheap: { sessions: 3_000, baseRows: 0, live: true }, +} as const + +const stateSchema = z.object({ + sessionOrdinal: z.number().int(), + events: z.array(z.object({ + ordinal: z.number().int().nonnegative(), + category: z.enum(['input', 'output', 'tool']), + metrics: z.object({ tokens: z.number().int(), bytes: z.number().int(), elapsedMs: z.number().int() }), + content: z.array(z.object({ kind: z.enum(['text', 'metadata']), text: z.string() })), + tags: z.array(z.string()), + })), + requests: z.array(z.object({ + ordinal: z.number().int(), firstRow: z.number().int(), lastRow: z.number().int(), + route: z.object({ provider: z.string(), model: z.string(), options: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])) }), + usage: z.object({ inputTokens: z.number().int(), outputTokens: z.number().int(), cache: z.object({ hit: z.number().int(), miss: z.number().int() }) }), + references: z.array(z.object({ ordinal: z.number().int(), weight: z.number() })), + })), + totals: z.object({ rows: z.number().int(), requests: z.number().int(), tokens: z.number().int() }), +}) +const viewSchema = z.object({ sessionOrdinal: z.number().int(), rows: z.number().int(), requests: z.number().int(), tokens: z.number().int() }) +type ProjectionState = z.infer + +declare module '@deepseek-ai/dsh-session-projection/types' { + interface SessionProjectionStateMap { syntheticProjectionList: ProjectionState } + interface SessionProjectionMap { syntheticProjectionList: z.infer } +} + +interface Memory { + readonly heapUsedBytes: number + readonly rssBytes: number + /** Process lifetime peak, including fixture creation and Host mounting. */ + readonly peakRssBytes: number +} + +interface ListSample { + readonly listMs: number + readonly listAndJsonMs: number + readonly listCpuMs: number + readonly listAndJsonCpuMs: number + readonly callbackDelayMs: number + readonly maxCallbackDelayMs: number + readonly maxViewsPerBatch: number + readonly yieldCalls: number + readonly eventLoopMaxMs: number + readonly items: number + readonly wireViews: number + readonly responseJsonBytes: number + readonly retainedHeapDeltaBytes: number + readonly memory: Memory +} + +/** One fresh Host's fixture dimensions, retained memory, and first plus three repeated list samples. */ +export interface ProjectionListReport { + readonly workload: keyof typeof WORKLOADS + readonly fixture: { sessions: number; baseRows: number; totalRows: number; totalRequests: number; stateJsonBytes: number } + readonly baselineMemory: Memory + readonly samples: readonly ListSample[] + readonly responsiveness: ListSample +} + +interface ViewObservation { + calls: number + onView: (() => void) | undefined +} + +function headerOf(ordinal: number): SessionHeader { + return { version: SESSION_FORMAT_VERSION, id: SessionId(`projection-bench-${ordinal}`), + createdAt: BASE_TIME + ordinal, cwd: CWD, isSeeded: false, delegationDepth: 0 } +} + +function makeState(sessionOrdinal: number, baseRows: number): ProjectionState { + const count = Math.floor(baseRows * ROW_PERCENTAGES[sessionOrdinal % ROW_PERCENTAGES.length]! / 100) + const events: ProjectionState['events'] = Array.from({ length: count }, (_, ordinal) => ({ + ordinal, category: (['input', 'output', 'tool'] as const)[ordinal % 3]!, + metrics: { tokens: 32 + ordinal % 97, bytes: 128 + ordinal % 257, elapsedMs: ordinal % 41 }, + content: [ + { kind: 'text', text: `Generated observation ${sessionOrdinal}:${ordinal}. ` + 'Synthetic text. '.repeat(4 + ordinal % 4) }, + { kind: 'metadata', text: `bucket-${ordinal % 11}` }, + ], + tags: [`group-${sessionOrdinal % 7}`, `kind-${ordinal % 3}`], + })) + const requests = Array.from({ length: Math.ceil(count / 8) }, (_, ordinal) => ({ + ordinal, firstRow: ordinal * 8, lastRow: Math.min(count - 1, ordinal * 8 + 7), + route: { provider: 'synthetic', model: 'generated-model', options: { temperature: 0, streaming: true, tier: 'fixed' } }, + usage: { inputTokens: 100 + ordinal % 500, outputTokens: 30 + ordinal % 200, cache: { hit: ordinal % 100, miss: 20 } }, + references: Array.from({ length: Math.min(8, count - ordinal * 8) }, (_, index) => ({ ordinal: ordinal * 8 + index, weight: (index + 1) / 8 })), + })) + return { sessionOrdinal, events, requests, + totals: { rows: count, requests: requests.length, tokens: events.reduce((sum, event) => sum + event.metrics.tokens, 0) } } +} + +async function mountStorage(ctx: Context, root: string): Promise { + await ctx.plugin(Storage) + await ctx.plugin(StorageJson, { root: join(root, 'storages') }) + await ctx.plugin(StorageDomain, { backend: 'json' }) +} + +async function seed(root: string, workload: typeof WORKLOADS[keyof typeof WORKLOADS]): Promise { + const ctx = new Context() + const fixture = { ...workload, totalRows: 0, totalRequests: 0, stateJsonBytes: 0 } + try { + if (workload.live) { + for (let ordinal = 0; ordinal < workload.sessions; ordinal++) { + fixture.stateJsonBytes += Buffer.byteLength(JSON.stringify(makeState(ordinal, workload.baseRows))) + } + return fixture + } + await mountStorage(ctx, root) + const domain = await ctx.storageDomain.open(projectionCacheDomainSpec) + try { + const table = domain.table('sessions') + for (let ordinal = 0; ordinal < workload.sessions; ordinal++) { + const header = headerOf(ordinal) + const directory = sessionDir(join(root, 'sessions'), header.cwd, header.id) + await mkdir(directory, { recursive: true }) + await writeFile(generationLogPath(join(root, 'sessions'), header.cwd, header.id, SESSION_FORMAT_VERSION, 'zstd'), + await compressZstdFrame(JSON.stringify(toHeaderLine(header)) + '\n'), { flag: 'wx' }) + const state = makeState(ordinal, workload.baseRows) + await table.put(header.id, { + identity: { formatVersion: header.version, createdAt: header.createdAt, cwd: header.cwd, isSeeded: false, inheritedEventCount: SessionLogOffset(0) }, + rows: { + [KEY]: { ver: 1, seq: -1, val: state }, + sessionListMetadata: { ver: 1, seq: -1, val: { blank: true, lastPromptAt: null } }, + }, + }) + fixture.totalRows += state.events.length + fixture.totalRequests += state.requests.length + fixture.stateJsonBytes += Buffer.byteLength(JSON.stringify(state)) + } + } finally { + await domain.close() + } + } finally { + await ctx.fiber.dispose() + } + return fixture +} + +async function mountHost(ctx: Context, root: string, workload: typeof WORKLOADS[keyof typeof WORKLOADS], observation: ViewObservation, listWorkSliceMs?: number): Promise { + await ctx.plugin(SessionStore) + await ctx.plugin(AgentRegistry) + await ctx.plugin(SessionProjectionRegistry) + ctx.sessionProjections.register({ + key: KEY, stateVersion: 1, stateSchema, + init: header => makeState(Number(header.id.slice('projection-bench-'.length)), workload.baseRows), + apply: state => state, + wire: { + viewSchema, + view(state) { + observation.calls++ + observation.onView?.() + return { sessionOrdinal: state.sessionOrdinal, ...state.totals } + }, + }, + }) + if (workload.live) { + for (let ordinal = 0; ordinal < workload.sessions; ordinal++) { + const header = headerOf(ordinal) + ctx.sessions.create(header.id, { meta: header }) + } + } + await ctx.plugin(JsonlSessionPersistence, { root: join(root, 'sessions'), compression: 'zstd' }) + await mountStorage(ctx, root) + await ctx.plugin(SessionProjectionCache, { writeEveryEvents: 200, writeIntervalMs: 5_000 }) + await ctx.plugin(SqliteSessionQueryEngine, { path: ':memory:', openAt: 'never' }) + // No Agents, uploads, model calls, or workspace actions occur on this list path. + const dispose = (): void => {} + ctx.provide('typert', { lookups: { configure: () => dispose }, contexts: { configureHost: () => dispose } } as never) + ctx.provide('fileUploads', { registerAgentResolver: () => dispose } as never) + ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'synthetic', model: 'generated-model' }) } as never) + ctx.provide('workspaceRegistry', { list: () => [], archivedSessionIds: [] } as never) + return new SessionController(ctx, { nativeOpen: false, ...(listWorkSliceMs === undefined ? {} : { listWorkSliceMs }) }, { canOpenPath: () => false }) +} + +async function memory(): Promise { + const gc = (globalThis as typeof globalThis & { gc?: () => void }).gc + assert.ok(gc, 'projection-list worker requires --expose-gc') + gc() + await scheduler.yield() + gc() + const usage = process.memoryUsage() + return { heapUsedBytes: usage.heapUsed, rssBytes: usage.rss, peakRssBytes: process.resourceUsage().maxRSS * 1024 } +} + +async function measure(controller: SessionController, observation: ViewObservation, sessions: number, baseline: Memory, fullProbe = false): Promise { + const histogram = monitorEventLoopDelay({ resolution: 1 }) + let immediate: NodeJS.Immediate | undefined + const initialViews = observation.calls + const yieldDescriptor = Object.getOwnPropertyDescriptor(scheduler, 'yield') + const yieldWork = scheduler.yield.bind(scheduler) + let yieldCalls = 0 + Object.defineProperty(scheduler, 'yield', { + configurable: true, + value: () => { yieldCalls++; return yieldWork() }, + }) + let queued: Promise | undefined + let callbackDelayMs = 0 + let maxCallbackDelayMs = 0 + let maxViewsPerBatch = 0 + observation.onView = () => { + if (immediate !== undefined) return + if (!fullProbe) observation.onView = undefined + const beforeView = observation.calls - 1 + const start = performance.now() + queued = new Promise((resolve) => { + immediate = setImmediate(() => { + const elapsed = performance.now() - start + if (maxViewsPerBatch === 0) callbackDelayMs = elapsed + maxCallbackDelayMs = Math.max(maxCallbackDelayMs, elapsed) + maxViewsPerBatch = Math.max(maxViewsPerBatch, observation.calls - beforeView) + immediate = undefined + resolve() + }) + }) + } + histogram.enable() + try { + // Arm the histogram before work, and keep it enabled through the final blocking segment. + await delay(2) + histogram.reset() + const cpu = process.cpuUsage() + const start = performance.now() + const result = await controller.list({}, new AbortController().signal) + const listDone = performance.now() + const listYieldCalls = yieldCalls + const listCpu = process.cpuUsage(cpu) + const json = JSON.stringify(result) + const jsonDone = performance.now() + const jsonCpu = process.cpuUsage(cpu) + assert.equal(result.items.length, sessions) + assert.equal(observation.calls - initialViews, sessions) + assert.ok(result.items.every(item => item.projections?.values[KEY] !== undefined)) + // No more views can arrive after list completion; drain the final armed batch. + await queued + await delay(2) + const eventLoopMaxMs = histogram.max / 1_000_000 + histogram.disable() + const endpoint = await memory() + // Both endpoint values remain reachable while GC measures the retained heap. + assert.ok(json.length > result.items.length) + return { listMs: listDone - start, listAndJsonMs: jsonDone - start, + listCpuMs: (listCpu.user + listCpu.system) / 1000, listAndJsonCpuMs: (jsonCpu.user + jsonCpu.system) / 1000, + callbackDelayMs, maxCallbackDelayMs, maxViewsPerBatch, yieldCalls: listYieldCalls, eventLoopMaxMs, items: result.items.length, wireViews: observation.calls - initialViews, + responseJsonBytes: Buffer.byteLength(json), retainedHeapDeltaBytes: endpoint.heapUsedBytes - baseline.heapUsedBytes, memory: endpoint } + } finally { + observation.onView = undefined + if (immediate !== undefined) clearImmediate(immediate) + histogram.disable() + if (yieldDescriptor === undefined) Reflect.deleteProperty(scheduler, 'yield') + else Object.defineProperty(scheduler, 'yield', yieldDescriptor) + } +} + +assertBuiltBenchmarkRuntime(import.meta.url, { + controller: import.meta.resolve('@deepseek-ai/dsh-api-session-controller'), + registry: import.meta.resolve('@deepseek-ai/dsh-session-projection'), + cache: import.meta.resolve('@deepseek-ai/dsh-session-projection-cache'), + persistence: import.meta.resolve('@deepseek-ai/dsh-session-persistence-jsonl'), +}) +const [root, workload, sliceArgument] = process.argv.slice(2) +if (root === undefined || (workload !== 'modest' && workload !== 'tail' && workload !== 'cheap')) throw new Error('usage: projection-list.worker.js [work-slice-ms]') +const listWorkSliceMs = sliceArgument === undefined ? undefined : Number(sliceArgument) +if (listWorkSliceMs !== undefined && (!Number.isSafeInteger(listWorkSliceMs) || listWorkSliceMs < 1)) throw new Error('work-slice-ms must be a positive integer') +const ctx = new Context() +try { + const fixture = await seed(root, WORKLOADS[workload]) + const observation: ViewObservation = { calls: 0, onView: undefined } + const controller = await mountHost(ctx, root, WORKLOADS[workload], observation, listWorkSliceMs) + assert.equal(observation.calls, 0, 'setup must not warm wire views') + const baselineMemory = await memory() + const samples: ListSample[] = [] + for (let call = 0; call < 4; call++) samples.push(await measure(controller, observation, fixture.sessions, baselineMemory)) + const responsiveness = await measure(controller, observation, fixture.sessions, baselineMemory, true) + const report: ProjectionListReport = { workload, fixture, baselineMemory, samples, responsiveness } + console.log(JSON.stringify(report)) +} finally { + await ctx.fiber.dispose() +} diff --git a/benchmarks/tsdown.config.ts b/benchmarks/tsdown.config.ts index 4d21a78117..36c4386865 100644 --- a/benchmarks/tsdown.config.ts +++ b/benchmarks/tsdown.config.ts @@ -49,7 +49,10 @@ export default defineConfig([ }, { ...shared, - entry: { 'session-corpus.worker': 'session-corpus/session-corpus.worker.ts' }, + entry: { + 'session-corpus.worker': 'session-corpus/session-corpus.worker.ts', + 'projection-list.worker': 'session-corpus/projection-list.worker.ts', + }, outDir: '.dsh-build/session-corpus', clean: true, tsconfig: 'tsconfig.host.json', diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 76de4d3632..d4bbea471f 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — The documentation standard -This file defines document structure, Markdown tiers, writing rules, and `verify-doc-budgets` ceilings. Use [dsh-doc](../.agents/skills/dsh-doc/SKILL.md) for placement and validation, and [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for required coverage and editorial judgment; the [doc-tiers Agent Note](../.agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md) owns rationale. +This file defines document structure, Markdown tiers, writing rules, and `verify-doc-budgets` ceilings. Use [dsh-doc](../.agents/skills/dsh-doc/SKILL.md) for placement and validation, and [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for required coverage and editorial judgment; the [archived doc-tiers Agent Note](../.agents/notes/archived/process/2026-07-04-doc-tiers-and-budgets.md) records the historical rationale. ## Document structure @@ -29,7 +29,7 @@ Each fact has one home: the tier whose job it is; elsewhere, link there. | [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history | | Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | | [development.md](development.md) | Contributor setup, daily workflow, and a summary of CI; a bilingual pair under the [i18n contract](i18n/README.md) | Runtime/version rationale (→ Agent Notes), check-by-check lists that drift from `package.json` scripts | -| Generated reference: the per-page `cordis-surface` regions in [subsystems/](subsystems/README.md), the [Cordis core API + inherited tier](cordis-api/context.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive English sources regenerated from source and freshness-gated; reviewed Chinese counterparts follow the [pairing workflow](i18n/README.md#scope-and-exclusions) | Hand edits to generated English sources or regions; Chinese counterparts update through pairing only | +| Generated reference: the per-page `cordis-surface` regions in [subsystems/](subsystems/README.md), the [Cordis core API + inherited tier](cordis-api/context.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md), [cli-help](cli-help.md) | Exhaustive English sources regenerated from source and freshness-gated; reviewed Chinese counterparts follow the [pairing workflow](i18n/README.md#scope-and-exclusions) | Hand edits to generated English sources or regions; Chinese counterparts update through pairing only | | Skills (`.agents/skills/`) | Reusable workflows and specialized decision standards | Product and runtime contracts (→ docs or source) | Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookbooks; type definitions → subsystems; package contracts → READMEs; standing orders → root `AGENTS.md` with a rationale link. @@ -40,10 +40,10 @@ Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookb - **Apply the Agent Note creation criteria.** Mechanical/local edits are exempt, including local UI changes; keep existing owning notes accurate ([scope](../.agents/notes/README.md#when-to-write-one)). - **One physical line per paragraph** (`verify-md-wrap`): use editor soft-wrap. Code blocks, tables, and list structure keep their formatting; code comments stay under the linter's column limit. - **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses ` ```ts public-api `; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)). -- **The owning [subsystems page](subsystems/README.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types; a type is documented on its declaring package group's page ([page scoping](../.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md)). +- **The owning [subsystems page](subsystems/README.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types; a type is documented on its declaring package group's page ([page scoping](../scripts/verify-subsystem-pages.ts)). - **Pairs update together**: [Terminology-guided](i18n/terminology.md), single-pass active-agent work repositions first-use annotations, preserves untouched prose, and re-records; `dsh-translate-docs` remains user-invoked ([contract](i18n/README.md)). - **Comments and JSDoc state complete contracts, not reasoning transcripts.** Preserve behavior, failure, timing, ownership, modality, exceptions, consequences, and non-obvious orientation; delete narration, test walkthroughs, review analysis, and code restatement. Keep the local contract and link its rationale. Use [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for details. -- Write directly: name actors and facts ([decision](../.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). Reserve `seam` for the defined capability. Name the exact check, type, API, operation, or behavior instead of metaphorical "gate", "vocabulary", or "surface". +- Write directly: name actors and facts ([reference](../.agents/skills/dsh-prose-standard/SKILL.md)). Reserve `seam` for the defined capability. Name the exact check, type, API, operation, or behavior instead of metaphorical "gate", "vocabulary", or "surface". ## Wordcount Budgets diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 5e0e5f9848..0b096186d7 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -3,32 +3,32 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md /deepseek-harness-architecture: - en: 2125f3ec236bc7b2 - zh: 23d8815579592ec4 + en: 20678ed3f962afba + zh: 87a2b81675d93346 /deepseek-harness-architecture/cordis: en: da5060e577578ea7 zh: 2e77d570e32d8938 /deepseek-harness-architecture/profiles-and-bundles: - en: 8ef4912295aa32f3 - zh: cf8848a83743f3cc + en: 21423f481b18b0be + zh: 5343bd7ea0fae5bc /deepseek-harness-architecture/application-launch: en: 334b4f390b6e376b zh: ce094f24c6162be3 /deepseek-harness-architecture/desktop-application: - en: 9f08587366c03567 - zh: bf6abf664869dd88 + en: b9eeb18645121a8b + zh: 70d87a12c43e042a /deepseek-harness-architecture/core-packages: - en: b4a221ac2910600a - zh: 341046d3c9a9c9cd + en: 20ef882e44b06a10 + zh: c10c3934ed5570e9 /deepseek-harness-architecture/events: en: 214c3c81c21df6f9 zh: 75d2cdc3c9fc9930 /deepseek-harness-architecture/turn-flow: - en: badc0ffea030cdce - zh: 3241f0b37c3ec067 + en: e6402b28c0ed15fa + zh: 6c07be9047b42056 /deepseek-harness-architecture/session-log: - en: e1e3a44140036f25 - zh: 84c70c969e5ad216 + en: a2e35c43438fec6d + zh: e27bdac8e9feff32 /deepseek-harness-architecture/capability-seams: en: 93d1dc2eeda4aec2 zh: ee7a9da89151ea17 diff --git a/docs/architecture.md b/docs/architecture.md index c63a75cb23..51dcfa7e0f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,8 +4,6 @@ English | [中文](architecture.zh.md) Read this before changing anything under `packages/`. It assumes you know Cordis; if you do not, start with the [primer](cordis-primer.md) or the [tutorial](cordis-tutorial/index.md). -We recommend using an agent to explore the codebase and understand its architecture. - ## Cordis [Cordis](cordis-primer.md) is the framework under dsh: plugins contribute services, typed events, and reversible effects to a shared context. Every part of the product is a plugin, including the model adapter, the tool registry, the session log, and the agent loop itself, so each is replaceable from configuration. @@ -24,7 +22,7 @@ Each declares itself in its own `package.json` under a `dsh` field: `dsh.profile [`dsh-base`](../packages/bundle/base/README.md) is the shared first layer of the `web`, `headless`, `sdk`, and `acp` profiles: model adapters, tools, persistence, sandbox and approval policy, settings, credentials, telemetry. [`dsh-web-app`](../packages/bundle/web-app/README.md) adds the browser application, [`dsh-headless`](../packages/bundle/headless/README.md) adds a one-shot runner with no server, [`dsh-sdk-app`](../packages/bundle/sdk-app/README.md) adds the SDK JSON-RPC server, and [`dsh-acp-app`](../packages/bundle/acp-app/README.md) adds the automation-only ACP server. [`dsh-sdk-minimal`](../packages/bundle/sdk-minimal/README.md) is the deliberate exception: one bundle owns its complete explicit SDK tree and does not apply `dsh-base`. -Layers apply to an empty entry list in this order: each bundle in the profile's listed order, then the profile's `cordis.patch.yml`, then the home-level one, then any `--patch` overlay. A patch targets a row by id and replaces its whole config, or inserts new rows. +Layers apply to an empty entry list: the profile's ordered bundles, its `cordis.patch.yml`, the home patch, then `--patch` overlays. A patch targets a row by id and replaces its whole config, or inserts new rows. A `preset` patch applies these operations inside that declaration’s `config.plugins`, with the same layer precedence. YAML controls HMR: base enables config-only `dsh-hmr`; headless, SDK and ACP disable it; `sdk-minimal` omits it. Profile patches override these defaults. HMR coordinates watching and reloads; the launcher provides profile data and readiness. @@ -52,12 +50,10 @@ The Python SDK follows the same application architecture. Its runtime wheel pack The [Electron desktop application](../apps/desktop/README.md) ships its exact dsh production runtime in signed resources and owns the reserved `$DSH_HOME/profiles/desktop`. Shared helpers initialize profile files, reconcile bundles, and resolve installation and bundle dependencies without replacing pnpm-owned packages. Desktop and npm CLI share product data but keep packages, activation, and lockfiles separate. Desktop’s bundled CLI manages its initialized plugins. -Electron starts the private Desktop Host in Electron Node mode. The Host invokes the shared CLI profile runner and complete Web application. The window immediately loads packaged Web assets and waits for boot injections before activating client plugins in the same document. Web owns RPC and streams; the desktop carrier connects the local page to the authenticated Host. Node IPC carries boot injections, readiness, fatal errors, and shutdown. Desktop defaults to port `19387`; profile configuration can override it. Shell-owned UI runs plugin transactions through bundled pnpm with normal user and profile configuration. +Electron starts the private Desktop Host in Electron Node mode. The Host invokes the shared CLI profile runner and complete Web application. The window immediately loads packaged Web assets and waits for boot injections before activating client plugins in the same document. Web owns RPC and streams; the desktop carrier connects the local page to the authenticated Host. Node IPC carries boot injections, readiness, fatal errors, and shutdown. Desktop listens on an OS-assigned port by default; profile configuration can override it. Shell-owned UI runs plugin transactions through bundled pnpm with normal user and profile configuration. ## Core packages -Here are some core packages that contribute to the Cordis tree. - | Package | Owns | `ctx` key | |---|---|---| | [`core/session`](subsystems/session.md) | The append-only `SessionEvent` log and in-memory store | `ctx.sessions` | @@ -67,7 +63,6 @@ Here are some core packages that contribute to the Cordis tree. | [`core/agent-loop`](subsystems/core.md) | The default driver implementing that interface | `ctx.agentLoop` | | [`core/scope`](subsystems/scope.md) | The per-agent scoped-registration primitive | library, no key | | [`llm/llm`](subsystems/llm-streaming.md) | Message and stream vocabulary plus the adapter seam | `ctx.llm` | -| [`webhook/webhook`](subsystems/webhook.md) | Authenticated-delivery dispatch and Workspace Session creation | `ctx.webhookRuntime` | ## Events @@ -88,13 +83,15 @@ A **step** is one model request plus the tools it calls. A **turn** is zero or m ```text turn/start claim next-step input plus one queued message - assemble prompt sections + tool schemas; project runtime context - -> agent/pre-step reject | enter(messages, startsRequestSeries?) + assemble prompt sections + tool schemas + -> agent/pre-step; project runtime context at fallback + reject | enter(messages, startsRequestSeries?) reject, or a first enter rewritten empty -> close the turn with no step step/start agent/request -> prepareCall (cancellation commits neither system nor users) reconcile system/message using the prepared call capability - append entered messages as user/message; log request/header and request/context as needed + append entered messages as user/message; reconcile retained runtime context + log request/header and request/context as needed derive and freeze model history from the log stream the bound prepared call -> llm/stream -> agent/assistant-stream start agent/assistant-stream chunk* @@ -110,7 +107,7 @@ turn/end One inbox feeds the driver; injected context waits for a waking message. AgentLoop’s durable `inbox` projection exposes pending input without live Agents. -`agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, `agent/request` and `prepareCall()` resolve the actual route before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Every attempt synchronously reconciles the same rendered assembly, appends users only on the first attempt, logs header/context as needed, and derives and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The first admitted step reserves the system head before user messages even for an empty prompt (no wire message). The prompt travels only as `system/message` history: an empty rendering clears all active system nodes, leaving no old prompt model-visible; capable routes append non-empty updates after cached history, including supported tool updates; incapable routes and new request series consolidate non-empty prompt text at the first system node, with logged empty replacements for non-empty later system nodes ([decision](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md); [decision rule](../packages/core/agent-loop/README.md#understand-the-implementation)). +`agent/pre-step` decides the accepted input. Listeners may rewrite or reject claimed messages; a rejected or empty first claim closes a durable turn without a step. An enter decision may set `startsRequestSeries`: the loop logs a fresh `request/header` (reason `series`, or `change` with `startsSeries: true` when the envelope also changed). Wrapping listeners preserve that declaration with `{ ...decision, messages }`. After assembly and `step/start`, `agent/request` and `prepareCall()` resolve the actual route before the system prompt and accepted users are committed; cancellation during either async phase commits neither. Prepared call capabilities govern prompt admission, not preceding `request/context`. Each attempt refreshes registered runtime facts, reconciles the bound prompt, admits users once, and restores removed runtime context, logs header/context as needed, and freezes the request before streaming the bound call. Retries do not repeat assembly or `agent/pre-step`. Surface replacements and image-offload decisions after attachment start a new request series, including during the first resumed pre-step; unchanged resume continues the series. The first admitted step reserves the system head before user messages even for an empty prompt (no wire message). The prompt travels only as `system/message` history: an empty rendering clears all active system nodes, leaving no old prompt model-visible; capable routes append non-empty updates after cached history, including supported tool updates; incapable routes and new request series consolidate non-empty prompt text at the first system node, with logged empty replacements for non-empty later system nodes ([decision](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.md); [decision rule](../packages/core/agent-loop/README.md#understand-the-implementation)). The loop sends immutable requests with live cancellation and reuses freeze evidence for frozen identities; [agent-loop](../packages/core/agent-loop/README.md) owns construction and cancellation causes. @@ -124,10 +121,12 @@ The session log is the source of the context the model sees. `deriveMessages()` Session consumers know only the current logical format. Header-only `stat` and `list` rescan each Session directory, select its numerically highest canonical generation, and translate a supported historical header without loading events or publishing a successor. A stored-session `open` selects that same generation, refuses a future version, or decodes and composes the static adjacent migration chain once before returning validated current logical events. A read open uses that in-memory result without publishing a successor; a write open first encodes, verifies, and exclusively publishes the final version-named successor beside the unchanged source. Ordinary repair of an unsealed interrupted tail remains a handle consumer responsibility; migration inserts a missing interrupted `turn/end` only for the bounded released restart already sealed by a later `turn/start`. JSONL v0 uses `session.jsonl[.zstd]`, v1 and later use lowercase `session.vN.jsonl[.zstd]`, and committed generation paths are never renamed, replaced, or deleted. The JSONL provider owns physical framing, compression, generation selection, and exclusive publication, while each adjacent migration package owns exactly one `vN -> vN+1` step ([decision](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md)). -**Model-visible means logged.** A runtime invariant checks model requests are reconstructable from the log. New model-visible inputs require session events. Plugins changing existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly. Tool changes are capability-independent; [Session tool history](../packages/core/session/README.md) supplies provider declarations. +**Model-visible means logged.** Every model request must be reconstructable from the log. New model-visible inputs require session events. Plugins changing existing message content register [pure message projections](subsystems/session.md#plugin-owned-message-projections); detached readers supply the same definitions explicitly. Tool changes are capability-independent; [Session tool history](../packages/core/session/README.md) supplies provider declarations. **Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, host consumers read one typed state with `stateOf()`, and carriers batch cropped client views with `snapshot()`. A host reader either requires this service during activation or fails explicitly when the registry or required key is absent. Contributors may retain `ctx.inject(['sessionProjections'], ...)` registration without silently defaulting a missing host value. The agent loop registers shared `turnBoundary` state for its readers ([decision](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md)). +[Working directories](subsystems/working-directory.md) supply user context and execution paths without changing original project identity, sandbox write roots, or existing process directories. + ## Capability seams A **seam** is a swappable capability with three roles: a **Service Definition** declaring the interface, a **Service Provider** implementing it, and a **Consumer** using it, commonly a model-facing tool. A package may combine roles, but one role alone is not a seam; adding a capability means designing all three ([capability graph](capability-seams.md)). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 1ae1356a74..8836217bd1 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -4,8 +4,6 @@ 改动 `packages/` 下的任何内容之前,请先阅读本文。本文假定你已了解 Cordis;如果尚未了解,请先阅读[入门](cordis-primer.zh.md)或[教程](cordis-tutorial/index.zh.md)。 -建议使用 agent(智能体)探索代码库并理解其架构。 - ## Cordis [Cordis](cordis-primer.zh.md) 是 dsh 底层的框架:插件向共享上下文贡献服务、类型化事件和可逆的副作用。产品的每一部分都是插件,包括模型适配器、工具注册表、会话日志,以及 agent loop(智能体循环)本身,因此每个都可以从配置替换。 @@ -24,7 +22,7 @@ [`dsh-base`](../packages/bundle/base/README.zh.md) 是 `web`、`headless`、`sdk` 与 `acp` profile 的共享第一层:模型适配器、工具、持久化、沙箱与审批策略、设置、凭据、遥测。[`dsh-web-app`](../packages/bundle/web-app/README.zh.md) 增加浏览器应用,[`dsh-headless`](../packages/bundle/headless/README.zh.md) 增加不带服务器的一次性运行器,[`dsh-sdk-app`](../packages/bundle/sdk-app/README.zh.md) 增加 SDK JSON-RPC 服务器,[`dsh-acp-app`](../packages/bundle/acp-app/README.zh.md) 增加仅用于自动化的 ACP 服务器。[`dsh-sdk-minimal`](../packages/bundle/sdk-minimal/README.zh.md) 是刻意保留的例外:一个组合包拥有完整的显式 SDK 配置树,不应用 `dsh-base`。 -各层按此顺序应用在空条目列表之上:先按 profile 列出的顺序应用每个组合包,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的那份,最后是任意 `--patch` overlay。一条 patch 按 id 定位某个条目并替换其整个 config,或插入新条目。 +各层依次应用在空条目列表之上:profile 中有序的组合包、其 `cordis.patch.yml`、home patch,最后是 `--patch` overlay。一条 patch 按 id 定位某个条目并替换其整个 config,或插入新条目。`preset` patch 在目标声明的 `config.plugins` 内应用这些操作,沿用相同的层优先级。 YAML 控制 HMR:base 启用仅监视配置的 `dsh-hmr`;Headless、SDK 和 ACP 禁用它;`sdk-minimal` 不包含它。Profile patch 覆盖这些默认值。HMR 协调监听和重载;启动器提供 profile 数据和就绪信号。 @@ -52,12 +50,10 @@ Python SDK 遵循相同的应用架构。其运行时 wheel 把普通 `dsh` CLI [Electron 桌面应用](../apps/desktop/README.zh.md)在签名资源中携带精确匹配的 dsh 生产运行时,并拥有保留的 `$DSH_HOME/profiles/desktop`。共享 helper 初始化 profile 文件、协调 bundle,并解析安装与 bundle 的依赖而不替换 pnpm 拥有的包。Desktop 与 npm CLI 共享产品数据,但包、启用选择与锁文件保持独立。Desktop 内置 CLI 管理其已初始化的插件。 -Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共享 CLI profile runner 与完整 Web 应用。窗口立即加载打包 Web 资源,等待启动注入后在同一文档中激活客户端插件。Web 负责 RPC 与流;桌面载体将本地页面连接到已认证的 Host。Node IPC 承载启动注入、就绪、致命错误与关闭。Desktop 默认端口为 `19387`,profile 配置可覆盖。壳拥有的 UI 通过内置 pnpm 执行插件事务,并遵循正常用户与 profile 配置。 +Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共享 CLI profile runner 与完整 Web 应用。窗口立即加载打包 Web 资源,等待启动注入后在同一文档中激活客户端插件。Web 负责 RPC 与流;桌面载体将本地页面连接到已认证的 Host。Node IPC 承载启动注入、就绪、致命错误与关闭。Desktop 默认监听系统分配的端口,profile 配置可覆盖。壳拥有的 UI 通过内置 pnpm 执行插件事务,并遵循正常用户与 profile 配置。 ## 核心包 -以下是向 Cordis 树贡献内容的部分核心包。 - | 包 | 职责 | `ctx` 键 | |---|---|---| | [`core/session`](subsystems/session.zh.md) | 仅追加的 `SessionEvent` 日志和内存存储 | `ctx.sessions` | @@ -67,7 +63,6 @@ Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共 | [`core/agent-loop`](subsystems/core.zh.md) | 实现该接口的默认驱动器 | `ctx.agentLoop` | | [`core/scope`](subsystems/scope.zh.md) | 按 agent 划分作用域的注册原语 | 库,无 ctx 键 | | [`llm/llm`](subsystems/llm-streaming.zh.md) | 消息与流式词汇表,以及适配器 seam | `ctx.llm` | -| [`webhook/webhook`](subsystems/webhook.zh.md) | 已认证 delivery 的分派和 Workspace Session 创建 | `ctx.webhookRuntime` | @@ -92,13 +87,15 @@ AgentLoop 在启动已排队工作前等待串行 `agent/created` 初始化。 ```text turn/start claim next-step input plus one queued message - assemble prompt sections + tool schemas; project runtime context - -> agent/pre-step reject | enter(messages, startsRequestSeries?) + assemble prompt sections + tool schemas + -> agent/pre-step; project runtime context at fallback + reject | enter(messages, startsRequestSeries?) reject, or a first enter rewritten empty -> close the turn with no step step/start agent/request -> prepareCall (cancellation commits neither system nor users) reconcile system/message using the prepared call capability - append entered messages as user/message; log request/header and request/context as needed + append entered messages as user/message; reconcile retained runtime context + log request/header and request/context as needed derive and freeze model history from the log stream the bound prepared call -> llm/stream -> agent/assistant-stream start agent/assistant-stream chunk* @@ -114,7 +111,7 @@ turn/end 输入通过同一个 inbox 到达驱动器;注入的上下文等待一条唤醒消息。AgentLoop 的持久 `inbox` 投影使待处理输入在没有活跃 Agent 时仍可读取。 -`agent/pre-step` 决定接纳的输入。监听器可以改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,`agent/request` 和 `prepareCall()` 先解析实际路由,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试同步协调同一份已渲染组装结果、仅在首次尝试追加用户消息、按需记录 header/context、派生并冻结请求,再通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。首次接纳的步骤在用户消息之前预留系统头节点,即使提示词为空(不产生协议消息)。提示词仅通过 `system/message` 历史传递:空渲染文本清除所有生效的系统节点,模型不再看到旧提示词;具备能力的路由可在缓存前缀之后追加非空更新,包括同时发生的受支持工具更新;不具备能力的路由与新请求序列将非空提示词文本归并到首个系统节点,并为非空的后续系统节点记录空内容替换([决策](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md);[决策规则](../packages/core/agent-loop/README.zh.md#understand-the-implementation))。 +`agent/pre-step` 决定接纳的输入。监听器可以改写或拒绝已领取消息;首次领取被拒绝或为空时,关闭不含步骤的持久轮次。enter 决策可设置 `startsRequestSeries`:循环记录新的 `request/header`(原因为 `series`,或在封装同时变化时为携带 `startsSeries: true` 的 `change`)。包装监听器通过 `{ ...decision, messages }` 保留该声明。组装与 `step/start` 之后,`agent/request` 和 `prepareCall()` 先解析实际路由,再提交系统提示词与已接纳用户消息;在任一异步阶段取消都不会提交这两者。提示词准入依据已准备调用的能力,而非先前的 `request/context`。每次尝试刷新已注册运行时事实、协调绑定的提示词、仅接纳一次用户消息并恢复被移除的运行时上下文、按需记录 header/context,再冻结请求并通过绑定调用发起流式请求。重试不重复组装或 `agent/pre-step`。附接后的 surface 替换和图片省略决定开启新请求序列,包括恢复后的首次 pre-step 中发生的替换;未变化的恢复延续序列。首次接纳的步骤在用户消息之前预留系统头节点,即使提示词为空(不产生协议消息)。提示词仅通过 `system/message` 历史传递:空渲染文本清除所有生效的系统节点,模型不再看到旧提示词;具备能力的路由可在缓存前缀之后追加非空更新,包括同时发生的受支持工具更新;不具备能力的路由与新请求序列将非空提示词文本归并到首个系统节点,并为非空的后续系统节点记录空内容替换([决策](../.agents/notes/implemented/architecture/2026-09-02-system-prompt-as-surface-node.zh.md);[决策规则](../packages/core/agent-loop/README.zh.md#understand-the-implementation))。 循环发送不可变请求,同时保持取消有效,仅对完全冻结的对象复用冻结证据;[agent-loop](../packages/core/agent-loop/README.zh.md) 负责请求构建与取消原因。 @@ -128,10 +125,12 @@ turn/end Session 消费方只了解当前逻辑格式。仅 header 的 `stat` 与 `list` 会重新扫描每个 Session 目录,选择数值最高的规范 generation,并在不加载事件或发布后继的情况下转换受支持的历史 header。已存储 Session 的 `open` 选择同一 generation,拒绝未来版本,或只 Decode 并组合一次构建时静态确定的相邻迁移链,再返回经过校验的当前逻辑事件。只读 open 直接使用这份内存结果,不发布后继;写 open 则先编码、校验并在未改变源的旁边排他发布最终版本命名的后继。未被后续事件封住的普通中断尾部仍由句柄消费方修复;只有在后续 `turn/start` 已经封住一种有限的已发布 restart 时,migration 才会插入缺失的 interrupted `turn/end`。JSONL v0 使用 `session.jsonl[.zstd]`,v1 及后续版本使用小写 `session.vN.jsonl[.zstd]`;已提交 generation 路径绝不重命名、替换或删除。JSONL provider 负责物理 framing、压缩、generation 选择与排他发布,每个相邻迁移包只负责一个 `vN -> vN+1` 步骤([决策](../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md))。 -**模型可见即已记录。** 运行时不变量检查模型请求是否可从日志重建。新增模型可见输入需要会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。 工具变更不依赖能力;[Session 工具历史](../packages/core/session/README.zh.md)提供提供方声明。 +**模型可见即已记录。** 每个模型请求都必须能从日志重建。新增模型可见输入需要会话事件。修改现有消息内容的插件注册[纯消息投影](subsystems/session.zh.md#plugin-owned-message-projections),独立读取器显式传入相同的处理器。 工具变更不依赖能力;[Session 工具历史](../packages/core/session/README.zh.md)提供提供方声明。 **投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,host 消费方通过 `stateOf()` 读取单个类型化状态,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。host 读取方要么在激活时要求该服务,要么在注册表或必需 key 缺席时明确失败。贡献方可以保留 `ctx.inject(['sessionProjections'], ...)` 注册,但不能为缺失的 host 值静默提供默认值。agent loop 为读取方注册共享的 `turnBoundary` 状态([决策](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。 +[工作目录](subsystems/working-directory.zh.md) 提供用户上下文和执行路径,不改变原始项目标识、沙箱写入根目录或已有进程目录。 + ## 能力 seam 一个 **seam** 是一项可替换能力,包含三种角色:声明接口的 **Service Definition**、实现它的 **Service Provider**,以及使用它的 **Consumer**(通常是面向模型的工具)。一个包可以合并承担多个角色,但单一角色本身不是 seam;添加一项能力意味着把三者一并设计([能力图](capability-seams.zh.md))。 diff --git a/docs/capability-seams.i18n.yaml b/docs/capability-seams.i18n.yaml index bdc6ab959f..1c39cb4a31 100644 --- a/docs/capability-seams.i18n.yaml +++ b/docs/capability-seams.i18n.yaml @@ -6,5 +6,5 @@ en: 41d772192075e133 zh: 5ef3da69b4de1d8a /capability-seams-and-core-services: - en: 890a2217279b7c70 - zh: a5c64adca1fb5a40 + en: 5bdd69045ebd8e3c + zh: f791f38cf578f9c9 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 5cc25f88f2..f240b94937 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -7,6 +7,10 @@ A service can be a core spine service, a swappable capability seam, a bundle/com ```mermaid flowchart LR + pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] + svc_cotTranslation["ctx.cotTranslation
Reasoning translation Remote"] + pkg_experimental_translator["experimental-translator"] + svc_translator["ctx.translator
Anonymous text translation"] pkg_hmr["hmr"] svc_hmr["ctx.hmr
Serialized module and configuration reloads"] pkg_app_boot["app-boot"] @@ -20,6 +24,17 @@ flowchart LR svc_connection["ctx.connection
Authenticated browser transport"] pkg_api_gateway["api-gateway"] pkg_host_frontend_static["host-frontend-static"] + pkg_experimental_worktree["experimental-worktree"] + svc_worktrees["ctx.worktrees
Git worktree creation"] + pkg_experimental_tool_worktree["experimental-tool-worktree"] + pkg_working_directory["working-directory"] + svc_workingDirectory["ctx.workingDirectory
Session working directory"] + pkg_tool_working_directory["tool-working-directory"] + pkg_tool_fs["tool-fs"] + pkg_tool_bash["tool-bash"] + pkg_tool_pwsh["tool-pwsh"] + pkg_subagent["subagent"] + pkg_sdk_jsonrpc_server["sdk-jsonrpc-server"] pkg_mcp_resources["mcp-resources"] svc_mcpResources["ctx.mcpResources
Scoped MCP resource access"] pkg_mcp_client["mcp-client"] @@ -39,7 +54,6 @@ flowchart LR svc_attachments["ctx.attachments
Durable binary attachment storage"] pkg_attachment_local["attachment-local"] pkg_api_session_controller["api-session-controller"] - pkg_tool_fs["tool-fs"] pkg_llm_pi_ai["llm-pi-ai"] pkg_llm_deepseek["llm-deepseek"] pkg_client_file_upload["client-file-upload"] @@ -63,8 +77,6 @@ flowchart LR pkg_session_persistence["session-persistence"] pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] - pkg_subagent_in_process_driver["subagent-in-process-driver"] - pkg_invariants["invariants"] pkg_message_feedback["message-feedback"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] svc_speechController["ctx.speechController
Experimental transcription Remote"] @@ -85,15 +97,12 @@ flowchart LR pkg_api_workspace_controller["api-workspace-controller"] svc_workspaceController["ctx.workspaceController
Host Workspace Remote controller"] svc_directoryPickerController["ctx.directoryPickerController
Host directory-picking Remote controller"] - svc_invariants["ctx.invariants
Package-owned invariant registry"] - pkg_scope["scope"] pkg_typert_registry["typert-registry"] svc_typert["ctx.typert
Runtime type registry"] pkg_typert_loader["typert-loader"] svc_typertGateway["ctx.typertGateway
Typert Host invocation gateway"] svc_sessionPersistence["ctx.sessionPersistence
Durable session persistence seam"] pkg_session_persistence_jsonl["session-persistence-jsonl"] - pkg_tool_bash["tool-bash"] pkg_hooks_claude_code["hooks-claude-code"] pkg_hooks_codex["hooks-codex"] pkg_config_editor["config-editor"] @@ -200,7 +209,6 @@ flowchart LR pkg_shell["shell"] svc_shell["ctx.shell
Bash executor seam"] pkg_pwsh_local["pwsh-local"] - pkg_tool_pwsh["tool-pwsh"] pkg_shell_env["shell-env"] svc_shellEnv["ctx.shellEnv
Managed bash environment registry"] pkg_terminal["terminal"] @@ -226,7 +234,6 @@ flowchart LR pkg_fs_observation_policy["fs-observation-policy"] pkg_compaction["compaction"] svc_compaction["ctx.compaction
Compaction seam"] - pkg_subagent["subagent"] svc_subagents["ctx.subagents
Subagent provider and continuation service"] pkg_subagent_spawn_in_process["subagent-spawn-in-process"] pkg_subagent_fork_in_process["subagent-fork-in-process"] @@ -239,6 +246,8 @@ flowchart LR pkg_experimental_agent_team["experimental-agent-team"] svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] + pkg_experimental_claude_code_mods["experimental-claude-code-mods"] + svc_claudeCodeMods["ctx.claudeCodeMods
Claude Code mods bridge"] pkg_inspector["inspector"] svc_inspector["ctx.inspector
Cross-realm runtime inspection"] pkg_jobs["jobs"] @@ -322,11 +331,15 @@ flowchart LR pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse pkg_experimental_browser_use_playwright_mcp --> svc_browserUse pkg_experimental_browser_use_stagehand_native --> svc_browserUse + pkg_experimental_claude_code_mods --> svc_claudeCodeMods + pkg_experimental_client_ui_cot_translation --> svc_cotTranslation pkg_experimental_computer_use_cua_driver_mcp --> svc_computerUse pkg_experimental_computer_use_cua_driver_native --> svc_computerUse pkg_experimental_ptc_runtime_python --> svc_ptcRuntime pkg_experimental_speech_to_text --> svc_speechToText pkg_experimental_speech_to_text_sensevoice --> svc_speechToText + pkg_experimental_translator --> svc_translator + pkg_experimental_worktree --> svc_worktrees pkg_file_reference --> svc_fileReferences pkg_file_reference_local --> svc_fileReferences pkg_fs --> svc_fs @@ -341,7 +354,6 @@ flowchart LR pkg_host_product_telemetry_otel --> svc_productTelemetry pkg_host_webserver --> svc_webServer pkg_inspector --> svc_inspector - pkg_invariants --> svc_invariants pkg_jobs --> svc_jobs pkg_jobs_local --> svc_jobs pkg_llm --> svc_llm @@ -423,6 +435,7 @@ flowchart LR pkg_webhook --> svc_webhookRuntime pkg_workflow --> svc_workflowEngine pkg_workflow_ptc --> svc_workflowEngine + pkg_working_directory --> svc_workingDirectory pkg_workspace --> svc_workspaceRegistry pkg_workspace_changes --> svc_workspaceChanges svc_agentDefaultModel --> pkg_api_session_controller @@ -432,7 +445,7 @@ flowchart LR svc_agentTeams --> pkg_experimental_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop - svc_agents --> pkg_subagent_in_process_driver + svc_agents --> pkg_subagent svc_approval --> pkg_acp svc_approval --> pkg_tool_bash svc_approval --> pkg_tools @@ -465,15 +478,10 @@ flowchart LR svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs svc_hmr --> pkg_app_boot - svc_invariants --> pkg_agent - svc_invariants --> pkg_agent_loop - svc_invariants --> pkg_scope - svc_invariants --> pkg_session svc_jobs --> pkg_api_job_controller svc_jobs --> pkg_tool_bash svc_jobs --> pkg_tool_jobs svc_jobs --> pkg_tool_pwsh - svc_jobs --> pkg_tool_subagent svc_jobs --> pkg_tool_terminal svc_llm --> pkg_agent_loop svc_llm --> pkg_compaction_basic @@ -510,12 +518,11 @@ flowchart LR svc_sessionQuery --> pkg_tool_session_query svc_sessions --> pkg_agent svc_sessions --> pkg_agent_loop - svc_sessions --> pkg_invariants svc_sessions --> pkg_message_feedback svc_sessions --> pkg_session_persistence svc_sessions --> pkg_session_query svc_sessions --> pkg_session_query_sqlite - svc_sessions --> pkg_subagent_in_process_driver + svc_sessions --> pkg_subagent svc_settings --> pkg_api_settings_controller svc_shell --> pkg_hooks_claude_code svc_shell --> pkg_hooks_codex @@ -560,6 +567,7 @@ flowchart LR svc_tools --> pkg_tool_terminal svc_tools --> pkg_tool_todo svc_tools --> pkg_tool_web + svc_translator --> pkg_experimental_client_ui_cot_translation svc_typert --> pkg_api_gateway svc_typert --> pkg_typert_loader svc_userQuestions --> pkg_tool_ask_user @@ -570,18 +578,29 @@ flowchart LR svc_webhookRuntime --> pkg_webhook_github svc_workflowEngine --> pkg_tool_ralph svc_workflowEngine --> pkg_tool_workflow + svc_workingDirectory --> pkg_sdk_jsonrpc_server + svc_workingDirectory --> pkg_subagent + svc_workingDirectory --> pkg_tool_bash + svc_workingDirectory --> pkg_tool_fs + svc_workingDirectory --> pkg_tool_pwsh + svc_workingDirectory --> pkg_tool_working_directory svc_workspaceRegistry --> pkg_api_session_controller svc_workspaceRegistry --> pkg_api_workspace_controller + svc_worktrees --> pkg_experimental_tool_worktree svc_fs -. event gate .-> pkg_fs_observation_policy ``` | ctx key | Role | Owner | Implementations | Direct consumers | Companion plugins | Note | | --- | --- | --- | --- | --- | --- | --- | +| `ctx.cotTranslation` | `service` | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | - | - | Serves reasoning translations, reuses saved results, and joins ordinary Session activation for uncached inactive Sessions. | +| `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | Resolves Google or Bing requests and retains shared experimental records through the active Session writer. | | `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | Owns module and exact configuration watchers; application mutations share its queue and automatic reloads await the application file lock. | | `ctx.pluginRegistryProbe` | `core` | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | Races public registry responses on the Host; the Client owns the initial registry recommendation. | | `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | Shares profile package operations with the CLI and reports persisted and running state to Web and agent callers. | | `ctx.profileContext` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | The dsh launcher supplies data-only profile locations and composition inputs; reload scheduling belongs to dsh-hmr. | | `ctx.connection` | `core` | [`client-connection`](../packages/client/connection) | - | [`api-gateway`](../packages/api/gateway), [`host-frontend-static`](../packages/host/frontend-static) | - | Owns browser authentication and shared HTTP request dispatch; API adapters register endpoints and streams. | +| `ctx.worktrees` | `seam` | [`experimental-worktree`](../packages/experimental/worktree) | [`experimental-worktree`](../packages/experimental/worktree) | [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | - | Explicit experimental creation from a pinned local commit. Existing write permissions govern checkout and shared Git metadata; the working-directory service owns the resulting Session directory. | +| `ctx.workingDirectory` | `seam` | [`working-directory`](../packages/session/working-directory) | [`working-directory`](../packages/session/working-directory) | [`tool-working-directory`](../packages/session/tool-working-directory), [`tool-fs`](../packages/fs/tool-fs), [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`subagent`](../packages/subagent/subagent), [`sdk-jsonrpc-server`](../packages/sdk/server) | - | One Session projection owns the effective execution directory. The filesystem validates changes; user context reports them while original metadata and write grants stay fixed. | | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | Connection-owned providers serve shared resource tools in the calling agent scope. | | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | One provider-owned name per service instance. Providers own their tools and browser resources per live Session; the shared service has no browser operation API. | | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | One provider-owned name per service instance. Each provider also owns its model tools; the service has no common action API, runtime selection, or Session workflow lock. | @@ -592,7 +611,7 @@ flowchart LR | `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | Plugins prepare independent top-level fields; the official adapter merges them and commits their delivery state after HTTP acceptance. | | `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. | | `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. | -| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent`](../packages/subagent/subagent), [`message-feedback`](../packages/feedback/message-feedback) | - | Owns append-only Session instances and emits the durable session event feed. | | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | Validates bounded browser audio before provider dispatch. | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Owns Session commands, cold reads, durable-event following, live control state, model catalogs, workspace opening, and Agent activation policy. | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | Delegates file-reference discovery through the Session Controller's established Agent lookup policy. | @@ -605,10 +624,9 @@ flowchart LR | `ctx.terminalController` | `core` | [`api-terminal-controller`](../packages/api/terminal-controller) | - | - | - | Owns user terminal processes, default shell resolution and bounded screen recovery through the subprocess provider and typed Remote transport. | | `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Owns Workspace commands and reconnect-safe Workspace state delivery through the generated Remote namespace. | | `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | Carries the picking seam onto the wire: capability gating, cancellation, and the seam-coded failures a browser directory flow discriminates on. | -| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. | | `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. | | `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. | -| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | The JSONL backend persists the SessionEvent vocabulary as one artifact per Session. | +| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | The JSONL backend persists the SessionEvent vocabulary as one artifact per Session. | | `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. | | `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. | | `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | Owns the default-off settings namespace that Agent-scoped delegation tools sample when composing a new top-level Session. | @@ -624,29 +642,29 @@ flowchart LR | `ctx.messageFeedback` | `core` | [`message-feedback`](../packages/feedback/message-feedback) | - | - | - | Owns per-assistant-message feedback in the canonical Session log, target validation, per-item compare-and-set, and the Host unary Remote contract. Feedback stays outside model history; log export follows the consumer policy. | | `ctx.sessionFeedback` | `core` | [`command-feedback`](../packages/feedback/command-feedback) | - | - | - | Records one Session-level remark with its category as a log-only feedback/record event on a live Session through the Host unary Remote contract; the /feedback command shares the same producer. | | `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | [`api-workspace-controller`](../packages/api/workspace-controller), [`api-session-controller`](../packages/api/session-controller) | - | Owns WorkspaceId-branded records over the domain facility; stable sessionIds accounts drive Host RPC and GUI projections. | -| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/session-query/tool-session-query) | - | The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering. | +| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/experimental/tool-session-query) | - | The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering. | | `ctx.fileReferences` | `seam` | [`file-reference`](../packages/context/file-reference) | [`file-reference-local`](../packages/context/file-reference-local) | [`api-session-controller`](../packages/api/session-controller) | - | The interface returns path-only completion candidates within an Agent cwd; providers own namespace access and ranking without reading file contents. | | `ctx.sessionReferenceResolver` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax. | -| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm), [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | -| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | -| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | +| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm), [`session-title-all-prompts-llm`](../packages/experimental/session-title-all-prompts-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | +| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | +| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | | `ctx.userQuestions` | `seam` | [`user-questions`](../packages/interaction/user-questions) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI front ends provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. | | `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions. | | `ctx.agentPresets` | `core` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | - | - | - | Eagerly mounts YAML-declared preset revisions, binds Agents and cold readers to scoped contributions, and retains retired revisions until their last user releases them. | | `ctx.commands` | `core` | [`commands`](../packages/interaction/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | | `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`api-session-controller`](../packages/api/session-controller), [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and the Session controller serves baselines and pushes changed values. | | `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`api-session-controller`](../packages/api/session-controller), [`session-query`](../packages/session-query/session-query), [`session-reference`](../packages/context/session-reference) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points), serves cached projection views, and accelerates prepared-Session projection hydration. | -| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl), [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem), [`skill-office`](../packages/skill/skill-office) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | -| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. | +| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl), [`skill-badge`](../packages/experimental/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem), [`skill-office`](../packages/skill/skill-office) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | +| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent`](../packages/subagent/subagent) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. | | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | Reads the default ModelSelection from volatile Config and saves selections through the profile editor. | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | | `ctx.schedule` | `core` | [`schedule`](../packages/schedule/schedule) | - | - | - | Stores tasks independently of Session activation and queues due messages in the original Session. | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. | | `ctx.ssh` | `core` | [`ssh`](../packages/ssh/ssh) | - | [`fs-ssh`](../packages/ssh/fs-ssh), [`subprocess-ssh`](../packages/ssh/subprocess-ssh), [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | - | Owns one authenticated OpenSSH connection, installed helper identity, independent program streams and disconnect cleanup for the paired remote providers. | | `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | The bash executors, the PTY shell backend, the LSP host, and the out-of-process ACP, Codex, and Claude Code subagent backends spawn through ctx.subprocess; the service owns process coordinates, tree/session lifetime, stdio dispositions, terminal mechanics, and kill escalation. | -| `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing shell tools and hook bridges consume this seam; sandboxed, remote, or PowerShell executors replace bash-local without touching them. | +| `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex) | - | The model-facing shell tools and hook bridges consume this seam; sandboxed, remote, or PowerShell executors replace bash-local without touching them. | | `ctx.shellEnv` | `core` | [`shell-env`](../packages/shell/shell-env) | - | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh) | - | Plugins declare effect-scoped DSH_* facts; each shell tool collects one trusted snapshot per execution and its executor rebuilds the namespace. | -| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools. | +| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/experimental/tool-terminal) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools. | | `ctx.sandbox` | `seam` | [`sandbox`](../packages/sandbox/sandbox) | [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | Consumers hand over the exact argv they are about to spawn; same-world backends wrap it under a per-call policy and report enforcement. | | `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | The one home for the deployment default mode + workspace root; only the sandboxed executor and provider read the service (the tool layers use the pure `sandbox/mode` fold it also exports). Both enforcing families read it so bash and fs cannot confine to different roots. | | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. | @@ -654,18 +672,19 @@ flowchart LR | `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools), [`workflow-ptc`](../packages/workflow/workflow-ptc) | - | Runs programs against host-provided async bindings; tools owns PTC presentation and workflow-ptc owns workflow orchestration. | | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-ssh`](../packages/ssh/fs-ssh) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-observation-policy contributes observed-state checks through the fs/* event gate. | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. | -| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | Providers implement transports; the service also owns optional Activation-based continuation orchestration, tool-subagent selects one-shot or continuable delegation, tool-subagent-control delivers follow-ups, and tool-ralph requires one fresh structured-output route. | +| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | Providers prepare local children or execute external tasks; one activation manager owns both lifecycles. The delegation tool selects a backend, control tools deliver local follow-ups, and workflows await activation results. | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | Routes explicit recognizers; the browser uses the authenticated Remote and keeps transcripts in the draft until submission. | | `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls. | +| `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | Loads mods that defineMod plugins add, raises their hook chains from harness extension points, and draws the band above the prompt over its Remote. | | `ctx.inspector` | `core` | `inspector` | - | - | - | Owns the Worker-hosted CDP target and the transport-independent Host and Client observation and Cordis-tree query API. | -| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | Producers (background bash/pwsh, PTY sends, and subagent delegations) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. | +| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | Producers (background bash/pwsh and PTY sends) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names. | | `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | The backend saves oversized tool text and returns a model-facing locator plus retrieval hint; spill-policy is the tools/post-execute consumer that decides when to spill. | | `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`api-workspace-controller`](../packages/api/workspace-controller) | - | Discriminated interaction capability: the native backend opens one OS chooser on the host display, the browse backend serves listing/creation primitives for the in-app browser; dual-face backends fill ui-workspace directory-flow slots from their browser halves (no wire advertisement). | | `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | Plain node:http carrier: named-route registry, index transform taps, and the static dist fallback; web-transport plugins register their own routes. | | `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | Composes the __DSH_BOOT__ entry graph from an incremental dsh.client scan, serves plugin bundles, and notifies rebuilt/graph-changed subscribers. | -| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. | -| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | Provider adapters dispatch authenticated deliveries; trusted plugins register independent process-local rules, and the runtime turns non-null results into ordinary Workspace-backed Sessions without delivery or completion state. | +| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/experimental/tool-ralph) | - | One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents. | +| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/experimental/webhook) | - | [`webhook-github`](../packages/experimental/webhook-github) | - | Provider adapters dispatch authenticated deliveries; trusted plugins register independent process-local rules, and the runtime turns non-null results into ordinary Workspace-backed Sessions without delivery or completion state. | | `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | Provider registration and selection plus normalized query execution over exactly four operations; the seam offers no protocol escape hatch, so a backend translates into the normalized request and result. | | `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Owns the in-memory definition registry, the vm sandbox for host halves, and the request-run round trip; browser pages reach the same service over the wire through its remote namespace. | | `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | Registers host inspect providers, mirrors the client provider manifest, and routes client queries through the dynamic Cordis transport. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 03c62ceeca..17b488deeb 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -9,6 +9,10 @@ ```mermaid flowchart LR + pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] + svc_cotTranslation["ctx.cotTranslation
Reasoning translation Remote"] + pkg_experimental_translator["experimental-translator"] + svc_translator["ctx.translator
Anonymous text translation"] pkg_hmr["hmr"] svc_hmr["ctx.hmr
Serialized module and configuration reloads"] pkg_app_boot["app-boot"] @@ -22,6 +26,17 @@ flowchart LR svc_connection["ctx.connection
Authenticated browser transport"] pkg_api_gateway["api-gateway"] pkg_host_frontend_static["host-frontend-static"] + pkg_experimental_worktree["experimental-worktree"] + svc_worktrees["ctx.worktrees
Git worktree creation"] + pkg_experimental_tool_worktree["experimental-tool-worktree"] + pkg_working_directory["working-directory"] + svc_workingDirectory["ctx.workingDirectory
Session working directory"] + pkg_tool_working_directory["tool-working-directory"] + pkg_tool_fs["tool-fs"] + pkg_tool_bash["tool-bash"] + pkg_tool_pwsh["tool-pwsh"] + pkg_subagent["subagent"] + pkg_sdk_jsonrpc_server["sdk-jsonrpc-server"] pkg_mcp_resources["mcp-resources"] svc_mcpResources["ctx.mcpResources
Scoped MCP resource access"] pkg_mcp_client["mcp-client"] @@ -41,7 +56,6 @@ flowchart LR svc_attachments["ctx.attachments
Durable binary attachment storage"] pkg_attachment_local["attachment-local"] pkg_api_session_controller["api-session-controller"] - pkg_tool_fs["tool-fs"] pkg_llm_pi_ai["llm-pi-ai"] pkg_llm_deepseek["llm-deepseek"] pkg_client_file_upload["client-file-upload"] @@ -65,8 +79,6 @@ flowchart LR pkg_session_persistence["session-persistence"] pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] - pkg_subagent_in_process_driver["subagent-in-process-driver"] - pkg_invariants["invariants"] pkg_message_feedback["message-feedback"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] svc_speechController["ctx.speechController
Experimental transcription Remote"] @@ -87,15 +99,12 @@ flowchart LR pkg_api_workspace_controller["api-workspace-controller"] svc_workspaceController["ctx.workspaceController
Host Workspace Remote controller"] svc_directoryPickerController["ctx.directoryPickerController
Host directory-picking Remote controller"] - svc_invariants["ctx.invariants
Package-owned invariant registry"] - pkg_scope["scope"] pkg_typert_registry["typert-registry"] svc_typert["ctx.typert
Runtime type registry"] pkg_typert_loader["typert-loader"] svc_typertGateway["ctx.typertGateway
Typert Host invocation gateway"] svc_sessionPersistence["ctx.sessionPersistence
Durable session persistence seam"] pkg_session_persistence_jsonl["session-persistence-jsonl"] - pkg_tool_bash["tool-bash"] pkg_hooks_claude_code["hooks-claude-code"] pkg_hooks_codex["hooks-codex"] pkg_config_editor["config-editor"] @@ -202,7 +211,6 @@ flowchart LR pkg_shell["shell"] svc_shell["ctx.shell
Bash executor seam"] pkg_pwsh_local["pwsh-local"] - pkg_tool_pwsh["tool-pwsh"] pkg_shell_env["shell-env"] svc_shellEnv["ctx.shellEnv
Managed bash environment registry"] pkg_terminal["terminal"] @@ -228,7 +236,6 @@ flowchart LR pkg_fs_observation_policy["fs-observation-policy"] pkg_compaction["compaction"] svc_compaction["ctx.compaction
Compaction seam"] - pkg_subagent["subagent"] svc_subagents["ctx.subagents
Subagent provider and continuation service"] pkg_subagent_spawn_in_process["subagent-spawn-in-process"] pkg_subagent_fork_in_process["subagent-fork-in-process"] @@ -241,6 +248,8 @@ flowchart LR pkg_experimental_agent_team["experimental-agent-team"] svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] + pkg_experimental_claude_code_mods["experimental-claude-code-mods"] + svc_claudeCodeMods["ctx.claudeCodeMods
Claude Code mods bridge"] pkg_inspector["inspector"] svc_inspector["ctx.inspector
Cross-realm runtime inspection"] pkg_jobs["jobs"] @@ -324,11 +333,15 @@ flowchart LR pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse pkg_experimental_browser_use_playwright_mcp --> svc_browserUse pkg_experimental_browser_use_stagehand_native --> svc_browserUse + pkg_experimental_claude_code_mods --> svc_claudeCodeMods + pkg_experimental_client_ui_cot_translation --> svc_cotTranslation pkg_experimental_computer_use_cua_driver_mcp --> svc_computerUse pkg_experimental_computer_use_cua_driver_native --> svc_computerUse pkg_experimental_ptc_runtime_python --> svc_ptcRuntime pkg_experimental_speech_to_text --> svc_speechToText pkg_experimental_speech_to_text_sensevoice --> svc_speechToText + pkg_experimental_translator --> svc_translator + pkg_experimental_worktree --> svc_worktrees pkg_file_reference --> svc_fileReferences pkg_file_reference_local --> svc_fileReferences pkg_fs --> svc_fs @@ -343,7 +356,6 @@ flowchart LR pkg_host_product_telemetry_otel --> svc_productTelemetry pkg_host_webserver --> svc_webServer pkg_inspector --> svc_inspector - pkg_invariants --> svc_invariants pkg_jobs --> svc_jobs pkg_jobs_local --> svc_jobs pkg_llm --> svc_llm @@ -425,6 +437,7 @@ flowchart LR pkg_webhook --> svc_webhookRuntime pkg_workflow --> svc_workflowEngine pkg_workflow_ptc --> svc_workflowEngine + pkg_working_directory --> svc_workingDirectory pkg_workspace --> svc_workspaceRegistry pkg_workspace_changes --> svc_workspaceChanges svc_agentDefaultModel --> pkg_api_session_controller @@ -434,7 +447,7 @@ flowchart LR svc_agentTeams --> pkg_experimental_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop - svc_agents --> pkg_subagent_in_process_driver + svc_agents --> pkg_subagent svc_approval --> pkg_acp svc_approval --> pkg_tool_bash svc_approval --> pkg_tools @@ -467,15 +480,10 @@ flowchart LR svc_fileUploads --> pkg_api_session_controller svc_fs --> pkg_tool_fs svc_hmr --> pkg_app_boot - svc_invariants --> pkg_agent - svc_invariants --> pkg_agent_loop - svc_invariants --> pkg_scope - svc_invariants --> pkg_session svc_jobs --> pkg_api_job_controller svc_jobs --> pkg_tool_bash svc_jobs --> pkg_tool_jobs svc_jobs --> pkg_tool_pwsh - svc_jobs --> pkg_tool_subagent svc_jobs --> pkg_tool_terminal svc_llm --> pkg_agent_loop svc_llm --> pkg_compaction_basic @@ -512,12 +520,11 @@ flowchart LR svc_sessionQuery --> pkg_tool_session_query svc_sessions --> pkg_agent svc_sessions --> pkg_agent_loop - svc_sessions --> pkg_invariants svc_sessions --> pkg_message_feedback svc_sessions --> pkg_session_persistence svc_sessions --> pkg_session_query svc_sessions --> pkg_session_query_sqlite - svc_sessions --> pkg_subagent_in_process_driver + svc_sessions --> pkg_subagent svc_settings --> pkg_api_settings_controller svc_shell --> pkg_hooks_claude_code svc_shell --> pkg_hooks_codex @@ -562,6 +569,7 @@ flowchart LR svc_tools --> pkg_tool_terminal svc_tools --> pkg_tool_todo svc_tools --> pkg_tool_web + svc_translator --> pkg_experimental_client_ui_cot_translation svc_typert --> pkg_api_gateway svc_typert --> pkg_typert_loader svc_userQuestions --> pkg_tool_ask_user @@ -572,18 +580,29 @@ flowchart LR svc_webhookRuntime --> pkg_webhook_github svc_workflowEngine --> pkg_tool_ralph svc_workflowEngine --> pkg_tool_workflow + svc_workingDirectory --> pkg_sdk_jsonrpc_server + svc_workingDirectory --> pkg_subagent + svc_workingDirectory --> pkg_tool_bash + svc_workingDirectory --> pkg_tool_fs + svc_workingDirectory --> pkg_tool_pwsh + svc_workingDirectory --> pkg_tool_working_directory svc_workspaceRegistry --> pkg_api_session_controller svc_workspaceRegistry --> pkg_api_workspace_controller + svc_worktrees --> pkg_experimental_tool_worktree svc_fs -. event gate .-> pkg_fs_observation_policy ``` | ctx 键 | 角色 | 所属包 | 实现 | 直接消费方 | 配套插件 | 说明 | | --- | --- | --- | --- | --- | --- | --- | +| `ctx.cotTranslation` | `service` | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | - | - | 为展开的思考内容提供有界的 Session 翻译请求,不改变模型可见历史。 | +| `ctx.translator` | `service` | [`experimental-translator`](../packages/experimental/translator) | - | [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | - | 解析 Google 或 Bing 请求,并可选保留统一的实验性 Session 记录。 | | `ctx.hmr` | `core` | [`hmr`](../packages/boot/hmr) | - | [`app-boot`](../packages/boot/app-boot) | - | 负责模块和精确配置监听;应用修改共用其队列,自动重载等待应用文件锁。 | | `ctx.pluginRegistryProbe` | `core` | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager) | - | 在 Host 上并发比较公共安装源响应;初始安装源推荐由 Client 负责。 | | `ctx.pluginManager` | `core` | [`plugin-manager`](../packages/boot/plugin-manager) | - | [`plugin-manager`](../packages/boot/plugin-manager), `ui-settings-plugin-inventory` | - | 与 CLI 共享 profile 包操作,并向 Web 和 Agent 调用方分别报告持久状态与运行状态。 | | `ctx.profileContext` | `core` | [`app-boot`](../packages/boot/app-boot) | - | [`plugin-manager`](../packages/boot/plugin-manager) | - | dsh launcher 提供纯数据形式的 profile 位置与组合输入;重载调度由 dsh-hmr 负责。 | | `ctx.connection` | `core` | [`client-connection`](../packages/client/connection) | - | [`api-gateway`](../packages/api/gateway), [`host-frontend-static`](../packages/host/frontend-static) | - | 负责浏览器认证与共享 HTTP 请求分发;API 适配器注册端点和流。 | +| `ctx.worktrees` | `seam` | [`experimental-worktree`](../packages/experimental/worktree) | [`experimental-worktree`](../packages/experimental/worktree) | [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | - | 显式实验组合从固定的本地提交创建工作树。检出目录与共享 Git 元数据受现有写权限约束;工作目录服务拥有创建后的 Session 目录。 | +| `ctx.workingDirectory` | `seam` | [`working-directory`](../packages/session/working-directory) | [`working-directory`](../packages/session/working-directory) | [`tool-working-directory`](../packages/session/tool-working-directory), [`tool-fs`](../packages/fs/tool-fs), [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`subagent`](../packages/subagent/subagent), [`sdk-jsonrpc-server`](../packages/sdk/server) | - | 一个 Session 投影拥有有效执行目录。文件系统验证变更,用户上下文报告目录信息,原始元数据与写入授权保持不变。 | | `ctx.mcpResources` | `seam` | [`mcp-resources`](../packages/mcp/mcp-resources) | [`mcp-client`](../packages/mcp/mcp-client) | [`mcp-resources`](../packages/mcp/mcp-resources) | - | 连接所有者提供的操作在调用 agent 的作用域内服务于共享资源工具。 | | `ctx.browserUse` | `seam` | [`browser-use`](../packages/browser-use/browser-use) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp), [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp), [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | - | 每个服务实例注册一个提供方拥有的名称。提供方按实时 Session 拥有自己的工具与浏览器资源;共享服务不提供浏览器操作 API。 | | `ctx.computerUse` | `seam` | [`computer-use`](../packages/computer-use/computer-use) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp), [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | - | 每个服务实例只注册一个提供方自定的名称。各提供方也拥有自己的模型工具;服务不提供通用操作 API、运行时选择或 Session 流程锁。 | @@ -594,7 +613,7 @@ flowchart LR | `ctx.deepseekLlmApiExtensions` | `seam` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | [`session-log-deepseek`](../packages/session/session-log-deepseek), [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | [`llm-deepseek`](../packages/llm/llm-deepseek) | - | 插件准备彼此独立的顶层字段;官方适配器会合并这些字段,并在 HTTP 接受后提交其交付状态。 | | `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 拥有按会话隔离的回放折叠区;压力消费方共享不可变且带修订版本的测量结果。 | | `ctx.toolResultPruner` | `core` | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | - | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 在摘要压缩前,通过可回放的单节点表层替换来改写过大的当前工具结果。 | -| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`invariants`](../packages/runtime-diagnostics/invariants), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent`](../packages/subagent/subagent), [`message-feedback`](../packages/feedback/message-feedback) | - | 拥有仅追加的 Session 实例,并发出持久的会话事件流。 | | `ctx.speechController` | `core` | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | - | - | 在 Provider 调用前校验受限的浏览器音频。 | | `ctx.sessionController` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 负责 Session 命令、冷读取、持久事件跟随、实时控制状态、模型目录、workspace 打开与 Agent 激活策略。 | | `ctx.sessionFileReferences` | `core` | [`api-session-controller`](../packages/api/session-controller) | - | - | - | 通过 Session Controller 的既有 Agent lookup 策略委托文件引用发现。 | @@ -607,10 +626,9 @@ flowchart LR | `ctx.terminalController` | `core` | [`api-terminal-controller`](../packages/api/terminal-controller) | - | - | - | 通过子进程提供方与类型化 Remote 传输管理用户终端进程、解析默认 shell,并恢复有界终端屏幕。 | | `ctx.workspaceController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 通过生成的 Remote namespace 负责 Workspace 命令和可在重连后收敛的 Workspace 状态投递。 | | `ctx.directoryPickerController` | `core` | [`api-workspace-controller`](../packages/api/workspace-controller) | - | - | - | 把选目录 seam 送上线:能力门禁、取消传播,以及浏览器目录流程用于分支判断的 seam 错误码。 | -| `ctx.invariants` | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | 配套子路径注册所属包本地的检查;该服务负责选择、唯一性、子 fiber,以及标明所属包的失败。 | | `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | 插件直接或通过 dsh-typert-loader 注册实时 zod 贡献;API 网关消费调用描述符和提供方,其他运行时消费方则在各自边界查询 schema 与反射元数据。 | | `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | 将生成的 Remote 描述符与实时 Cordis 服务关联,解析已注册的身份,并通过共享的 Connection RPC 载体提供一元调用。 | -| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | JSONL backend 把 SessionEvent 词汇持久化为每个 Session 一份产物。 | +| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/shell/tool-bash), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`message-feedback`](../packages/feedback/message-feedback) | - | JSONL backend 把 SessionEvent 词汇持久化为每个 Session 一份产物。 | | `ctx.configEditor` | `core` | [`config-editor`](../packages/boot/config-editor) | - | [`settings`](../packages/settings/settings), [`agent-default-model`](../packages/core/agent-default-model) | - | Persists profile config patches under the application file lock and HMR queue, then reconciles Loader entries. | | `ctx.settings` | `core` | [`settings`](../packages/settings/settings) | - | [`api-settings-controller`](../packages/api/settings-controller) | - | Forms project volatile Config fields from active profile entries and delegate validated edits to config-editor. Plugins consume their own Config references. | | `ctx.subagentModelSelection` | `core` | [`tool-subagent`](../packages/subagent/tool-subagent) | - | [`tool-subagent`](../packages/subagent/tool-subagent) | - | 拥有默认关闭的设置命名空间;Agent 作用域的委派工具会在组合新顶层 Session 时读取它。 | @@ -626,29 +644,29 @@ flowchart LR | `ctx.messageFeedback` | `core` | [`message-feedback`](../packages/feedback/message-feedback) | - | - | - | 拥有权威 Session 日志中的逐 assistant 消息反馈、目标校验、逐条目 compare-and-set 及 Host 一元 Remote 契约。反馈不进入模型历史;日志导出遵循消费方策略。 | | `ctx.sessionFeedback` | `core` | [`command-feedback`](../packages/feedback/command-feedback) | - | - | - | 通过 Host 一元 Remote 契约在 live Session 上把一条带分类的 Session 级评价记录为仅写日志的 feedback/record 事件;/feedback 命令共用同一个生产方。 | | `ctx.workspaceRegistry` | `core` | [`workspace`](../packages/workspace/workspace) | - | [`api-workspace-controller`](../packages/api/workspace-controller), [`api-session-controller`](../packages/api/session-controller) | - | 通过领域设施拥有带 WorkspaceId 品牌类型的记录;稳定的 sessionIds 账户驱动 Host RPC 与 GUI 投影。 | -| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/session-query/tool-session-query) | - | 该接口提供精确读取、过滤和追踪;具体后端还提供全文协调、排序、摘要片段和游标世代,而模型消费方负责工作区权限与不含游标的渲染。 | +| `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/experimental/tool-session-query) | - | 该接口提供精确读取、过滤和追踪;具体后端还提供全文协调、排序、摘要片段和游标世代,而模型消费方负责工作区权限与不含游标的渲染。 | | `ctx.fileReferences` | `seam` | [`file-reference`](../packages/context/file-reference) | [`file-reference-local`](../packages/context/file-reference-local) | [`api-session-controller`](../packages/api/session-controller) | - | 该接口返回 Agent cwd 内仅含路径的补全候选;提供方负责命名空间访问与排序,但不读取文件内容。 | | `ctx.sessionReferenceResolver` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | 将当前表层中有界的对话快照投影为持久但不可信的消息上下文;Host 适配器负责提及语法。 | -| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm), [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | - | - | 负责确定性回退、最新标题折叠区,以及唯一的可选异步提供方注册。 | -| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-web`](../packages/web/tool-web) | - | 为每个步骤收集提示词各部分和面向模型的工具 schema。 | -| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | 注册能力,负责 PTC mode 传输,并让调用依次经过策略前处理、单调守卫、环绕分派、策略后处理和最终结果观测。 | +| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm), [`session-title-all-prompts-llm`](../packages/experimental/session-title-all-prompts-llm) | - | - | 负责确定性回退、最新标题折叠区,以及唯一的可选异步提供方注册。 | +| `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-web`](../packages/web/tool-web) | - | 为每个步骤收集提示词各部分和面向模型的工具 schema。 | +| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | 注册能力,负责 PTC mode 传输,并让调用依次经过策略前处理、单调守卫、环绕分派、策略后处理和最终结果观测。 | | `ctx.userQuestions` | `seam` | [`user-questions`](../packages/interaction/user-questions) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI 前端提供当前生效的人工回答提供方;tool-ask-user 在提供方无关的 ask() promise 上暂停工具调用。 | | `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | 折叠已记录的计划/模式状态,在轮次边界刷新用户选择,渲染由部署方拥有的指导信息,注册 /plan,并在状态转换期间保持计划退出 schema 稳定。 | | `ctx.agentPresets` | `core` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | - | - | - | 立即挂载 YAML 声明的 preset 版本,把 Agent 和冷读取绑定到作用域内的贡献,并保留已替换的版本,直到最后一个使用者释放它。 | | `ctx.commands` | `core` | [`commands`](../packages/interaction/commands) | - | - | - | 插件注册直接面向人的命令,而不会把调用发送给模型。 | | `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`api-session-controller`](../packages/api/session-controller), [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title) | - | 各领域注册由状态驱动的折叠单元;主动驱动过程维护每个会话的水位状态,Session controller 提供 baseline 并推送发生变化的值。 | | `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`api-session-controller`](../packages/api/session-controller), [`session-query`](../packages/session-query/session-query), [`session-reference`](../packages/context/session-reference) | - | 按会话持久保存投影单元状态的检查点(节流检查点,以及轮次/结束/分离时的必选检查点),提供缓存投影视图,并加速 prepared Session 的投影恢复。 | -| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl), [`skill-badge`](../packages/skill/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem), [`skill-office`](../packages/skill/skill-office) | [`tool-skill`](../packages/skill/tool-skill) | - | 合并提供方的 skill(技能)目录;tool-skill 渲染会话前缀目录,并加载完整的 skill 正文。 | -| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | - | 拥有实时 Agent 句柄、创建/恢复工厂 seam,以及进程本地的发起方传播。 | +| `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl), [`skill-badge`](../packages/experimental/skill-badge), [`skill-filesystem`](../packages/skill/skill-filesystem), [`skill-office`](../packages/skill/skill-office) | [`tool-skill`](../packages/skill/tool-skill) | - | 合并提供方的 skill(技能)目录;tool-skill 渲染会话前缀目录,并加载完整的 skill 正文。 | +| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent`](../packages/subagent/subagent) | - | 拥有实时 Agent 句柄、创建/恢复工厂 seam,以及进程本地的发起方传播。 | | `ctx.agentDefaultModel` | `core` | [`agent-default-model`](../packages/core/agent-default-model) | - | [`api-session-controller`](../packages/api/session-controller), [`headless`](../packages/bundle/headless) | - | Reads the default ModelSelection from volatile Config and saves selections through the profile editor. | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`base`](../packages/bundle/base), [`sdk-minimal`](../packages/bundle/sdk-minimal) | - | 唯一的具体循环插件;扩展包依赖 dsh-agent 的事件和服务,而不依赖此包。 | | `ctx.schedule` | `core` | [`schedule`](../packages/schedule/schedule) | - | - | - | 独立于 Session 的加载状态存储任务,并将到期消息排入原 Session。 | | `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | 从会话日志折叠带修订版本的目标状态,并将实时延续激活保留在进程本地。 | | `ctx.ssh` | `core` | [`ssh`](../packages/ssh/ssh) | - | [`fs-ssh`](../packages/ssh/fs-ssh), [`subprocess-ssh`](../packages/ssh/subprocess-ssh), [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | - | 负责一条经过认证的 OpenSSH 连接、已安装辅助程序身份、独立程序流,以及配套远端提供方的断连清理。 | | `ctx.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local), [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash), [`lsp-stdio`](../packages/lsp/lsp-stdio), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | - | Bash 执行器、PTY shell 后端、LSP Host,以及进程外 ACP、Codex 和 Claude Code subagent 后端都通过 ctx.subprocess 执行 spawn;该服务负责进程坐标、进程树/会话生命周期、stdio 处置、终端机制和 kill 升级。 | -| `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex) | - | 面向模型的 shell 工具和钩子桥接消费此 seam;沙箱、远程或 PowerShell 执行器可以替换 bash-local,而无需改动这些消费方。 | +| `ctx.shell` | `seam` | [`shell`](../packages/shell/shell) | [`bash-local`](../packages/shell/bash-local), [`bash-sandbox`](../packages/shell/bash-sandbox), [`pwsh-local`](../packages/shell/pwsh-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex) | - | 面向模型的 shell 工具和钩子桥接消费此 seam;沙箱、远程或 PowerShell 执行器可以替换 bash-local,而无需改动这些消费方。 | | `ctx.shellEnv` | `core` | [`shell-env`](../packages/shell/shell-env) | - | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh) | - | 插件声明限定于 effect 作用域的 DSH_* 事实;每个 shell 工具在每次执行时收集一份可信快照,其执行器据此重建命名空间。 | -| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/terminal/tool-terminal) | - | 注册表负责精确到 Agent 的会话身份和清理;后端负责终端机制,tool-terminal 则提供限定于所有者作用域的模型接口。 | +| `ctx.terminals` | `seam` | [`terminal`](../packages/terminal/terminal) | [`terminal-bash`](../packages/terminal/terminal-bash) | [`tool-terminal`](../packages/experimental/tool-terminal) | - | 注册表负责精确到 Agent 的会话身份和清理;后端负责终端机制,tool-terminal 则提供限定于所有者作用域的模型接口。 | | `ctx.sandbox` | `seam` | [`sandbox`](../packages/sandbox/sandbox) | [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | [`bash-sandbox`](../packages/shell/bash-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | 消费方交出即将执行 spawn 的确切 argv;与配套子进程提供方共享执行环境的后端按每次调用的策略包装该 argv,并报告强制执行情况。 | | `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/shell/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`terminal-bash`](../packages/terminal/terminal-bash) | - | 统一保存部署默认模式和工作区根目录;只有沙箱执行器和提供方读取该服务(工具层使用它同时导出的纯 `sandbox/mode` 折叠区)。两类强制执行组件都读取该服务,因此 bash 与 fs 不会限制到不同的根目录。 | | `ctx.approval` | `seam` | [`user-approval`](../packages/interaction/user-approval) | - | [`tools`](../packages/core/tools), [`tool-bash`](../packages/shell/tool-bash), [`acp`](../packages/acp/acp) | - | 一次性权限决策通过 `approval/request` waterfall(瀑布式事件)分派;回答方是监听器(即 ACP 为自身 agent 提供的桥接),没有回答方时以 `unavailable` 关闭失败。 | @@ -656,18 +674,19 @@ flowchart LR | `ctx.ptcRuntime` | `seam` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node), [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | [`tools`](../packages/core/tools), [`workflow-ptc`](../packages/workflow/workflow-ptc) | - | 使用 Host 提供的异步绑定运行程序;tools 负责 PTC 呈现,workflow-ptc 负责工作流编排。 | | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-ssh`](../packages/ssh/fs-ssh) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | tool-fs 通过 ctx.fs 执行读取/写入/编辑;fs-sandbox 按共享沙箱模式限制变更;fs-observation-policy 通过 fs/* 事件门禁贡献基于观测状态的检查。 | | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 | -| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 提供方实现传输;该服务还负责可选的、基于 Activation 的延续编排,tool-subagent 选择一次性或可延续委派,tool-subagent-control 传递后续消息,而 tool-ralph 要求一条全新的结构化输出路由。 | +| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | 提供方准备本地子级或执行外部任务;一个 activation 管理器持有两类生命周期。委派工具选择后端,控制工具投递本地后续消息,工作流等待 activation 结果。 | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | 路由显式选择的识别器;浏览器使用带认证的 Remote,并在提交前将转写保留在草稿中。 | | `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供模型控制工具。 | +| `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | 加载 defineMod 插件添加的模组,从 harness 扩展点触发其钩子链,并通过其 Remote 绘制提示框上方的横幅。 | | `ctx.inspector` | `core` | `inspector` | - | - | - | 负责 Worker 托管的 CDP target,以及独立于传输的 Host 和 Client observation 与 Cordis tree query API。 | -| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | 生产方(后台 bash/pwsh、PTY 发送和 subagent 委派)登记正在运行的工作;声明 record 的 job 还为非消费观察者流式提供原始输出;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 | +| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | 生产方(后台 bash/pwsh 与 PTY 发送)登记正在运行的工作;声明 record 的 job 还为非消费观察者流式提供原始输出;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 | | `ctx.web` | `seam` | [`web`](../packages/web/web) | [`web-search-exa`](../packages/web/web-search-exa), [`web-search-perplexity`](../packages/web/web-search-perplexity), [`web-search-deepseek`](../packages/web/web-search-deepseek), [`web-fetch-http`](../packages/web/web-fetch-http) | [`tool-web`](../packages/web/tool-web) | - | 搜索和抓取提供方注册到同一个 ctx.web seam;tool-web 负责稳定的面向模型名称。 | | `ctx.spillStore` | `seam` | [`spill`](../packages/spill/spill) | [`spill-local`](../packages/spill/spill-local) | [`spill-policy`](../packages/spill/spill-policy) | - | 后端保存过大的工具文本,并返回面向模型的定位信息和取回提示;spill-policy 是 tools/post-execute 消费方,负责决定何时 spill。 | | `ctx.directoryPicker` | `seam` | [`host-directory-picker`](../packages/host/directory-picker) | [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | [`api-workspace-controller`](../packages/api/workspace-controller) | - | 带判别标记的交互能力:原生后端在 Host 显示设备上打开一个操作系统选择器,浏览后端为应用内浏览器提供列表与创建原语;双端后端通过其浏览器侧填充 ui-workspace 目录流程的 slot(不通过协议发布)。 | | `ctx.webServer` | `core` | [`host-webserver`](../packages/host/webserver) | - | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`client-hmr`](../packages/client/hmr) | - | 普通的 node:http 载体:具名路由注册表、索引转换 tap,以及静态 dist 回退;Web 传输插件注册自己的路由。 | | `ctx.clientModules` | `core` | [`client-modules`](../packages/client/modules) | - | [`client-hmr`](../packages/client/hmr) | - | 通过增量 `dsh.client` 扫描组合 __DSH_BOOT__ 入口图,提供插件组合包,并通知重建/图变更订阅方。 | -| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/workflow/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 | -| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/webhook/webhook) | - | [`webhook-github`](../packages/webhook/webhook-github) | - | 提供方适配器分派已认证交付;可信插件注册独立的进程本地规则,runtime 把非 null 结果转换为普通的 Workspace-backed Session,不保留交付或完成状态。 | +| `ctx.workflowEngine` | `seam` | [`workflow`](../packages/workflow/workflow) | [`workflow-ptc`](../packages/workflow/workflow-ptc) | [`tool-workflow`](../packages/workflow/tool-workflow), [`tool-ralph`](../packages/experimental/tool-ralph) | - | 每个上下文使用一个引擎,与 bash 相同,且没有具名提供方注册表;通用工作流与固定 Ralph 消费方启动运行,其中的 agent() 调用通过 ctx.subagents 扇出。 | +| `ctx.webhookRuntime` | `core` | [`webhook`](../packages/experimental/webhook) | - | [`webhook-github`](../packages/experimental/webhook-github) | - | 提供方适配器分派已认证交付;可信插件注册独立的进程本地规则,runtime 把非 null 结果转换为普通的 Workspace-backed Session,不保留交付或完成状态。 | | `ctx.lsp` | `seam` | [`lsp`](../packages/lsp/lsp) | [`lsp-stdio`](../packages/lsp/lsp-stdio) | [`tool-lsp`](../packages/lsp/tool-lsp) | - | 提供方注册与选择,加上恰好四种操作的标准化查询执行;该 seam 不提供协议逃生口,后端必须转换为标准化请求和结果。 | | `ctx.dynamicCordisRunner` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 拥有内存定义注册表、Host 半的 vm 沙箱和 request-run 往返流程;浏览器页面通过其 Remote 命名空间在线访问同一服务。 | | `ctx.cordisInspect` | `core` | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | - | [`tool-cordis`](../packages/extensions/tool-cordis) | - | 注册 Host inspect 提供方、镜像 Client 提供方 manifest,并通过动态 Cordis 传输路由 Client 查询。 | diff --git a/docs/cli-help.i18n.yaml b/docs/cli-help.i18n.yaml new file mode 100644 index 0000000000..6445218133 --- /dev/null +++ b/docs/cli-help.i18n.yaml @@ -0,0 +1,34 @@ +# Bilingual-pair consistency record for cli-help.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/cli-help.md +/: + en: d11ca88bdc409249 + zh: d11ca88bdc409249 +/dsh-cli-help: + en: b7174eab3476579d + zh: 5ddab967320e3cff +/dsh-cli-help/contents: + en: 00308bcc6dc90afd + zh: 16f7442b76686a02 +/dsh-cli-help/dsh: + en: e0695abcf7d8b521 + zh: 712c23b3c8a115ca +/dsh-cli-help/dsh-plugin: + en: adad99c125d0ac51 + zh: f11024971184f737 +/dsh-cli-help/dsh-acp: + en: bcb0f84577ce5f03 + zh: 4382e617006849db +/dsh-cli-help/dsh-headless: + en: 878a1dd78017964d + zh: bb43d91b4beb9bda +/dsh-cli-help/dsh-sdk: + en: 701f239c5157dedb + zh: ee11be2932b05ce4 +/dsh-cli-help/dsh-sdk-minimal: + en: 8f0d7548d299ffd3 + zh: fa2262e70d6819a0 +/dsh-cli-help/dsh-web: + en: 5d073104e8954cc8 + zh: 91cd28f7c6ac96ac diff --git a/docs/cli-help.md b/docs/cli-help.md new file mode 100644 index 0000000000..4539636c4a --- /dev/null +++ b/docs/cli-help.md @@ -0,0 +1,194 @@ + + +# dsh CLI help + +This reference contains complete help for the launcher, subcommands, and shipped profiles. Identical help appears once with every invocation listed. Help output retains its original language. + +Run `pnpm run gen-cli-help` to regenerate; run `pnpm run verify-cli-help` to check freshness. + +## Contents + +- [`dsh`](#dsh) +- [`dsh plugin`](#dsh-plugin) +- [`dsh acp`](#dsh-acp) +- [`dsh headless`](#dsh-headless) +- [`dsh sdk`](#dsh-sdk) +- [`dsh sdk-minimal`](#dsh-sdk-minimal) +- [`dsh web`](#dsh-web) + + +## `dsh` + +Invocations: `dsh --help` + +```text +Usage: dsh [--profile] [options] [app-args...] + dsh plugin --profile + +dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch +layers under your own overrides. + +Arguments: + args arguments for the booted profile's app (see: + dsh --profile --help) + +Options: + -V, --version output the version number + --profile the profile under $DSH_HOME/profiles to boot + --from-default-profile initialize a new custom profile from a shipped + profile template + --patch extra patch-list overlay applied after the + profile layer (repeatable) + --dump-config print the composed profile tree and exit + --dump-config-schema print JSON Schema for profile entries and + patches without mounting + --dump-default-config print the profile tree without its user layer + or --patch overlays and exit + +Examples: + dsh web boot the web profile (same as: dsh --profile web) + dsh rescue --from-default-profile web + create rescue from the shipped web template, then boot it + dsh headless "run the tests" answer one task, print the result, and exit + dsh tui --patch ./extra.yml boot a custom profile with one extra overlay + dsh tui --resume arguments after the launcher flags reach the app + dsh web --help the web app's own flags and help + dsh plugin --profile tui add install a plugin into the tui profile + +``` + + +## `dsh plugin` + +Invocations: `dsh plugin --help` + +```text +Usage: dsh plugin [options] [args...] + +manage a profile's plugins by forwarding the remaining arguments to pnpm in the +profile directory + +Arguments: + args pnpm arguments, forwarded verbatim (add , remove , + why , ...) + +Options: + --profile the profile whose plugins to manage (initialized on first + use) + -h, --help show this help +``` + + +## `dsh acp` + +Invocations: `dsh acp --help`, `dsh --profile acp --help` + +```text +Usage: dsh --profile acp [options] + +Serve automation clients over Agent Client Protocol stdio. + +Options: + -h, --help show this help + +Example: + dsh --profile acp serve ACP until the client disconnects + +``` + + +## `dsh headless` + +Invocations: `dsh headless --help`, `dsh --profile headless --help` + +```text +Usage: dsh --profile headless [options] [task...] + +Answer one task and exit; the answer goes to stdout and diagnostics to stderr. + +Arguments: + task the task text; multiple words are joined by spaces, and `-` + reads stdin + +Options: + --json write newline-delimited run events to stdout instead of the + final message + --session-id adopt the persisted Session with this id; an unknown id is + an error + -h, --help show this help + +Examples: + dsh --profile headless "run the tests" answer one task and exit + echo "run the tests" | dsh --profile headless read the task from stdin + dsh --profile headless --json "run the tests" emit machine-readable run events + dsh --profile headless --session-id session-… "continue" resume an existing Session + +``` + + +## `dsh sdk` + +Invocations: `dsh sdk --help`, `dsh --profile sdk --help` + +```text +Usage: dsh --profile sdk [options] + +Serve DeepSeek Harness SDK clients over stdio JSON-RPC. + +Options: + -h, --help show this help + +Example: + dsh --profile sdk serve one SDK runtime until its client disconnects + +``` + + +## `dsh sdk-minimal` + +Invocations: `dsh sdk-minimal --help`, `dsh --profile sdk-minimal --help` + +```text +Usage: dsh --profile sdk-minimal [options] + +Serve DeepSeek Harness SDK clients over stdio JSON-RPC. + +Options: + -h, --help show this help + +Example: + dsh --profile sdk-minimal serve one SDK runtime until its client disconnects + +``` + + +## `dsh web` + +Invocations: `dsh web --help`, `dsh --profile web --help` + +```text +Usage: dsh --profile web [options] + +Serve the DeepSeek Harness browser UI. + +Options: + --host bind host + --no-open do not open the Web UI in the default browser + --port listen port; pass 0 to let the OS pick a free + one + --public-url advertise this HTTP(S) root in the printed, + opened, web-surface, and DSH_WEB_URL forms; + grants no trust + --trusted-host extra authority the /api browser-trust fence + accepts (host or host:port; repeatable) + -h, --help show this help + +Examples: + dsh --profile web serve on the composed host and port + dsh --profile web --no-open serve without opening a browser + dsh --profile web --port 8080 serve on another port + dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example + advertise a prefix-stripping HTTPS proxy entry and admit its authority + +``` diff --git a/docs/cli-help.zh.md b/docs/cli-help.zh.md new file mode 100644 index 0000000000..f875c988e1 --- /dev/null +++ b/docs/cli-help.zh.md @@ -0,0 +1,196 @@ + + +# dsh 命令行帮助 + +[English](cli-help.md) | 中文 + +此参考收录启动器、子命令和随附 profile 的完整帮助。同一份帮助只展示一次,并列出所有调用方式。帮助输出保留原文。 + +运行 `pnpm run gen-cli-help` 重新生成;运行 `pnpm run verify-cli-help` 检查是否与当前命令一致。 + +## 目录 + +- [`dsh`](#dsh) +- [`dsh plugin`](#dsh-plugin) +- [`dsh acp`](#dsh-acp) +- [`dsh headless`](#dsh-headless) +- [`dsh sdk`](#dsh-sdk) +- [`dsh sdk-minimal`](#dsh-sdk-minimal) +- [`dsh web`](#dsh-web) + + +## `dsh` + +调用方式: `dsh --help` + +```text +Usage: dsh [--profile] [options] [app-args...] + dsh plugin --profile + +dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch +layers under your own overrides. + +Arguments: + args arguments for the booted profile's app (see: + dsh --profile --help) + +Options: + -V, --version output the version number + --profile the profile under $DSH_HOME/profiles to boot + --from-default-profile initialize a new custom profile from a shipped + profile template + --patch extra patch-list overlay applied after the + profile layer (repeatable) + --dump-config print the composed profile tree and exit + --dump-config-schema print JSON Schema for profile entries and + patches without mounting + --dump-default-config print the profile tree without its user layer + or --patch overlays and exit + +Examples: + dsh web boot the web profile (same as: dsh --profile web) + dsh rescue --from-default-profile web + create rescue from the shipped web template, then boot it + dsh headless "run the tests" answer one task, print the result, and exit + dsh tui --patch ./extra.yml boot a custom profile with one extra overlay + dsh tui --resume arguments after the launcher flags reach the app + dsh web --help the web app's own flags and help + dsh plugin --profile tui add install a plugin into the tui profile + +``` + + +## `dsh plugin` + +调用方式: `dsh plugin --help` + +```text +Usage: dsh plugin [options] [args...] + +manage a profile's plugins by forwarding the remaining arguments to pnpm in the +profile directory + +Arguments: + args pnpm arguments, forwarded verbatim (add , remove , + why , ...) + +Options: + --profile the profile whose plugins to manage (initialized on first + use) + -h, --help show this help +``` + + +## `dsh acp` + +调用方式: `dsh acp --help`, `dsh --profile acp --help` + +```text +Usage: dsh --profile acp [options] + +Serve automation clients over Agent Client Protocol stdio. + +Options: + -h, --help show this help + +Example: + dsh --profile acp serve ACP until the client disconnects + +``` + + +## `dsh headless` + +调用方式: `dsh headless --help`, `dsh --profile headless --help` + +```text +Usage: dsh --profile headless [options] [task...] + +Answer one task and exit; the answer goes to stdout and diagnostics to stderr. + +Arguments: + task the task text; multiple words are joined by spaces, and `-` + reads stdin + +Options: + --json write newline-delimited run events to stdout instead of the + final message + --session-id adopt the persisted Session with this id; an unknown id is + an error + -h, --help show this help + +Examples: + dsh --profile headless "run the tests" answer one task and exit + echo "run the tests" | dsh --profile headless read the task from stdin + dsh --profile headless --json "run the tests" emit machine-readable run events + dsh --profile headless --session-id session-… "continue" resume an existing Session + +``` + + +## `dsh sdk` + +调用方式: `dsh sdk --help`, `dsh --profile sdk --help` + +```text +Usage: dsh --profile sdk [options] + +Serve DeepSeek Harness SDK clients over stdio JSON-RPC. + +Options: + -h, --help show this help + +Example: + dsh --profile sdk serve one SDK runtime until its client disconnects + +``` + + +## `dsh sdk-minimal` + +调用方式: `dsh sdk-minimal --help`, `dsh --profile sdk-minimal --help` + +```text +Usage: dsh --profile sdk-minimal [options] + +Serve DeepSeek Harness SDK clients over stdio JSON-RPC. + +Options: + -h, --help show this help + +Example: + dsh --profile sdk-minimal serve one SDK runtime until its client disconnects + +``` + + +## `dsh web` + +调用方式: `dsh web --help`, `dsh --profile web --help` + +```text +Usage: dsh --profile web [options] + +Serve the DeepSeek Harness browser UI. + +Options: + --host bind host + --no-open do not open the Web UI in the default browser + --port listen port; pass 0 to let the OS pick a free + one + --public-url advertise this HTTP(S) root in the printed, + opened, web-surface, and DSH_WEB_URL forms; + grants no trust + --trusted-host extra authority the /api browser-trust fence + accepts (host or host:port; repeatable) + -h, --help show this help + +Examples: + dsh --profile web serve on the composed host and port + dsh --profile web --no-open serve without opening a browser + dsh --profile web --port 8080 serve on another port + dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example + advertise a prefix-stripping HTTPS proxy entry and admit its authority + +``` diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 4cd90d0768..5863ced6c1 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -59,15 +59,13 @@ export interface Config { ## `@deepseek-ai/dsh-agent-instructions` -- `inject`: `sessionProjections` +- `inject`: `sessionProjections` · `workingDirectory` - `source`: [`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts) ```ts config-catalog /** User-facing workspace instruction loader configuration. */ export interface Config { - /** Harness home containing the fixed user-global `AGENTS.md`; defaults to `$DSH_HOME` or `~/.dsh`. */ - dshHome?: string - /** Directory entries that identify the project root while walking upward from the session cwd. */ + /** Directory entries that identify the project root while walking upward from the current working directory. */ projectRootMarkers?: string[] /** UTF-8 byte cap for one rendered baseline or dynamic batch; non-positive or non-finite disables loading. */ maxBytes: number @@ -141,7 +139,7 @@ export type Config = PresetDefinition - `inject`: `loader` · `sessionProjections` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:13`](../packages/preset/agent-preset-registry/src/preset.ts) +- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:15`](../packages/preset/agent-preset-registry/src/preset.ts) ```ts config-catalog /** Registry selection policy. */ @@ -168,7 +166,7 @@ export interface Config { export interface Config { /** * The form this agent's model sees. `native` sends every visible schema, - * `ptc` sends only `run_code` plus a generated SDK, `both` sends both. + * `ptc` sends only `run_code` plus a generated SDK. * Required rather than defaulted: the deployment default is what a preset * without this row already gets, so an omitted value would mean the row was * composed for nothing. @@ -229,6 +227,8 @@ export interface Config { export interface Config { /** Override platform desktop-opener detection. */ readonly nativeOpen?: boolean + /** Positive integral milliseconds of list work before yielding between complete rows. */ + readonly listWorkSliceMs?: number } ``` @@ -254,8 +254,8 @@ export interface SettingsControllerInternals { ## `@deepseek-ai/dsh-api-terminal-controller` -- `inject`: `subprocess` · `sandboxPolicy` · `typert` -- `source`: [`packages/api/terminal-controller/src/index.ts:26`](../packages/api/terminal-controller/src/index.ts) +- `inject`: `subprocess` · `sandboxPolicy` · `workingDirectory` · `typert` +- `source`: [`packages/api/terminal-controller/src/index.ts:27`](../packages/api/terminal-controller/src/index.ts) ```ts config-catalog /** Deployment limits and an optional shell profile. */ @@ -872,9 +872,9 @@ export interface Config { ## `@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp` -- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` +- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` · `workingDirectory` - `refs`: `BrowserMcpConfig` (`@deepseek-ai/dsh-experimental-browser-use-runtime/mcp`) -- `source`: [`packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts:14`](../packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts) +- `source`: [`packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts:15`](../packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts) ```ts config-catalog /** Fixed Chromium launch or existing-browser attachment settings. */ @@ -887,9 +887,9 @@ export type Config = BrowserMcpConfig ## `@deepseek-ai/dsh-experimental-browser-use-playwright-mcp` -- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` +- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` · `workingDirectory` - `refs`: `BrowserMcpConfig` (`@deepseek-ai/dsh-experimental-browser-use-runtime/mcp`) -- `source`: [`packages/experimental/browser-use-playwright-mcp/src/index.ts:15`](../packages/experimental/browser-use-playwright-mcp/src/index.ts) +- `source`: [`packages/experimental/browser-use-playwright-mcp/src/index.ts:16`](../packages/experimental/browser-use-playwright-mcp/src/index.ts) ```ts config-catalog /** Fixed Chromium launch or existing-browser attachment settings. */ @@ -939,6 +939,61 @@ export interface StagehandModelConfig { ``` + + + +## `@deepseek-ai/dsh-experimental-claude-code-mods` + +- `inject`: `workingDirectory` +- `source`: [`packages/experimental/claude-code-mods/src/index.ts:53`](../packages/experimental/claude-code-mods/src/index.ts) + +```ts config-catalog +/** Plugin config: the limits mod hooks run under. */ +export interface Config { + /** A hook's own running-time limit in milliseconds (Claude Code: 10 seconds). */ + hookTimeoutMs?: number + /** A `.catch` handler's running-time limit in milliseconds (Claude Code: 1 second). */ + catchTimeoutMs?: number + /** Default `$.process.run` and `$.http.fetch` timeout in milliseconds (Claude Code: 30 seconds). */ + processTimeoutMs?: number + /** Claude Code tool name → harness tool name entries added to the built-in alias table. */ + toolAliases?: Record + /** Columns the band above the prompt reports to `ui.render` as `bodyColumns` and `viewport.columns`. */ + bandColumns?: number + /** Rows the band reports as `maxRows`. */ + bandRows?: number +} +``` + + + + + +## `@deepseek-ai/dsh-experimental-client-ui-cot-translation` + +- `inject`: `translator` · `typert` · `sessionController` +- `refs`: [`TranslationProvider`](subsystems/translation.md) · `Volatile` (`@deepseek-ai/cordis`) +- `source`: [`packages/experimental/client-ui-cot-translation/src/index.ts:14`](../packages/experimental/client-ui-cot-translation/src/index.ts) + +```ts config-catalog +/** Live preferences presented on the bundle's Plugins page. */ +export interface Config { + /** Explicit translation provider, defaulting to Bing. */ + provider: Volatile + /** Target language code; auto follows the browser UI locale. */ + targetLanguage: Volatile +} + +/** Translation choices; auto targets the active browser UI language. */ +export interface CotTranslationPreferences { + /** Explicit provider selection; requests never fall back to another provider. */ + provider: TranslationProvider + /** BCP 47 language code, or auto to follow the UI language. */ + targetLanguage: string +} +``` + + @@ -968,7 +1023,7 @@ export interface Config { ## `@deepseek-ai/dsh-experimental-inspector` -- `inject`: `webServer` +- `inject`: `webServer` · `connection` - `source`: [`packages/experimental/inspector/src/index.ts:66`](../packages/experimental/inspector/src/index.ts) ```ts config-catalog @@ -1037,7 +1092,8 @@ export interface InspectorOptions { ## `@deepseek-ai/dsh-experimental-ptc-runtime-python` -- `source`: [`packages/experimental/ptc-runtime-python/src/index.ts:42`](../packages/experimental/ptc-runtime-python/src/index.ts) +- `inject`: `sandbox` · `sandboxPolicy` +- `source`: [`packages/experimental/ptc-runtime-python/src/index.ts:45`](../packages/experimental/ptc-runtime-python/src/index.ts) ```ts config-catalog /** Plugin config: every cap, changeable from `cordis.yml` (no hardcoded tunables). */ @@ -1197,13 +1253,81 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-experimental-translator` + +- `source`: [`packages/experimental/translator/src/index.ts:25`](../packages/experimental/translator/src/index.ts) + +```ts config-catalog +/** Routing and bounds for anonymous requests and independent native model translation. */ +export interface Config { + /** Provider selected when a consumer omits it. */ + provider: TranslationProvider + /** Google-compatible anonymous translation endpoint. */ + googleEndpoint: string + /** Bing-compatible Microsoft Edge browser translation endpoint. */ + bingEndpoint: string + /** Deadline covering the request and complete response body, in milliseconds. */ + timeoutMs: number + /** Maximum UTF-16 code units per request; at least two to admit one supplementary-plane character. */ + maxTextChars: number + /** Maximum anonymous JSON-body bytes or paid assembled UTF-8 translation bytes. */ + maxResponseBytes: number + /** Deadline for native admission and complete translation output, in milliseconds. */ + deepseekTimeoutMs: number + /** Requested output-token cap for independent native Flash translation. */ + deepseekMaxOutputTokens: number +} + +/** Explicit anonymous endpoint or native paid model route. */ +export type TranslationProvider = AnonymousTranslationProvider | PaidTranslationProvider + +/** Anonymous browser endpoint selected for one translation. */ +export type AnonymousTranslationProvider = 'google' | 'bing' + +/** Explicit native DeepSeek credential route selected for paid translation. */ +export type PaidTranslationProvider = 'deepseek-account' | 'deepseek-official' +``` + + + + + +## `@deepseek-ai/dsh-experimental-worktree` + +- `inject`: `workingDirectory` · `fs` · `subprocess` · `sandbox` · `sandboxPolicy` +- `source`: [`packages/experimental/worktree/src/index.ts:19`](../packages/experimental/worktree/src/index.ts) + +```ts config-catalog +/** Worktree location, generated names, executables, and subprocess resource limits. */ +export interface Config { + /** Relative checkout pool inside the source repository. */ + directory?: string + /** Prefix for automatically generated branch and checkout names. */ + namePrefix?: string + /** Git 2.45 or newer executable name or absolute execution-world path. */ + gitCommand?: string + /** Node executable used for sandboxed directory allocation. */ + nodeCommand?: string + /** Deadline in milliseconds for one command. */ + timeoutMs?: number + /** Subprocess termination grace in milliseconds. */ + graceMs?: number + /** Maximum captured bytes per subprocess output stream. */ + maxOutputBytes?: number +} +``` + + ## `@deepseek-ai/dsh-file-reference-local` -- `inject`: `agents` -- `source`: [`packages/context/file-reference-local/src/index.ts:34`](../packages/context/file-reference-local/src/index.ts) +- `inject`: `agents` · `workingDirectory` +- `source`: [`packages/context/file-reference-local/src/index.ts:35`](../packages/context/file-reference-local/src/index.ts) ```ts config-catalog /** Local file-reference discovery configuration. */ @@ -1282,7 +1406,7 @@ export interface Config { ## `@deepseek-ai/dsh-headless` - `inject`: `agentDefaultModel` · `agents` · `sessions` -- `source`: [`packages/bundle/headless/src/index.ts:42`](../packages/bundle/headless/src/index.ts) +- `source`: [`packages/bundle/headless/src/index.ts:43`](../packages/bundle/headless/src/index.ts) ```ts config-catalog /** Plugin config: the task and run options resolved from this app's injected provider service. */ @@ -1303,7 +1427,7 @@ export interface Config { ## `@deepseek-ai/dsh-hmr` - `refs`: `ChokidarOptions` (`chokidar`) -- `source`: [`packages/boot/hmr/src/index.ts:51`](../packages/boot/hmr/src/index.ts) +- `source`: [`packages/boot/hmr/src/index.ts:53`](../packages/boot/hmr/src/index.ts) ```ts config-catalog /** Module roots and watcher timing, with Chokidar deployment options. */ @@ -1325,8 +1449,8 @@ export interface HmrConfig extends ChokidarOptions { ## `@deepseek-ai/dsh-hooks-claude-code` -- `inject`: `shell` · `sessionProjections` -- `source`: [`packages/hooks/hooks-claude-code/src/index.ts:51`](../packages/hooks/hooks-claude-code/src/index.ts) +- `inject`: `shell` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/experimental/hooks-claude-code/src/index.ts:51`](../packages/experimental/hooks-claude-code/src/index.ts) ```ts config-catalog /** Plugin config: where the CC hook config lives + substitution roots. */ @@ -1346,7 +1470,7 @@ export interface Config { /** * Replaces `${CLAUDE_PROJECT_DIR}` in command strings AND is exported as the * `CLAUDE_PROJECT_DIR` env var for hook processes. When omitted, the env var - * defaults per-run to the agent's session workspace (`session.header.cwd`, the + * defaults per-run to the agent's current directory (the * same dir the hook runs in) — Claude Code always exports this var, and common * unmodified hooks reference `$CLAUDE_PROJECT_DIR` for project-relative paths. */ @@ -1364,8 +1488,8 @@ export interface Config { ## `@deepseek-ai/dsh-hooks-codex` -- `inject`: `shell` · `sessionProjections` -- `source`: [`packages/hooks/hooks-codex/src/index.ts:50`](../packages/hooks/hooks-codex/src/index.ts) +- `inject`: `shell` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/experimental/hooks-codex/src/index.ts:50`](../packages/experimental/hooks-codex/src/index.ts) ```ts config-catalog /** Plugin config: where the Codex hooks.json lives + the model name for payloads. */ @@ -1513,26 +1637,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-invariants` - -- `source`: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) - -```ts config-catalog -/** Runtime invariant selection configured on the service plugin. */ -export interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - - @@ -1936,7 +2040,7 @@ export interface ReplayModelConfig { * no image pricing. */ imageRequestTokens?: number - /** Optional reasoning-effort ids the replay route accepts, in display order. */ + /** Optional reasoning-effort ids from least to greatest selectable effort. */ reasoningEfforts?: string[] /** * Optional effort materialized when callers omit one; must appear in @@ -2231,7 +2335,7 @@ export interface Config { ## `@deepseek-ai/dsh-plan-mode` - `inject`: `tools` · `systemPrompt` · `sessionProjections` -- `source`: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) +- `source`: [`packages/plan/plan-mode/src/index.ts:69`](../packages/plan/plan-mode/src/index.ts) ```ts config-catalog /** Deployment-owned plan guidance. */ @@ -2248,7 +2352,7 @@ export interface PlanModeConfig { ## `@deepseek-ai/dsh-plugin-manager` - `inject`: `loader` · `profileContext` -- `source`: [`packages/boot/plugin-manager/src/index.ts:40`](../packages/boot/plugin-manager/src/index.ts) +- `source`: [`packages/boot/plugin-manager/src/index.ts:44`](../packages/boot/plugin-manager/src/index.ts) ```ts config-catalog /** The pnpm executable, registries, and limits for diagnostics, lookups and connection checks. */ @@ -2534,9 +2638,9 @@ export interface Config { ## `@deepseek-ai/dsh-sdk-jsonrpc-server` -- `inject`: `agents` +- `inject`: `agents` · `workingDirectory` - `refs`: `Readable` (`node:stream`) · `Writable` (`node:stream`) -- `source`: [`packages/sdk/server/src/index.ts:25`](../packages/sdk/server/src/index.ts) +- `source`: [`packages/sdk/server/src/index.ts:24`](../packages/sdk/server/src/index.ts) ```ts config-catalog /** JSON-RPC deployment config plus runtime-only test hooks. */ @@ -2726,8 +2830,8 @@ export interface Config { ## `@deepseek-ai/dsh-session-telemetry-otel` - `inject`: `sessions` · `otel` -- `refs`: `BatchLogRecordProcessorOptions` (`@opentelemetry/sdk-logs`) · `OTLPExporterNodeConfigBase` (`@opentelemetry/otlp-exporter-base`) -- `source`: [`packages/session/session-telemetry-otel/src/index.ts:87`](../packages/session/session-telemetry-otel/src/index.ts) +- `refs`: [`LogExporterOptions`](../packages/telemetry/otel/src/index.ts) · [`SessionLogProcessorOptions`](../packages/telemetry/otel/src/index.ts) +- `source`: [`packages/session/session-telemetry-otel/src/index.ts:85`](../packages/session/session-telemetry-otel/src/index.ts) ```ts config-catalog /** @@ -2741,16 +2845,18 @@ export interface Config { /** * Explicit SDK HTTP transport settings, including optional routing headers. * Ambient credentials are not inherited. URL is required while uploading. + * Exporter self-observability metering is not supported. */ - exporter?: OTLPExporterNodeConfigBase & { + exporter?: Omit & { /** Full logs endpoint (e.g. `https://collector.example.com/v1/logs`). Required outside `DISABLED`; validated at load. */ url?: string } /** * Count, queue, cadence, and per-request watchdog settings for the byte-bounded * processor. A watchdog warning never releases an unsettled transport slot. + * SDK processor self-observability metering is not supported. */ - processor?: Omit + processor?: SessionLogProcessorOptions /** Maximum time spent awaiting the SDK provider's complete shutdown path. */ shutdownTimeoutMillis?: number /** Uncompressed OTLP request byte limit, at most 4,000,000. */ @@ -2793,7 +2899,7 @@ export interface Config { - `inject`: `sessionTitle` · `llm` · `sessions` - `refs`: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/index.ts) -- `source`: [`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts) +- `source`: [`packages/experimental/session-title-all-prompts-llm/src/index.ts:15`](../packages/experimental/session-title-all-prompts-llm/src/index.ts) ```ts config-catalog /** Required LLM policy; this plugin adds no defaults. */ @@ -2854,7 +2960,7 @@ export interface Config { ## `@deepseek-ai/dsh-skill-filesystem` - `inject`: `skills` -- `source`: [`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts) +- `source`: [`packages/skill/skill-filesystem/src/index.ts:48`](../packages/skill/skill-filesystem/src/index.ts) ```ts config-catalog /** Local filesystem skill provider configuration. */ @@ -3084,8 +3190,9 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ## `@deepseek-ai/dsh-subagent` +- `inject`: `workingDirectory` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/subagent/subagent/src/index.ts:192`](../packages/subagent/subagent/src/index.ts) +- `source`: [`packages/subagent/subagent/src/index.ts:184`](../packages/subagent/subagent/src/index.ts) ```ts config-catalog /** Host configuration for continuable subagent capacity. */ @@ -3104,7 +3211,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-acp` - `inject`: `subagents` · `subprocess` -- `source`: [`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts) +- `source`: [`packages/subagent/subagent-acp/src/index.ts:25`](../packages/subagent/subagent-acp/src/index.ts) ```ts config-catalog /** Config: how to spawn and drive the child ACP agent process. */ @@ -3115,14 +3222,6 @@ export interface Config { command: string /** Arguments passed to {@link command}. */ args: string[] - /** - * Working directory override for the child process and its ACP session. - * Must be non-empty; a relative path resolves against the harness launch - * directory at load, and the result must be an existing directory. When - * omitted, each child inherits its delegating parent session's cwd — and - * starting one from a parent session that has no cwd fails. - */ - cwd?: string /** * How to auto-answer the child's `session/request_permission` prompts: * `reject` (default — decline every prompt) or `allow` (approve via the first @@ -3243,15 +3342,6 @@ export interface Config { patches: string[] /** Absolute isolated Harness home for every nested child process. */ dshHome: string - /** - * Working directory override for the child process and its SDK session - * workspace. Must be non-empty; a relative path resolves against the - * harness launch directory at load, and the result must be an existing - * directory. When omitted, each child inherits its delegating parent - * session's cwd — and starting one from a parent session that has no cwd - * fails. - */ - cwd?: string /** Provider route the child runtime initializes with (default `deepseek-official`). */ provider: string /** Model the child runtime initializes with (default `deepseek-v4-flash`). */ @@ -3285,7 +3375,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-fork-in-process` - `inject`: `subagents` -- `source`: [`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts) +- `source`: [`packages/subagent/subagent-fork-in-process/src/index.ts:29`](../packages/subagent/subagent-fork-in-process/src/index.ts) ```ts config-catalog /** Config: the registry name to register the provider under. */ @@ -3302,7 +3392,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-spawn-in-process` - `inject`: `subagents` -- `source`: [`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) +- `source`: [`packages/subagent/subagent-spawn-in-process/src/index.ts:23`](../packages/subagent/subagent-spawn-in-process/src/index.ts) ```ts config-catalog /** Config: the registry name to register the provider under. */ @@ -3318,14 +3408,14 @@ export interface Config { ## `@deepseek-ai/dsh-system-prompt` -- `source`: [`packages/core/system-prompt/src/index.ts:247`](../packages/core/system-prompt/src/index.ts) +- `source`: [`packages/core/system-prompt/src/index.ts:254`](../packages/core/system-prompt/src/index.ts) ```ts config-catalog /** Plugin config: the deployment-authored fragment of the system prompt (see {@link Config.personaPrefix} for its contract). */ export interface Config { /** Include the fixed DeepSeek Harness identity before the deployment persona (default true). */ includeHarnessIdentity?: boolean - /** Include dynamic runtime-context snapshots in model history (default true). */ + /** Include optional runtime-context snapshots in model history (default true); required context remains. */ includeRuntimeContext?: boolean /** * Deployment-wide persona prefix template before first-party guidance. A scoped section named @@ -3352,7 +3442,7 @@ export interface Config { ## `@deepseek-ai/dsh-terminal-bash` -- `inject`: `terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess` +- `inject`: `terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess` · `workingDirectory` - `source`: [`packages/terminal/terminal-bash/src/config.ts:10`](../packages/terminal/terminal-bash/src/config.ts) ```ts config-catalog @@ -3481,7 +3571,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-bash` -- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` +- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` · `workingDirectory` - `source`: [`packages/shell/tool-bash/src/index.ts:37`](../packages/shell/tool-bash/src/index.ts) ```ts config-catalog @@ -3511,8 +3601,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-bash-persistent` -- `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-bash-persistent/src/index.ts:444`](../packages/shell/tool-bash-persistent/src/index.ts) +- `inject`: `tools` · `terminals` · `workingDirectory` +- `source`: [`packages/shell/tool-bash-persistent/src/index.ts:445`](../packages/shell/tool-bash-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent Bash tool. */ @@ -3534,8 +3624,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-fs` -- `inject`: `tools` · `fs` · `systemPrompt` -- `source`: [`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts) +- `inject`: `tools` · `fs` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/fs/tool-fs/src/index.ts:26`](../packages/fs/tool-fs/src/index.ts) ```ts config-catalog /** Plugin config (all optional — `Config` supplies the defaults). */ @@ -3557,8 +3647,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-fs-search` -- `inject`: `tools` · `systemPrompt` · `subprocess` -- `source`: [`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts) +- `inject`: `tools` · `systemPrompt` · `subprocess` · `workingDirectory` +- `source`: [`packages/fs/tool-fs-search/src/index.ts:74`](../packages/fs/tool-fs-search/src/index.ts) ```ts config-catalog /** Plugin config; over-cap glob sampling is an explicit deployment choice and the remaining fields have defaults. */ @@ -3647,8 +3737,8 @@ export type CompletionDelivery = 'quiet' | 'wakeup' ## `@deepseek-ai/dsh-tool-lsp` -- `inject`: `tools` · `lsp` · `systemPrompt` -- `source`: [`packages/lsp/tool-lsp/src/index.ts:57`](../packages/lsp/tool-lsp/src/index.ts) +- `inject`: `tools` · `lsp` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/lsp/tool-lsp/src/index.ts:56`](../packages/lsp/tool-lsp/src/index.ts) ```ts config-catalog /** Plugin configuration: result caps and the timeout budget. */ @@ -3668,8 +3758,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-present` -- `inject`: `tools` · `fs` · `sessionProjections` -- `source`: [`packages/deliverables/tool-present/src/index.ts:15`](../packages/deliverables/tool-present/src/index.ts) +- `inject`: `tools` · `fs` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/deliverables/tool-present/src/index.ts:16`](../packages/deliverables/tool-present/src/index.ts) ```ts config-catalog /** Per-call delivery limit. */ @@ -3685,7 +3775,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh` -- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` +- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` · `workingDirectory` - `source`: [`packages/shell/tool-pwsh/src/index.ts:54`](../packages/shell/tool-pwsh/src/index.ts) ```ts config-catalog @@ -3715,8 +3805,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh-persistent` -- `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:457`](../packages/shell/tool-pwsh-persistent/src/index.ts) +- `inject`: `tools` · `terminals` · `workingDirectory` +- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:458`](../packages/shell/tool-pwsh-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent pwsh tool. */ @@ -3739,7 +3829,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-ralph` - `inject`: `tools` · `workflowEngine` · `subagents` · `systemPrompt` -- `source`: [`packages/workflow/tool-ralph/src/index.ts:21`](../packages/workflow/tool-ralph/src/index.ts) +- `source`: [`packages/experimental/tool-ralph/src/index.ts:21`](../packages/experimental/tool-ralph/src/index.ts) ```ts config-catalog /** Deployment policy for the fixed Ralph workflow. */ @@ -3762,7 +3852,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-session-query` - `inject`: `tools` · `systemPrompt` · `sessionQuery` · `sessionProjections` -- `source`: [`packages/session-query/tool-session-query/src/index.ts:28`](../packages/session-query/tool-session-query/src/index.ts) +- `source`: [`packages/experimental/tool-session-query/src/index.ts:28`](../packages/experimental/tool-session-query/src/index.ts) ```ts config-catalog /** Deployment-owned search count and timeout bounds. */ @@ -3780,8 +3870,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-skill` -- `inject`: `agents` · `tools` · `skills` -- `source`: [`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts) +- `inject`: `agents` · `tools` · `skills` · `workingDirectory` +- `source`: [`packages/skill/tool-skill/src/index.ts:62`](../packages/skill/tool-skill/src/index.ts) ```ts config-catalog /** Model-facing skill catalog configuration. */ @@ -3835,18 +3925,6 @@ export interface Config { * Session and inherit that decision in its child Sessions. */ modelSelectionSettings?: boolean - /** - * Expose `run_in_background` (default true). Disabled instances omit the - * parameter and reject forced background calls. - */ - enableRunInBackground?: boolean - /** - * Background execution policy (default `one-shot`). `one-shot` defaults calls - * to foreground; `continuable` defaults them to background, requires a provider - * with the `prepareContinuable` capability, and returns the durable child id. - * Follow-up adapters remain independently optional. - */ - backgroundMode?: 'one-shot' | 'continuable' /** * Agent options applied to every child; omitted fields use child-loop defaults. */ @@ -3887,8 +3965,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-terminal` -- `inject`: `terminals` · `tools` · `systemPrompt` -- `source`: [`packages/terminal/tool-terminal/src/index.ts:36`](../packages/terminal/tool-terminal/src/index.ts) +- `inject`: `terminals` · `tools` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/experimental/tool-terminal/src/index.ts:38`](../packages/experimental/tool-terminal/src/index.ts) ```ts config-catalog /** Model-facing terminal tool configuration. */ @@ -4009,7 +4087,7 @@ export interface Config { ## `@deepseek-ai/dsh-tools` - `inject`: `systemPrompt` -- `source`: [`packages/core/tools/src/index.ts:674`](../packages/core/tools/src/index.ts) +- `source`: [`packages/core/tools/src/index.ts:675`](../packages/core/tools/src/index.ts) ```ts config-catalog /** Plugin config: how the registered tools are presented to the model. */ @@ -4018,9 +4096,9 @@ export interface Config { * Model presentation. `native` (default) sends every visible schema; `ptc` * sends only `run_code` plus a generated SDK prompt and collapses the * executor to the same surface (a model-direct call may only name - * `run_code`; `run_code` SDK sub-dispatches keep every visible tool); `both` - * sends both forms. PTC mode requires a `ctx.ptcRuntime` whose `language` - * has a registered SDK renderer (TypeScript or Python) and fail prompt + * `run_code`; `run_code` SDK sub-dispatches keep every visible tool). + * PTC mode requires a `ctx.ptcRuntime` whose `language` + * has a registered SDK renderer (TypeScript or Python) and fails prompt * assembly when it is absent or has no renderer. Under `ptc`, native names * in `toolOrder` are invalid. */ @@ -4036,7 +4114,7 @@ export interface Config { } /** How the registry presents its tools to the model (see {@link Config.mode}). */ -export type ToolPresentationMode = 'native' | 'ptc' | 'both' +export type ToolPresentationMode = 'native' | 'ptc' ``` @@ -4119,7 +4197,7 @@ export interface WebRuntimeConfig { ## `@deepseek-ai/dsh-web-app` - `inject`: `webServer` -- `source`: [`packages/bundle/web-app/src/index.ts:44`](../packages/bundle/web-app/src/index.ts) +- `source`: [`packages/bundle/web-app/src/index.ts:46`](../packages/bundle/web-app/src/index.ts) ```ts config-catalog /** Plugin config: composed deployment settings plus per-invocation command-line values. */ @@ -4135,6 +4213,14 @@ export interface Config { * orientation text would be false. */ surfaceContext: boolean + /** + * Canonical HTTP(S) root to advertise in the printed and opened URL, + * `DSH_WEB_URL`, and the web-surface orientation, e.g. + * `https://app.example/ui/`, normalized to end in `/`. Advertisement only; + * see [public deployments](../README.md#public-deployments). Absent or YAML + * `null` advertises the loopback URL. + */ + publicUrl?: string /** Explicit `--trusted-host` authorities from this invocation. */ trustedHosts: string[] } @@ -4252,7 +4338,7 @@ export interface Config { ## `@deepseek-ai/dsh-webhook-github` - `inject`: `webServer` · `webhookRuntime` · `credentials` -- `source`: [`packages/webhook/webhook-github/src/index.ts:17`](../packages/webhook/webhook-github/src/index.ts) +- `source`: [`packages/experimental/webhook-github/src/index.ts:17`](../packages/experimental/webhook-github/src/index.ts) ```ts config-catalog /** Required GitHub ingress configuration. */ @@ -4274,8 +4360,8 @@ export interface Config { ## `@deepseek-ai/dsh-workflow-ptc` -- `inject`: `subagents` · `ptcRuntime` · `sandboxPolicy` -- `source`: [`packages/workflow/workflow-ptc/src/index.ts:32`](../packages/workflow/workflow-ptc/src/index.ts) +- `inject`: `subagents` · `ptcRuntime` · `sandboxPolicy` · `workingDirectory` +- `source`: [`packages/workflow/workflow-ptc/src/index.ts:33`](../packages/workflow/workflow-ptc/src/index.ts) ```ts config-catalog /** Plugin config (all optional — `static Config` supplies the defaults). */ @@ -4294,13 +4380,30 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-working-directory` + +- `inject`: `fs` · `sessionProjections` · `systemPrompt` +- `source`: [`packages/session/working-directory/src/index.ts:56`](../packages/session/working-directory/src/index.ts) + +```ts config-catalog +/** Deployment default for Sessions without an original project directory. */ +export interface Config { + /** Absolute execution-world fallback directory; omitted values use the launch directory. */ + defaultDirectory?: string +} +``` + + ## `@deepseek-ai/dsh-workspace-changes` -- `inject`: `subprocess` -- `source`: [`packages/deliverables/workspace-changes/src/index.ts:33`](../packages/deliverables/workspace-changes/src/index.ts) +- `inject`: `subprocess` · `fs` +- `source`: [`packages/deliverables/workspace-changes/src/index.ts:34`](../packages/deliverables/workspace-changes/src/index.ts) ```ts config-catalog /** Snapshot, capture, and comparison bounds. Invalid values fail plugin load. */ @@ -4394,10 +4497,13 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-config-editor` | `loader` · `profileContext` | [`packages/boot/config-editor/src/index.ts`](../packages/boot/config-editor/src/index.ts) | | `@deepseek-ai/dsh-cordis-client-runner` | — | [`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts) | | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | -| `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | +| `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` · `workingDirectory` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-agent-team` | — | [`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts) | +| `@deepseek-ai/dsh-experimental-client-ui-claude-code-mods` | — | [`packages/experimental/client-ui-claude-code-mods/src/index.ts`](../packages/experimental/client-ui-claude-code-mods/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) | | `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` | `computerUse` · `tools` · `systemPrompt` | [`packages/experimental/computer-use-cua-driver-native/src/index.ts`](../packages/experimental/computer-use-cua-driver-native/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-inspector` | — | [`packages/experimental/session-inspector/src/index.ts`](../packages/experimental/session-inspector/src/index.ts) | +| `@deepseek-ai/dsh-experimental-tool-worktree` | `tools` · `worktrees` | [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) | | `@deepseek-ai/dsh-fs-observation-policy` | — | [`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts) | | `@deepseek-ai/dsh-fs-ssh` | `ssh` · `sandboxPolicy` | [`packages/ssh/fs-ssh/src/index.ts`](../packages/ssh/fs-ssh/src/index.ts) | | `@deepseek-ai/dsh-goal-round-driver` | `agents` · `goals` · `sessions` | [`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts) | @@ -4415,7 +4521,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-session-stats` | `sessionProjections` | [`packages/session/session-stats/src/index.ts`](../packages/session/session-stats/src/index.ts) | | `@deepseek-ai/dsh-session-turn-outline` | `sessionProjections` | [`packages/session/session-turn-outline/src/index.ts`](../packages/session/session-turn-outline/src/index.ts) | | `@deepseek-ai/dsh-settings` | `configEditor` · `profileContext` | [`packages/settings/settings/src/index.ts`](../packages/settings/settings/src/index.ts) | -| `@deepseek-ai/dsh-skill-badge` | `skills` | [`packages/skill/skill-badge/src/index.ts`](../packages/skill/skill-badge/src/index.ts) | +| `@deepseek-ai/dsh-skill-badge` | `skills` | [`packages/experimental/skill-badge/src/index.ts`](../packages/experimental/skill-badge/src/index.ts) | | `@deepseek-ai/dsh-storage` | — | [`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts) | | `@deepseek-ai/dsh-subprocess-local` | — | [`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts) | | `@deepseek-ai/dsh-subprocess-ssh` | `ssh` | [`packages/ssh/subprocess-ssh/src/index.ts`](../packages/ssh/subprocess-ssh/src/index.ts) | @@ -4424,8 +4530,9 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) | | `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) | | `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) | +| `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) | | `@deepseek-ai/dsh-user-questions` | — | [`packages/interaction/user-questions/src/index.ts`](../packages/interaction/user-questions/src/index.ts) | -| `@deepseek-ai/dsh-webhook` | `agents` · `agentDefaultModel` · `agentPresets` · `permissionPresets` · `sessionTitle` · `workspaceRegistry` | [`packages/webhook/webhook/src/index.ts`](../packages/webhook/webhook/src/index.ts) | +| `@deepseek-ai/dsh-webhook` | `agents` · `agentDefaultModel` · `agentPresets` · `permissionPresets` · `sessionTitle` · `workspaceRegistry` | [`packages/experimental/webhook/src/index.ts`](../packages/experimental/webhook/src/index.ts) | | `@deepseek-ai/dsh-workspace` | `storageDomain` · `sessionPersistence` | [`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts) | @@ -4477,12 +4584,19 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. | `@deepseek-ai/dsh-cmdline` | — | [`packages/boot/cmdline/src/index.ts`](../packages/boot/cmdline/src/index.ts) | | `@deepseek-ai/dsh-deque` | — | [`packages/util/deque/src/index.ts`](../packages/util/deque/src/index.ts) | | `@deepseek-ai/dsh-experimental-agent-team-profile` | — | [`packages/experimental/agent-team-profile/src/index.ts`](../packages/experimental/agent-team-profile/src/index.ts) | +| `@deepseek-ai/dsh-experimental-badge-skill-bundle` | — | [`packages/experimental/badge-skill-bundle/src/index.ts`](../packages/experimental/badge-skill-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-browser-use-runtime` | — | [`packages/experimental/browser-use-runtime/src/index.ts`](../packages/experimental/browser-use-runtime/src/index.ts) | +| `@deepseek-ai/dsh-experimental-cot-translation-bundle` | — | [`packages/experimental/cot-translation-bundle/src/index.ts`](../packages/experimental/cot-translation-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-inspector-profile` | — | [`packages/experimental/inspector-profile/src/index.ts`](../packages/experimental/inspector-profile/src/index.ts) | +| `@deepseek-ai/dsh-experimental-ralph-bundle` | — | [`packages/experimental/ralph-bundle/src/index.ts`](../packages/experimental/ralph-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-search` | — | [`packages/experimental/session-search/src/index.ts`](../packages/experimental/session-search/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-titles-bundle` | — | [`packages/experimental/session-titles-bundle/src/index.ts`](../packages/experimental/session-titles-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-terminal-bundle` | — | [`packages/experimental/terminal-bundle/src/index.ts`](../packages/experimental/terminal-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-voice-input-bundle` | — | [`packages/experimental/voice-input-bundle/src/index.ts`](../packages/experimental/voice-input-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-webworker-packer` | — | [`packages/experimental/webworker-packer/src/index.ts`](../packages/experimental/webworker-packer/src/index.ts) | | `@deepseek-ai/dsh-experimental-webworker-runtime` | — | [`packages/experimental/webworker-runtime/src/index.ts`](../packages/experimental/webworker-runtime/src/index.ts) | | `@deepseek-ai/dsh-home-paths` | — | [`packages/util/home-paths/src/index.ts`](../packages/util/home-paths/src/index.ts) | -| `@deepseek-ai/dsh-hook-protocol` | — | [`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts) | +| `@deepseek-ai/dsh-hook-protocol` | — | [`packages/experimental/hook-protocol/src/index.ts`](../packages/experimental/hook-protocol/src/index.ts) | | `@deepseek-ai/dsh-http-proxy` | — | [`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts) | | `@deepseek-ai/dsh-launch-environment` | — | [`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts) | | `@deepseek-ai/dsh-lazy-require` | — | [`packages/util/lazy-require/src/index.ts`](../packages/util/lazy-require/src/index.ts) | @@ -4507,7 +4621,6 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. | `@deepseek-ai/dsh-session-snapshot` | — | [`packages/test-support/session-snapshot/src/index.ts`](../packages/test-support/session-snapshot/src/index.ts) | | `@deepseek-ai/dsh-session-telemetry` | — | [`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts) | | `@deepseek-ai/dsh-session-title-llm` | — | [`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts) | -| `@deepseek-ai/dsh-subagent-in-process-driver` | — | [`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts) | | `@deepseek-ai/dsh-timeout` | — | [`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts) | | `@deepseek-ai/dsh-typert-generator` | — | [`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts) | | `@deepseek-ai/dsh-typert-protocol` | — | [`packages/typert/protocol/src/index.ts`](../packages/typert/protocol/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 415ec1b4c2..835470b464 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -61,15 +61,13 @@ export interface Config { ## `@deepseek-ai/dsh-agent-instructions` -- `inject`: `sessionProjections` +- `inject`: `sessionProjections` · `workingDirectory` - `source`: [`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts) ```ts config-catalog /** User-facing workspace instruction loader configuration. */ export interface Config { - /** Harness home containing the fixed user-global `AGENTS.md`; defaults to `$DSH_HOME` or `~/.dsh`. */ - dshHome?: string - /** Directory entries that identify the project root while walking upward from the session cwd. */ + /** Directory entries that identify the project root while walking upward from the current working directory. */ projectRootMarkers?: string[] /** UTF-8 byte cap for one rendered baseline or dynamic batch; non-positive or non-finite disables loading. */ maxBytes: number @@ -143,7 +141,7 @@ export type Config = PresetDefinition - `inject`: `loader` · `sessionProjections` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:13`](../packages/preset/agent-preset-registry/src/preset.ts) +- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:15`](../packages/preset/agent-preset-registry/src/preset.ts) ```ts config-catalog /** Registry selection policy. */ @@ -170,7 +168,7 @@ export interface Config { export interface Config { /** * The form this agent's model sees. `native` sends every visible schema, - * `ptc` sends only `run_code` plus a generated SDK, `both` sends both. + * `ptc` sends only `run_code` plus a generated SDK. * Required rather than defaulted: the deployment default is what a preset * without this row already gets, so an omitted value would mean the row was * composed for nothing. @@ -231,6 +229,8 @@ export interface Config { export interface Config { /** Override platform desktop-opener detection. */ readonly nativeOpen?: boolean + /** Positive integral milliseconds of list work before yielding between complete rows. */ + readonly listWorkSliceMs?: number } ``` @@ -256,8 +256,8 @@ export interface SettingsControllerInternals { ## `@deepseek-ai/dsh-api-terminal-controller` -- `inject`: `subprocess` · `sandboxPolicy` · `typert` -- `source`: [`packages/api/terminal-controller/src/index.ts:26`](../packages/api/terminal-controller/src/index.ts) +- `inject`: `subprocess` · `sandboxPolicy` · `workingDirectory` · `typert` +- `source`: [`packages/api/terminal-controller/src/index.ts:27`](../packages/api/terminal-controller/src/index.ts) ```ts config-catalog /** Deployment limits and an optional shell profile. */ @@ -874,9 +874,9 @@ export interface Config { ## `@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp` -- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` +- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` · `workingDirectory` - `refs`: `BrowserMcpConfig` (`@deepseek-ai/dsh-experimental-browser-use-runtime/mcp`) -- `source`: [`packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts:14`](../packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts) +- `source`: [`packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts:15`](../packages/experimental/browser-use-chrome-devtools-mcp/src/index.ts) ```ts config-catalog /** Fixed Chromium launch or existing-browser attachment settings. */ @@ -889,9 +889,9 @@ export type Config = BrowserMcpConfig ## `@deepseek-ai/dsh-experimental-browser-use-playwright-mcp` -- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` +- `inject`: `browserUse` · `agents` · `tools` · `systemPrompt` · `workingDirectory` - `refs`: `BrowserMcpConfig` (`@deepseek-ai/dsh-experimental-browser-use-runtime/mcp`) -- `source`: [`packages/experimental/browser-use-playwright-mcp/src/index.ts:15`](../packages/experimental/browser-use-playwright-mcp/src/index.ts) +- `source`: [`packages/experimental/browser-use-playwright-mcp/src/index.ts:16`](../packages/experimental/browser-use-playwright-mcp/src/index.ts) ```ts config-catalog /** Fixed Chromium launch or existing-browser attachment settings. */ @@ -941,6 +941,61 @@ export interface StagehandModelConfig { ``` + + + +## `@deepseek-ai/dsh-experimental-claude-code-mods` + +- `inject`: `workingDirectory` +- `source`: [`packages/experimental/claude-code-mods/src/index.ts:53`](../packages/experimental/claude-code-mods/src/index.ts) + +```ts config-catalog +/** Plugin config: the limits mod hooks run under. */ +export interface Config { + /** A hook's own running-time limit in milliseconds (Claude Code: 10 seconds). */ + hookTimeoutMs?: number + /** A `.catch` handler's running-time limit in milliseconds (Claude Code: 1 second). */ + catchTimeoutMs?: number + /** Default `$.process.run` and `$.http.fetch` timeout in milliseconds (Claude Code: 30 seconds). */ + processTimeoutMs?: number + /** Claude Code tool name → harness tool name entries added to the built-in alias table. */ + toolAliases?: Record + /** Columns the band above the prompt reports to `ui.render` as `bodyColumns` and `viewport.columns`. */ + bandColumns?: number + /** Rows the band reports as `maxRows`. */ + bandRows?: number +} +``` + + + + + +## `@deepseek-ai/dsh-experimental-client-ui-cot-translation` + +- `inject`: `translator` · `typert` · `sessionController` +- `refs`: [`TranslationProvider`](subsystems/translation.zh.md) · `Volatile` (`@deepseek-ai/cordis`) +- `source`: [`packages/experimental/client-ui-cot-translation/src/index.ts:14`](../packages/experimental/client-ui-cot-translation/src/index.ts) + +```ts config-catalog +/** Live preferences presented on the bundle's Plugins page. */ +export interface Config { + /** Explicit translation provider, defaulting to Bing. */ + provider: Volatile + /** Target language code; auto follows the browser UI locale. */ + targetLanguage: Volatile +} + +/** Translation choices; auto targets the active browser UI language. */ +export interface CotTranslationPreferences { + /** Explicit provider selection; requests never fall back to another provider. */ + provider: TranslationProvider + /** BCP 47 language code, or auto to follow the UI language. */ + targetLanguage: string +} +``` + + @@ -970,7 +1025,7 @@ export interface Config { ## `@deepseek-ai/dsh-experimental-inspector` -- `inject`: `webServer` +- `inject`: `webServer` · `connection` - `source`: [`packages/experimental/inspector/src/index.ts:66`](../packages/experimental/inspector/src/index.ts) ```ts config-catalog @@ -1039,7 +1094,8 @@ export interface InspectorOptions { ## `@deepseek-ai/dsh-experimental-ptc-runtime-python` -- `source`: [`packages/experimental/ptc-runtime-python/src/index.ts:42`](../packages/experimental/ptc-runtime-python/src/index.ts) +- `inject`: `sandbox` · `sandboxPolicy` +- `source`: [`packages/experimental/ptc-runtime-python/src/index.ts:45`](../packages/experimental/ptc-runtime-python/src/index.ts) ```ts config-catalog /** Plugin config: every cap, changeable from `cordis.yml` (no hardcoded tunables). */ @@ -1199,13 +1255,81 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-experimental-translator` + +- `source`: [`packages/experimental/translator/src/index.ts:25`](../packages/experimental/translator/src/index.ts) + +```ts config-catalog +/** Routing and bounds for anonymous requests and independent native model translation. */ +export interface Config { + /** Provider selected when a consumer omits it. */ + provider: TranslationProvider + /** Google-compatible anonymous translation endpoint. */ + googleEndpoint: string + /** Bing-compatible Microsoft Edge browser translation endpoint. */ + bingEndpoint: string + /** Deadline covering the request and complete response body, in milliseconds. */ + timeoutMs: number + /** Maximum UTF-16 code units per request; at least two to admit one supplementary-plane character. */ + maxTextChars: number + /** Maximum anonymous JSON-body bytes or paid assembled UTF-8 translation bytes. */ + maxResponseBytes: number + /** Deadline for native admission and complete translation output, in milliseconds. */ + deepseekTimeoutMs: number + /** Requested output-token cap for independent native Flash translation. */ + deepseekMaxOutputTokens: number +} + +/** Explicit anonymous endpoint or native paid model route. */ +export type TranslationProvider = AnonymousTranslationProvider | PaidTranslationProvider + +/** Anonymous browser endpoint selected for one translation. */ +export type AnonymousTranslationProvider = 'google' | 'bing' + +/** Explicit native DeepSeek credential route selected for paid translation. */ +export type PaidTranslationProvider = 'deepseek-account' | 'deepseek-official' +``` + + + + + +## `@deepseek-ai/dsh-experimental-worktree` + +- `inject`: `workingDirectory` · `fs` · `subprocess` · `sandbox` · `sandboxPolicy` +- `source`: [`packages/experimental/worktree/src/index.ts:19`](../packages/experimental/worktree/src/index.ts) + +```ts config-catalog +/** Worktree location, generated names, executables, and subprocess resource limits. */ +export interface Config { + /** Relative checkout pool inside the source repository. */ + directory?: string + /** Prefix for automatically generated branch and checkout names. */ + namePrefix?: string + /** Git 2.45 or newer executable name or absolute execution-world path. */ + gitCommand?: string + /** Node executable used for sandboxed directory allocation. */ + nodeCommand?: string + /** Deadline in milliseconds for one command. */ + timeoutMs?: number + /** Subprocess termination grace in milliseconds. */ + graceMs?: number + /** Maximum captured bytes per subprocess output stream. */ + maxOutputBytes?: number +} +``` + + ## `@deepseek-ai/dsh-file-reference-local` -- `inject`: `agents` -- `source`: [`packages/context/file-reference-local/src/index.ts:34`](../packages/context/file-reference-local/src/index.ts) +- `inject`: `agents` · `workingDirectory` +- `source`: [`packages/context/file-reference-local/src/index.ts:35`](../packages/context/file-reference-local/src/index.ts) ```ts config-catalog /** Local file-reference discovery configuration. */ @@ -1284,7 +1408,7 @@ export interface Config { ## `@deepseek-ai/dsh-headless` - `inject`: `agentDefaultModel` · `agents` · `sessions` -- `source`: [`packages/bundle/headless/src/index.ts:42`](../packages/bundle/headless/src/index.ts) +- `source`: [`packages/bundle/headless/src/index.ts:43`](../packages/bundle/headless/src/index.ts) ```ts config-catalog /** Plugin config: the task and run options resolved from this app's injected provider service. */ @@ -1305,7 +1429,7 @@ export interface Config { ## `@deepseek-ai/dsh-hmr` - `refs`: `ChokidarOptions` (`chokidar`) -- `source`: [`packages/boot/hmr/src/index.ts:51`](../packages/boot/hmr/src/index.ts) +- `source`: [`packages/boot/hmr/src/index.ts:53`](../packages/boot/hmr/src/index.ts) ```ts config-catalog /** Module roots and watcher timing, with Chokidar deployment options. */ @@ -1327,8 +1451,8 @@ export interface HmrConfig extends ChokidarOptions { ## `@deepseek-ai/dsh-hooks-claude-code` -- `inject`: `shell` · `sessionProjections` -- `source`: [`packages/hooks/hooks-claude-code/src/index.ts:51`](../packages/hooks/hooks-claude-code/src/index.ts) +- `inject`: `shell` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/experimental/hooks-claude-code/src/index.ts:51`](../packages/experimental/hooks-claude-code/src/index.ts) ```ts config-catalog /** Plugin config: where the CC hook config lives + substitution roots. */ @@ -1348,7 +1472,7 @@ export interface Config { /** * Replaces `${CLAUDE_PROJECT_DIR}` in command strings AND is exported as the * `CLAUDE_PROJECT_DIR` env var for hook processes. When omitted, the env var - * defaults per-run to the agent's session workspace (`session.header.cwd`, the + * defaults per-run to the agent's current directory (the * same dir the hook runs in) — Claude Code always exports this var, and common * unmodified hooks reference `$CLAUDE_PROJECT_DIR` for project-relative paths. */ @@ -1366,8 +1490,8 @@ export interface Config { ## `@deepseek-ai/dsh-hooks-codex` -- `inject`: `shell` · `sessionProjections` -- `source`: [`packages/hooks/hooks-codex/src/index.ts:50`](../packages/hooks/hooks-codex/src/index.ts) +- `inject`: `shell` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/experimental/hooks-codex/src/index.ts:50`](../packages/experimental/hooks-codex/src/index.ts) ```ts config-catalog /** Plugin config: where the Codex hooks.json lives + the model name for payloads. */ @@ -1515,26 +1639,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-invariants` - -- `source`: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) - -```ts config-catalog -/** Runtime invariant selection configured on the service plugin. */ -export interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - - @@ -1938,7 +2042,7 @@ export interface ReplayModelConfig { * no image pricing. */ imageRequestTokens?: number - /** Optional reasoning-effort ids the replay route accepts, in display order. */ + /** Optional reasoning-effort ids from least to greatest selectable effort. */ reasoningEfforts?: string[] /** * Optional effort materialized when callers omit one; must appear in @@ -2233,7 +2337,7 @@ export interface Config { ## `@deepseek-ai/dsh-plan-mode` - `inject`: `tools` · `systemPrompt` · `sessionProjections` -- `source`: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) +- `source`: [`packages/plan/plan-mode/src/index.ts:69`](../packages/plan/plan-mode/src/index.ts) ```ts config-catalog /** Deployment-owned plan guidance. */ @@ -2250,7 +2354,7 @@ export interface PlanModeConfig { ## `@deepseek-ai/dsh-plugin-manager` - `inject`: `loader` · `profileContext` -- `source`: [`packages/boot/plugin-manager/src/index.ts:40`](../packages/boot/plugin-manager/src/index.ts) +- `source`: [`packages/boot/plugin-manager/src/index.ts:44`](../packages/boot/plugin-manager/src/index.ts) ```ts config-catalog /** The pnpm executable, registries, and limits for diagnostics, lookups and connection checks. */ @@ -2536,9 +2640,9 @@ export interface Config { ## `@deepseek-ai/dsh-sdk-jsonrpc-server` -- `inject`: `agents` +- `inject`: `agents` · `workingDirectory` - `refs`: `Readable` (`node:stream`) · `Writable` (`node:stream`) -- `source`: [`packages/sdk/server/src/index.ts:25`](../packages/sdk/server/src/index.ts) +- `source`: [`packages/sdk/server/src/index.ts:24`](../packages/sdk/server/src/index.ts) ```ts config-catalog /** JSON-RPC deployment config plus runtime-only test hooks. */ @@ -2728,8 +2832,8 @@ export interface Config { ## `@deepseek-ai/dsh-session-telemetry-otel` - `inject`: `sessions` · `otel` -- `refs`: `BatchLogRecordProcessorOptions` (`@opentelemetry/sdk-logs`) · `OTLPExporterNodeConfigBase` (`@opentelemetry/otlp-exporter-base`) -- `source`: [`packages/session/session-telemetry-otel/src/index.ts:87`](../packages/session/session-telemetry-otel/src/index.ts) +- `refs`: [`LogExporterOptions`](../packages/telemetry/otel/src/index.ts) · [`SessionLogProcessorOptions`](../packages/telemetry/otel/src/index.ts) +- `source`: [`packages/session/session-telemetry-otel/src/index.ts:85`](../packages/session/session-telemetry-otel/src/index.ts) ```ts config-catalog /** @@ -2743,16 +2847,18 @@ export interface Config { /** * Explicit SDK HTTP transport settings, including optional routing headers. * Ambient credentials are not inherited. URL is required while uploading. + * Exporter self-observability metering is not supported. */ - exporter?: OTLPExporterNodeConfigBase & { + exporter?: Omit & { /** Full logs endpoint (e.g. `https://collector.example.com/v1/logs`). Required outside `DISABLED`; validated at load. */ url?: string } /** * Count, queue, cadence, and per-request watchdog settings for the byte-bounded * processor. A watchdog warning never releases an unsettled transport slot. + * SDK processor self-observability metering is not supported. */ - processor?: Omit + processor?: SessionLogProcessorOptions /** Maximum time spent awaiting the SDK provider's complete shutdown path. */ shutdownTimeoutMillis?: number /** Uncompressed OTLP request byte limit, at most 4,000,000. */ @@ -2795,7 +2901,7 @@ export interface Config { - `inject`: `sessionTitle` · `llm` · `sessions` - `refs`: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/index.ts) -- `source`: [`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts) +- `source`: [`packages/experimental/session-title-all-prompts-llm/src/index.ts:15`](../packages/experimental/session-title-all-prompts-llm/src/index.ts) ```ts config-catalog /** Required LLM policy; this plugin adds no defaults. */ @@ -2856,7 +2962,7 @@ export interface Config { ## `@deepseek-ai/dsh-skill-filesystem` - `inject`: `skills` -- `source`: [`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts) +- `source`: [`packages/skill/skill-filesystem/src/index.ts:48`](../packages/skill/skill-filesystem/src/index.ts) ```ts config-catalog /** Local filesystem skill provider configuration. */ @@ -3086,8 +3192,9 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ## `@deepseek-ai/dsh-subagent` +- `inject`: `workingDirectory` - `refs`: `Volatile` (`@deepseek-ai/cordis`) -- `source`: [`packages/subagent/subagent/src/index.ts:192`](../packages/subagent/subagent/src/index.ts) +- `source`: [`packages/subagent/subagent/src/index.ts:184`](../packages/subagent/subagent/src/index.ts) ```ts config-catalog /** Host configuration for continuable subagent capacity. */ @@ -3106,7 +3213,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-acp` - `inject`: `subagents` · `subprocess` -- `source`: [`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts) +- `source`: [`packages/subagent/subagent-acp/src/index.ts:25`](../packages/subagent/subagent-acp/src/index.ts) ```ts config-catalog /** Config: how to spawn and drive the child ACP agent process. */ @@ -3117,14 +3224,6 @@ export interface Config { command: string /** Arguments passed to {@link command}. */ args: string[] - /** - * Working directory override for the child process and its ACP session. - * Must be non-empty; a relative path resolves against the harness launch - * directory at load, and the result must be an existing directory. When - * omitted, each child inherits its delegating parent session's cwd — and - * starting one from a parent session that has no cwd fails. - */ - cwd?: string /** * How to auto-answer the child's `session/request_permission` prompts: * `reject` (default — decline every prompt) or `allow` (approve via the first @@ -3245,15 +3344,6 @@ export interface Config { patches: string[] /** Absolute isolated Harness home for every nested child process. */ dshHome: string - /** - * Working directory override for the child process and its SDK session - * workspace. Must be non-empty; a relative path resolves against the - * harness launch directory at load, and the result must be an existing - * directory. When omitted, each child inherits its delegating parent - * session's cwd — and starting one from a parent session that has no cwd - * fails. - */ - cwd?: string /** Provider route the child runtime initializes with (default `deepseek-official`). */ provider: string /** Model the child runtime initializes with (default `deepseek-v4-flash`). */ @@ -3287,7 +3377,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-fork-in-process` - `inject`: `subagents` -- `source`: [`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts) +- `source`: [`packages/subagent/subagent-fork-in-process/src/index.ts:29`](../packages/subagent/subagent-fork-in-process/src/index.ts) ```ts config-catalog /** Config: the registry name to register the provider under. */ @@ -3304,7 +3394,7 @@ export interface Config { ## `@deepseek-ai/dsh-subagent-spawn-in-process` - `inject`: `subagents` -- `source`: [`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) +- `source`: [`packages/subagent/subagent-spawn-in-process/src/index.ts:23`](../packages/subagent/subagent-spawn-in-process/src/index.ts) ```ts config-catalog /** Config: the registry name to register the provider under. */ @@ -3320,14 +3410,14 @@ export interface Config { ## `@deepseek-ai/dsh-system-prompt` -- `source`: [`packages/core/system-prompt/src/index.ts:247`](../packages/core/system-prompt/src/index.ts) +- `source`: [`packages/core/system-prompt/src/index.ts:254`](../packages/core/system-prompt/src/index.ts) ```ts config-catalog /** Plugin config: the deployment-authored fragment of the system prompt (see {@link Config.personaPrefix} for its contract). */ export interface Config { /** Include the fixed DeepSeek Harness identity before the deployment persona (default true). */ includeHarnessIdentity?: boolean - /** Include dynamic runtime-context snapshots in model history (default true). */ + /** Include optional runtime-context snapshots in model history (default true); required context remains. */ includeRuntimeContext?: boolean /** * Deployment-wide persona prefix template before first-party guidance. A scoped section named @@ -3354,7 +3444,7 @@ export interface Config { ## `@deepseek-ai/dsh-terminal-bash` -- `inject`: `terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess` +- `inject`: `terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess` · `workingDirectory` - `source`: [`packages/terminal/terminal-bash/src/config.ts:10`](../packages/terminal/terminal-bash/src/config.ts) ```ts config-catalog @@ -3483,7 +3573,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-bash` -- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` +- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` · `workingDirectory` - `source`: [`packages/shell/tool-bash/src/index.ts:37`](../packages/shell/tool-bash/src/index.ts) ```ts config-catalog @@ -3513,8 +3603,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-bash-persistent` -- `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-bash-persistent/src/index.ts:444`](../packages/shell/tool-bash-persistent/src/index.ts) +- `inject`: `tools` · `terminals` · `workingDirectory` +- `source`: [`packages/shell/tool-bash-persistent/src/index.ts:445`](../packages/shell/tool-bash-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent Bash tool. */ @@ -3536,8 +3626,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-fs` -- `inject`: `tools` · `fs` · `systemPrompt` -- `source`: [`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts) +- `inject`: `tools` · `fs` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/fs/tool-fs/src/index.ts:26`](../packages/fs/tool-fs/src/index.ts) ```ts config-catalog /** Plugin config (all optional — `Config` supplies the defaults). */ @@ -3559,8 +3649,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-fs-search` -- `inject`: `tools` · `systemPrompt` · `subprocess` -- `source`: [`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts) +- `inject`: `tools` · `systemPrompt` · `subprocess` · `workingDirectory` +- `source`: [`packages/fs/tool-fs-search/src/index.ts:74`](../packages/fs/tool-fs-search/src/index.ts) ```ts config-catalog /** Plugin config; over-cap glob sampling is an explicit deployment choice and the remaining fields have defaults. */ @@ -3649,8 +3739,8 @@ export type CompletionDelivery = 'quiet' | 'wakeup' ## `@deepseek-ai/dsh-tool-lsp` -- `inject`: `tools` · `lsp` · `systemPrompt` -- `source`: [`packages/lsp/tool-lsp/src/index.ts:57`](../packages/lsp/tool-lsp/src/index.ts) +- `inject`: `tools` · `lsp` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/lsp/tool-lsp/src/index.ts:56`](../packages/lsp/tool-lsp/src/index.ts) ```ts config-catalog /** Plugin configuration: result caps and the timeout budget. */ @@ -3670,8 +3760,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-present` -- `inject`: `tools` · `fs` · `sessionProjections` -- `source`: [`packages/deliverables/tool-present/src/index.ts:15`](../packages/deliverables/tool-present/src/index.ts) +- `inject`: `tools` · `fs` · `sessionProjections` · `workingDirectory` +- `source`: [`packages/deliverables/tool-present/src/index.ts:16`](../packages/deliverables/tool-present/src/index.ts) ```ts config-catalog /** Per-call delivery limit. */ @@ -3687,7 +3777,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh` -- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` +- `inject`: `tools` · `shell` · `systemPrompt` · `shellEnv` · `workingDirectory` - `source`: [`packages/shell/tool-pwsh/src/index.ts:54`](../packages/shell/tool-pwsh/src/index.ts) ```ts config-catalog @@ -3717,8 +3807,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-pwsh-persistent` -- `inject`: `tools` · `terminals` -- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:457`](../packages/shell/tool-pwsh-persistent/src/index.ts) +- `inject`: `tools` · `terminals` · `workingDirectory` +- `source`: [`packages/shell/tool-pwsh-persistent/src/index.ts:458`](../packages/shell/tool-pwsh-persistent/src/index.ts) ```ts config-catalog /** Configuration for the persistent pwsh tool. */ @@ -3741,7 +3831,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-ralph` - `inject`: `tools` · `workflowEngine` · `subagents` · `systemPrompt` -- `source`: [`packages/workflow/tool-ralph/src/index.ts:21`](../packages/workflow/tool-ralph/src/index.ts) +- `source`: [`packages/experimental/tool-ralph/src/index.ts:21`](../packages/experimental/tool-ralph/src/index.ts) ```ts config-catalog /** Deployment policy for the fixed Ralph workflow. */ @@ -3764,7 +3854,7 @@ export interface Config { ## `@deepseek-ai/dsh-tool-session-query` - `inject`: `tools` · `systemPrompt` · `sessionQuery` · `sessionProjections` -- `source`: [`packages/session-query/tool-session-query/src/index.ts:28`](../packages/session-query/tool-session-query/src/index.ts) +- `source`: [`packages/experimental/tool-session-query/src/index.ts:28`](../packages/experimental/tool-session-query/src/index.ts) ```ts config-catalog /** Deployment-owned search count and timeout bounds. */ @@ -3782,8 +3872,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-skill` -- `inject`: `agents` · `tools` · `skills` -- `source`: [`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts) +- `inject`: `agents` · `tools` · `skills` · `workingDirectory` +- `source`: [`packages/skill/tool-skill/src/index.ts:62`](../packages/skill/tool-skill/src/index.ts) ```ts config-catalog /** Model-facing skill catalog configuration. */ @@ -3837,18 +3927,6 @@ export interface Config { * Session and inherit that decision in its child Sessions. */ modelSelectionSettings?: boolean - /** - * Expose `run_in_background` (default true). Disabled instances omit the - * parameter and reject forced background calls. - */ - enableRunInBackground?: boolean - /** - * Background execution policy (default `one-shot`). `one-shot` defaults calls - * to foreground; `continuable` defaults them to background, requires a provider - * with the `prepareContinuable` capability, and returns the durable child id. - * Follow-up adapters remain independently optional. - */ - backgroundMode?: 'one-shot' | 'continuable' /** * Agent options applied to every child; omitted fields use child-loop defaults. */ @@ -3889,8 +3967,8 @@ export interface Config { ## `@deepseek-ai/dsh-tool-terminal` -- `inject`: `terminals` · `tools` · `systemPrompt` -- `source`: [`packages/terminal/tool-terminal/src/index.ts:36`](../packages/terminal/tool-terminal/src/index.ts) +- `inject`: `terminals` · `tools` · `systemPrompt` · `workingDirectory` +- `source`: [`packages/experimental/tool-terminal/src/index.ts:38`](../packages/experimental/tool-terminal/src/index.ts) ```ts config-catalog /** Model-facing terminal tool configuration. */ @@ -4011,7 +4089,7 @@ export interface Config { ## `@deepseek-ai/dsh-tools` - `inject`: `systemPrompt` -- `source`: [`packages/core/tools/src/index.ts:674`](../packages/core/tools/src/index.ts) +- `source`: [`packages/core/tools/src/index.ts:675`](../packages/core/tools/src/index.ts) ```ts config-catalog /** Plugin config: how the registered tools are presented to the model. */ @@ -4020,9 +4098,9 @@ export interface Config { * Model presentation. `native` (default) sends every visible schema; `ptc` * sends only `run_code` plus a generated SDK prompt and collapses the * executor to the same surface (a model-direct call may only name - * `run_code`; `run_code` SDK sub-dispatches keep every visible tool); `both` - * sends both forms. PTC mode requires a `ctx.ptcRuntime` whose `language` - * has a registered SDK renderer (TypeScript or Python) and fail prompt + * `run_code`; `run_code` SDK sub-dispatches keep every visible tool). + * PTC mode requires a `ctx.ptcRuntime` whose `language` + * has a registered SDK renderer (TypeScript or Python) and fails prompt * assembly when it is absent or has no renderer. Under `ptc`, native names * in `toolOrder` are invalid. */ @@ -4038,7 +4116,7 @@ export interface Config { } /** How the registry presents its tools to the model (see {@link Config.mode}). */ -export type ToolPresentationMode = 'native' | 'ptc' | 'both' +export type ToolPresentationMode = 'native' | 'ptc' ``` @@ -4121,7 +4199,7 @@ export interface WebRuntimeConfig { ## `@deepseek-ai/dsh-web-app` - `inject`: `webServer` -- `source`: [`packages/bundle/web-app/src/index.ts:44`](../packages/bundle/web-app/src/index.ts) +- `source`: [`packages/bundle/web-app/src/index.ts:46`](../packages/bundle/web-app/src/index.ts) ```ts config-catalog /** Plugin config: composed deployment settings plus per-invocation command-line values. */ @@ -4137,6 +4215,14 @@ export interface Config { * orientation text would be false. */ surfaceContext: boolean + /** + * Canonical HTTP(S) root to advertise in the printed and opened URL, + * `DSH_WEB_URL`, and the web-surface orientation, e.g. + * `https://app.example/ui/`, normalized to end in `/`. Advertisement only; + * see [public deployments](../README.md#public-deployments). Absent or YAML + * `null` advertises the loopback URL. + */ + publicUrl?: string /** Explicit `--trusted-host` authorities from this invocation. */ trustedHosts: string[] } @@ -4254,7 +4340,7 @@ export interface Config { ## `@deepseek-ai/dsh-webhook-github` - `inject`: `webServer` · `webhookRuntime` · `credentials` -- `source`: [`packages/webhook/webhook-github/src/index.ts:17`](../packages/webhook/webhook-github/src/index.ts) +- `source`: [`packages/experimental/webhook-github/src/index.ts:17`](../packages/experimental/webhook-github/src/index.ts) ```ts config-catalog /** Required GitHub ingress configuration. */ @@ -4276,8 +4362,8 @@ export interface Config { ## `@deepseek-ai/dsh-workflow-ptc` -- `inject`: `subagents` · `ptcRuntime` · `sandboxPolicy` -- `source`: [`packages/workflow/workflow-ptc/src/index.ts:32`](../packages/workflow/workflow-ptc/src/index.ts) +- `inject`: `subagents` · `ptcRuntime` · `sandboxPolicy` · `workingDirectory` +- `source`: [`packages/workflow/workflow-ptc/src/index.ts:33`](../packages/workflow/workflow-ptc/src/index.ts) ```ts config-catalog /** Plugin config (all optional — `static Config` supplies the defaults). */ @@ -4296,13 +4382,30 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-working-directory` + +- `inject`: `fs` · `sessionProjections` · `systemPrompt` +- `source`: [`packages/session/working-directory/src/index.ts:56`](../packages/session/working-directory/src/index.ts) + +```ts config-catalog +/** Deployment default for Sessions without an original project directory. */ +export interface Config { + /** Absolute execution-world fallback directory; omitted values use the launch directory. */ + defaultDirectory?: string +} +``` + + ## `@deepseek-ai/dsh-workspace-changes` -- `inject`: `subprocess` -- `source`: [`packages/deliverables/workspace-changes/src/index.ts:33`](../packages/deliverables/workspace-changes/src/index.ts) +- `inject`: `subprocess` · `fs` +- `source`: [`packages/deliverables/workspace-changes/src/index.ts:34`](../packages/deliverables/workspace-changes/src/index.ts) ```ts config-catalog /** Snapshot, capture, and comparison bounds. Invalid values fail plugin load. */ @@ -4396,10 +4499,13 @@ export interface Config { | `@deepseek-ai/dsh-config-editor` | `loader` · `profileContext` | [`packages/boot/config-editor/src/index.ts`](../packages/boot/config-editor/src/index.ts) | | `@deepseek-ai/dsh-cordis-client-runner` | — | [`packages/extensions/cordis-client-runner/src/index.ts`](../packages/extensions/cordis-client-runner/src/index.ts) | | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | -| `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | +| `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` · `workingDirectory` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-agent-team` | — | [`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts) | +| `@deepseek-ai/dsh-experimental-client-ui-claude-code-mods` | — | [`packages/experimental/client-ui-claude-code-mods/src/index.ts`](../packages/experimental/client-ui-claude-code-mods/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) | | `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` | `computerUse` · `tools` · `systemPrompt` | [`packages/experimental/computer-use-cua-driver-native/src/index.ts`](../packages/experimental/computer-use-cua-driver-native/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-inspector` | — | [`packages/experimental/session-inspector/src/index.ts`](../packages/experimental/session-inspector/src/index.ts) | +| `@deepseek-ai/dsh-experimental-tool-worktree` | `tools` · `worktrees` | [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) | | `@deepseek-ai/dsh-fs-observation-policy` | — | [`packages/fs/fs-observation-policy/src/index.ts`](../packages/fs/fs-observation-policy/src/index.ts) | | `@deepseek-ai/dsh-fs-ssh` | `ssh` · `sandboxPolicy` | [`packages/ssh/fs-ssh/src/index.ts`](../packages/ssh/fs-ssh/src/index.ts) | | `@deepseek-ai/dsh-goal-round-driver` | `agents` · `goals` · `sessions` | [`packages/goal/goal-round-driver/src/index.ts`](../packages/goal/goal-round-driver/src/index.ts) | @@ -4417,7 +4523,7 @@ export interface Config { | `@deepseek-ai/dsh-session-stats` | `sessionProjections` | [`packages/session/session-stats/src/index.ts`](../packages/session/session-stats/src/index.ts) | | `@deepseek-ai/dsh-session-turn-outline` | `sessionProjections` | [`packages/session/session-turn-outline/src/index.ts`](../packages/session/session-turn-outline/src/index.ts) | | `@deepseek-ai/dsh-settings` | `configEditor` · `profileContext` | [`packages/settings/settings/src/index.ts`](../packages/settings/settings/src/index.ts) | -| `@deepseek-ai/dsh-skill-badge` | `skills` | [`packages/skill/skill-badge/src/index.ts`](../packages/skill/skill-badge/src/index.ts) | +| `@deepseek-ai/dsh-skill-badge` | `skills` | [`packages/experimental/skill-badge/src/index.ts`](../packages/experimental/skill-badge/src/index.ts) | | `@deepseek-ai/dsh-storage` | — | [`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts) | | `@deepseek-ai/dsh-subprocess-local` | — | [`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts) | | `@deepseek-ai/dsh-subprocess-ssh` | `ssh` | [`packages/ssh/subprocess-ssh/src/index.ts`](../packages/ssh/subprocess-ssh/src/index.ts) | @@ -4426,8 +4532,9 @@ export interface Config { | `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) | | `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) | | `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) | +| `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) | | `@deepseek-ai/dsh-user-questions` | — | [`packages/interaction/user-questions/src/index.ts`](../packages/interaction/user-questions/src/index.ts) | -| `@deepseek-ai/dsh-webhook` | `agents` · `agentDefaultModel` · `agentPresets` · `permissionPresets` · `sessionTitle` · `workspaceRegistry` | [`packages/webhook/webhook/src/index.ts`](../packages/webhook/webhook/src/index.ts) | +| `@deepseek-ai/dsh-webhook` | `agents` · `agentDefaultModel` · `agentPresets` · `permissionPresets` · `sessionTitle` · `workspaceRegistry` | [`packages/experimental/webhook/src/index.ts`](../packages/experimental/webhook/src/index.ts) | | `@deepseek-ai/dsh-workspace` | `storageDomain` · `sessionPersistence` | [`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts) | @@ -4479,12 +4586,19 @@ export interface Config { | `@deepseek-ai/dsh-cmdline` | — | [`packages/boot/cmdline/src/index.ts`](../packages/boot/cmdline/src/index.ts) | | `@deepseek-ai/dsh-deque` | — | [`packages/util/deque/src/index.ts`](../packages/util/deque/src/index.ts) | | `@deepseek-ai/dsh-experimental-agent-team-profile` | — | [`packages/experimental/agent-team-profile/src/index.ts`](../packages/experimental/agent-team-profile/src/index.ts) | +| `@deepseek-ai/dsh-experimental-badge-skill-bundle` | — | [`packages/experimental/badge-skill-bundle/src/index.ts`](../packages/experimental/badge-skill-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-browser-use-runtime` | — | [`packages/experimental/browser-use-runtime/src/index.ts`](../packages/experimental/browser-use-runtime/src/index.ts) | +| `@deepseek-ai/dsh-experimental-cot-translation-bundle` | — | [`packages/experimental/cot-translation-bundle/src/index.ts`](../packages/experimental/cot-translation-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-inspector-profile` | — | [`packages/experimental/inspector-profile/src/index.ts`](../packages/experimental/inspector-profile/src/index.ts) | +| `@deepseek-ai/dsh-experimental-ralph-bundle` | — | [`packages/experimental/ralph-bundle/src/index.ts`](../packages/experimental/ralph-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-search` | — | [`packages/experimental/session-search/src/index.ts`](../packages/experimental/session-search/src/index.ts) | +| `@deepseek-ai/dsh-experimental-session-titles-bundle` | — | [`packages/experimental/session-titles-bundle/src/index.ts`](../packages/experimental/session-titles-bundle/src/index.ts) | +| `@deepseek-ai/dsh-experimental-terminal-bundle` | — | [`packages/experimental/terminal-bundle/src/index.ts`](../packages/experimental/terminal-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-voice-input-bundle` | — | [`packages/experimental/voice-input-bundle/src/index.ts`](../packages/experimental/voice-input-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-webworker-packer` | — | [`packages/experimental/webworker-packer/src/index.ts`](../packages/experimental/webworker-packer/src/index.ts) | | `@deepseek-ai/dsh-experimental-webworker-runtime` | — | [`packages/experimental/webworker-runtime/src/index.ts`](../packages/experimental/webworker-runtime/src/index.ts) | | `@deepseek-ai/dsh-home-paths` | — | [`packages/util/home-paths/src/index.ts`](../packages/util/home-paths/src/index.ts) | -| `@deepseek-ai/dsh-hook-protocol` | — | [`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts) | +| `@deepseek-ai/dsh-hook-protocol` | — | [`packages/experimental/hook-protocol/src/index.ts`](../packages/experimental/hook-protocol/src/index.ts) | | `@deepseek-ai/dsh-http-proxy` | — | [`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts) | | `@deepseek-ai/dsh-launch-environment` | — | [`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts) | | `@deepseek-ai/dsh-lazy-require` | — | [`packages/util/lazy-require/src/index.ts`](../packages/util/lazy-require/src/index.ts) | @@ -4509,7 +4623,6 @@ export interface Config { | `@deepseek-ai/dsh-session-snapshot` | — | [`packages/test-support/session-snapshot/src/index.ts`](../packages/test-support/session-snapshot/src/index.ts) | | `@deepseek-ai/dsh-session-telemetry` | — | [`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts) | | `@deepseek-ai/dsh-session-title-llm` | — | [`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts) | -| `@deepseek-ai/dsh-subagent-in-process-driver` | — | [`packages/subagent/subagent-in-process-driver/src/index.ts`](../packages/subagent/subagent-in-process-driver/src/index.ts) | | `@deepseek-ai/dsh-timeout` | — | [`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts) | | `@deepseek-ai/dsh-typert-generator` | — | [`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts) | | `@deepseek-ai/dsh-typert-protocol` | — | [`packages/typert/protocol/src/index.ts`](../packages/typert/protocol/src/index.ts) | diff --git a/docs/cookbook/adding-a-package.i18n.yaml b/docs/cookbook/adding-a-package.i18n.yaml index a5f3e9fba3..013ecb4344 100644 --- a/docs/cookbook/adding-a-package.i18n.yaml +++ b/docs/cookbook/adding-a-package.i18n.yaml @@ -6,8 +6,8 @@ en: b94bcdbebcd6355e zh: 9fd61c2b986da759 /cookbook-adding-a-workspace-package/1-create-the-package: - en: 3499082415478cc5 - zh: 65d3120abd646b64 + en: 062249fc249b2fec + zh: f6b9fe7f63c4cbad /cookbook-adding-a-workspace-package/2-register-it-in-the-root-configs: en: 12b985dee93a47a7 zh: 1027db3d251cd860 @@ -18,11 +18,11 @@ en: 995813ee34abfbd6 zh: 328d78e65599e0cc /cookbook-adding-a-workspace-package/4-write-the-package-readme: - en: 519b4adbef7b0b91 - zh: b0b857ec6ee50ad7 + en: 0894bd9f35430753 + zh: 2d28090e71134edd /cookbook-adding-a-workspace-package/5-add-optional-plugin-display-metadata: - en: c883c460403c2b15 - zh: 07f3269cb0d404dd + en: 2382017c7f84ace6 + zh: 03af99592baaab9f /cookbook-adding-a-workspace-package/6-verify: en: 80c7fb90b1175aae zh: 2ba37962dbc57299 diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 60b5d6e047..af3547ea1f 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -24,7 +24,7 @@ packages/// Choose an existing group when one matches the package's role (`core`, `llm`, `shell`, `compaction`, `subagent`, `todo`, `session`, `client`/`host`, `util`, or `test-support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. -package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, a `version` matching the root `package.json`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `@deepseek-ai/cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `@deepseek-ai/schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list contains exactly `lib/index.js`, `lib/types/**/*.d.ts`, and package-specific runtime artifacts recognized by the gate; a package that publishes `./invariant` also includes `lib/invariant.js`. A package whose runtime export points into the emitted tree also includes `lib/types/**/*.js`. Do not publish `src`, declaration maps, JS maps, or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. +package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, a `version` matching the root `package.json`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `@deepseek-ai/cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `@deepseek-ai/schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list contains exactly `lib/index.js`, `lib/types/**/*.d.ts`, and package-specific runtime artifacts recognized by the gate. A package whose runtime export points into the emitted tree also includes `lib/types/**/*.js`. Do not publish `src`, declaration maps, JS maps, or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. In-package relative imports use explicit `.ts` specifiers in source (for example, `export * from './types.ts'`). The compiler rewrites those to `.js` in emitted JS and leaves explicit `.ts` specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling `.d.ts` files. @@ -107,7 +107,7 @@ Append-only, prefix-stable, replacing, or independent behavior, including the ex Fill Model Experience from the implementation. Use one H3 per direct, conditional, capped, lifetime, or auxiliary model-context entry, with the three ordered H4 fields shown above and one prose paragraph under each. Quote stable text owned by the package: system-prompt prose goes in a titled H5 plus `markdown` fence under the field that introduces it—normally `What the model sees`—other short literals stay inline with named placeholders, and other long literals use the same nested form. Summarize only data-dependent or provider-owned text. A tool-schema entry links its anchored section in the generated [tool catalog](../tool-catalog.md) and states only deltas absent there. Keep prompt and schema entries separate when scoping can hide one without the other. In `KV Cache effect`, distinguish append-only growth, a stable repeated prefix, replacement of earlier request tokens, and an independent model request, then name the package-owned changes that can invalidate reuse. “Does not invalidate” means the package preserves an already-reusable prefix; provider cache availability and eviction remain outside the package contract. The [prose standard](../../.agents/skills/dsh-prose-standard/SKILL.md) governs completeness and ownership; the verifier enforces the required section structure. -A package with no context effect or one consumer-owned path uses the audited `None, as ` or `Indirectly, through ` sentence in [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts), followed by a `KV Cache effect` H4 and one non-empty paragraph; a model-agnostic generic package may instead join `NO_MODEL_EXPERIENCE_SECTION`. Do not expand either case into a description of another package's work. The limitations [allowlist](../../scripts/verify-package-readme-limitations.ts) is independent. The [Model Experience Agent Note](../../.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale. +A package with no context effect or one consumer-owned path uses the audited `None, as ` or `Indirectly, through ` sentence in [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts), followed by a `KV Cache effect` H4 and one non-empty paragraph; a model-agnostic generic package may instead join `NO_MODEL_EXPERIENCE_SECTION`. Do not expand either case into a description of another package's work. The limitations [allowlist](../../scripts/verify-package-readme-limitations.ts) is independent. The [archived Model Experience Agent Note](../../.agents/notes/archived/process/2026-07-12-package-model-experience-contract.md) records the rationale. @@ -140,12 +140,12 @@ Keep language files together in `locale/`, with `en.json` as the discovery entry Fields fall back independently before view-specific name formatting, using the existing locale language chain first: -- Title: locale `meta.title` → `package.json.name` → complete Cordis plugin name. -- Description: locale `meta.description` → `package.json.description` → no description. +- Title: locale `meta.title` → `package.json.name` for a package-root plugin → complete Cordis plugin name. +- Description: locale `meta.description` → `package.json.description` for a package-root plugin → no description. -Export `/locale/en.json` for locale lookup; expose `/package.json` for package-field fallback or an icon declaration. +Export `/locale/en.json` for locale lookup; expose `/package.json` for package-field fallback or an icon declaration. A subpath plugin such as `/search` is not a package and never reads a `package.json`, even one exported at `./search/package.json`; it uses `./search/locale/*.json` for text and `./search/icon` for its image. -For an image on bundle cards, details, and component rows, set top-level `"icon": "./icon.svg"` in that exported manifest and include the image in `files`. The path is relative to the declaring manifest's directory, including for independently exported plugin manifests. SVG, PNG, JPEG (`.jpg`/`.jpeg`), and WebP files are supported up to 256 KiB. Absolute paths, URLs, paths outside that directory, and symlinks resolving outside it are rejected. Images need no separate export and must be self-contained; SVG is rendered as an image, not inline HTML. The Host returns a data URL without activating the plugin. Invalid declarations or unreadable files produce a metadata diagnostic while retaining valid text; missing or undecodable images use the panel's default artwork. +For an image on bundle cards, details, and component rows, a package-root plugin sets top-level `"icon": "./icon.svg"` in its exported manifest, or exports `./icon` when that field is omitted. A subpath plugin exports its own icon, such as `"./search/icon": "./search.svg"`. Include each image in `files`. A manifest `icon` is relative to the manifest's directory; absolute paths, URLs, paths outside that directory, and symlinks resolving outside it are rejected. An exported icon resolves through Node exports like locale resources and must remain inside its package. SVG, PNG, JPEG (`.jpg`/`.jpeg`), and WebP files are supported up to 256 KiB. Images must be self-contained; SVG is rendered as an image, not inline HTML. The Host returns a data URL without activating the plugin. Invalid declarations or unreadable files produce a metadata diagnostic while retaining valid text, without trying the export; missing or undecodable images use the panel's default artwork. Installed bundle cards and details, component lists and configuration details, and Settings' plugin inventory display this metadata, including disabled and preset plugins. Reads do not activate plugins. @@ -158,8 +158,6 @@ Verify the result: 1. Run `pnpm run verify-package-meta` from the repository root to check fields, resource exports, and publication coverage. 2. Switch an installed plugin's applicable Plugin Manager and Settings entries between English and Chinese; check the title, description, per-field fallback, and Settings-only compact technical names. -See the [plugin metadata Agent Note](../../.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.md) for resource ownership and non-activation rationale. - ## 6. Verify ```sh diff --git a/docs/cookbook/adding-a-package.zh.md b/docs/cookbook/adding-a-package.zh.md index 44a613bcf0..c45632d3e7 100644 --- a/docs/cookbook/adding-a-package.zh.md +++ b/docs/cookbook/adding-a-package.zh.md @@ -24,7 +24,7 @@ packages/// 当已有分组与包的角色匹配时,选择该分组(`core`、`llm`、`shell`、`compaction`、`subagent`、`todo`、`session`、`client`/`host`、`util` 或 `test-support`)。允许新建分组,但分组只是纯容器:没有 `package.json`,没有源文件,包仍然恰好位于其下一层。 -package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制执行):`private: true`,`version` 与根 `package.json` 一致,`type: module`,`main: "lib/index.js"`,`types: "lib/types/index.d.ts"`,`exports["."].types: "./lib/types/index.d.ts"`,`exports["."].default: "./lib/index.js"`,`@deepseek-ai/cordis` 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。`@deepseek-ai/schemastery` 放在 `dependencies` 中(它是运行时校验器),与 agent-loop 保持一致。`files` 列表精确包含 `lib/index.js`、`lib/types/**/*.d.ts` 以及门禁认可的包专用运行时产物;发布 `./invariant` 的包还要包含 `lib/invariant.js`。如果包的运行时 export 指向输出树,还要包含 `lib/types/**/*.js`。不要发布 `src`、声明映射、JS map 或陈旧的根声明文件。带有 `bin` 的 CLI 应用包在 `files` 中将 `lib/bin.js` 紧跟在 `lib/index.js` 之后。 +package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-constraints.ts` 强制执行):`private: true`,`version` 与根 `package.json` 一致,`type: module`,`main: "lib/index.js"`,`types: "lib/types/index.d.ts"`,`exports["."].types: "./lib/types/index.d.ts"`,`exports["."].default: "./lib/index.js"`,`@deepseek-ai/cordis` 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。`@deepseek-ai/schemastery` 放在 `dependencies` 中(它是运行时校验器),与 agent-loop 保持一致。`files` 列表精确包含 `lib/index.js`、`lib/types/**/*.d.ts` 以及门禁认可的包专用运行时产物。如果包的运行时 export 指向输出树,还要包含 `lib/types/**/*.js`。不要发布 `src`、声明映射、JS map 或陈旧的根声明文件。带有 `bin` 的 CLI 应用包在 `files` 中将 `lib/bin.js` 紧跟在 `lib/index.js` 之后。 包内的相对导入在源码中使用显式 `.ts` 后缀(例如 `export * from './types.ts'`)。编译器在输出的 JS 中将其重写为 `.js`,在声明文件中保留显式 `.ts` 后缀;标准的 NodeNext/Node16 TypeScript 消费方会将其解析到同目录的 `.d.ts` 文件。 @@ -107,7 +107,7 @@ Append-only, prefix-stable, replacing, or independent behavior, including the ex 根据实现填写 Model Experience。每个直接、条件、上限、生命周期或辅助的模型上下文条目使用一个 H3,包含上述三个有序 H4 字段,每个字段下有一个正文段落。引用包拥有的稳定文本:系统提示词放在引出它的字段下,用带标题的 H5 加 `markdown` 围栏表示,通常归入 `What the model sees`;其他短文本以命名占位符内联,其他长文本使用相同的嵌套形式。仅概述数据依赖或提供方拥有的文本。工具 schema 条目链接到生成的[工具目录](../tool-catalog.zh.md)中对应的锚定章节,仅说明该处缺失的差异。当作用域可以隐藏 prompt 或 schema 其中之一而不影响另一个时,将二者分开。填写 `KV Cache effect` 时,应区分仅追加增长、稳定重复的前缀、替换既有请求 token 和独立模型请求,并列出会使缓存复用失效、且由本包拥有的变化。“不使缓存失效”仅表示本包保留了已有的可复用前缀;缓存是否可用以及何时淘汰不属于本包约定。[行文标准](../../.agents/skills/dsh-prose-standard/SKILL.md)约束完整性与归属;验证器强制执行所需章节结构。 -没有上下文效果或仅有消费方拥有路径的包使用 [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) 中经过审计的 `None, as ` 或 `Indirectly, through ` 语句,随后添加 `KV Cache effect` H4 和一个非空正文段落;与模型无关的通用包可以改为加入 `NO_MODEL_EXPERIENCE_SECTION`。两种情况都不要展开为对另一个包工作的描述。limitations [allowlist](../../scripts/verify-package-readme-limitations.ts) 独立管理。[Model Experience Agent Note](../../.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.zh.md) 记录了设计动机。 +没有上下文效果或仅有消费方拥有路径的包使用 [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) 中经过审计的 `None, as ` 或 `Indirectly, through ` 语句,随后添加 `KV Cache effect` H4 和一个非空正文段落;与模型无关的通用包可以改为加入 `NO_MODEL_EXPERIENCE_SECTION`。两种情况都不要展开为对另一个包工作的描述。limitations [allowlist](../../scripts/verify-package-readme-limitations.ts) 独立管理。[已归档的Model Experience Agent Note](../../.agents/notes/archived/process/2026-07-12-package-model-experience-contract.md) 记录了设计动机。 @@ -140,12 +140,12 @@ Append-only, prefix-stable, replacing, or independent behavior, including the ex 各字段先独立回退,再按页面规则格式化技术名称;回退优先经过现有 locale 语言链: -- 标题:locale `meta.title` → `package.json.name` → 完整 Cordis 插件名。 -- 描述:locale `meta.description` → `package.json.description` → 不显示描述。 +- 标题:locale `meta.title` → 包根插件的 `package.json.name` → 完整 Cordis 插件名。 +- 描述:locale `meta.description` → 包根插件的 `package.json.description` → 不显示描述。 -导出 `<包名>/locale/en.json` 供 locale 查询;需要包字段回退或声明图标时,开放 `<包名>/package.json`。 +导出 `<包名>/locale/en.json` 供 locale 查询;需要包字段回退或声明图标时,开放 `<包名>/package.json`。`<包名>/search` 这样的子路径插件不是包,从不读取 `package.json`,即使导出了 `./search/package.json`;它用 `./search/locale/*.json` 提供文本,用 `./search/icon` 提供图片。 -要在组合包卡片、详情和组件行显示图片,在该导出清单顶层设置 `"icon": "./icon.svg"`,并将图片加入 `files`。路径相对于声明清单所在目录,独立导出的插件清单也遵循此规则。支持不超过 256 KiB 的 SVG、PNG、JPEG(`.jpg`/`.jpeg`)和 WebP 文件。绝对路径、URL、目录外路径,以及解析到目录外的符号链接均被拒绝。图片不需要单独导出,且必须自包含;SVG 作为图片渲染,不作为内联 HTML。Host 返回 data URL,不激活插件。声明无效或文件不可读时,显示元信息诊断并保留有效文本;图片缺失或无法解码时使用面板的默认插画。 +要在组合包卡片、详情和组件行显示图片,包根插件在导出清单顶层设置 `"icon": "./icon.svg"`,或在省略该字段时导出 `./icon`。子路径插件导出自己的图标,例如 `"./search/icon": "./search.svg"`。将每张图片加入 `files`。清单 `icon` 相对于清单所在目录;绝对路径、URL、目录外路径,以及解析到目录外的符号链接均被拒绝。导出的图标像 locale 资源一样经 Node exports 解析,且必须位于所属包内。支持不超过 256 KiB 的 SVG、PNG、JPEG(`.jpg`/`.jpeg`)和 WebP 文件。图片必须自包含;SVG 作为图片渲染,不作为内联 HTML。Host 返回 data URL,不激活插件。声明无效或文件不可读时,显示元信息诊断并保留有效文本,不再尝试导出的图标;图片缺失或无法解码时使用面板的默认插画。 已安装 bundle 的卡片和详情、组件列表与配置详情、设置中的插件清单都展示这些元信息,包括禁用插件和预设内插件。读取时不激活插件。 @@ -158,8 +158,6 @@ Install 界面仍使用 `pnpm view` 返回的 npm registry 信息,不用 local 1. 在仓库根目录运行 `pnpm run verify-package-meta`,检查字段、资源 exports 和发布文件覆盖。 2. 对已安装插件,在适用的插件管理页与设置条目中切换中英文,检查标题、描述、逐字段回退,以及仅设置页使用的技术短名。 -资源归属与不激活插件的原因见[插件元信息 Agent Note](../../.agents/notes/implemented/architecture/2026-09-18-localized-package-metadata.zh.md)。 - ## 6. 验证 ```sh diff --git a/docs/cookbook/adding-a-session-format-version.i18n.yaml b/docs/cookbook/adding-a-session-format-version.i18n.yaml index f0c70b2798..e37df25963 100644 --- a/docs/cookbook/adding-a-session-format-version.i18n.yaml +++ b/docs/cookbook/adding-a-session-format-version.i18n.yaml @@ -18,8 +18,8 @@ en: 77c9c26d94fb33ca zh: 313c54c45cfc7dbc /cookbook-adding-a-session-log-format-version/3-implement-per-artifact-stages-and-validation: - en: 2bea8b93ac9b359a - zh: 2c24e71680f66ef7 + en: c8d879d5c9fb14ed + zh: ea19e54c808fea0b /cookbook-adding-a-session-log-format-version/4-update-current-version-consumers: en: a285126945d02c12 zh: e039c77cf7866ad2 diff --git a/docs/cookbook/adding-a-session-format-version.md b/docs/cookbook/adding-a-session-format-version.md index b962ffb1c1..7c6d08b993 100644 --- a/docs/cookbook/adding-a-session-format-version.md +++ b/docs/cookbook/adding-a-session-format-version.md @@ -58,6 +58,8 @@ Treat the inherited cut as a logical event count, not a physical row count. Expo Define the new edge's event admission and transformation rules explicitly. The [V2-to-V3 source audit](../../packages/session/session-format-v2-to-v3/README.md#source-audit) and [alpha V0→V1 rule](../../.agents/notes/implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.md) own the policies of those released edges, not the new edge. Do not generalize either to every edge. A change to structure or event positions requires classifying source events, payload members, and references, and explicitly deciding whether opaque data can remain valid. [Equal-version retention](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md) alone does not prove a structural transformation safe. Validate target semantics and give each newly accepted case a rejecting counterexample; never widen older edges to hide an unsupported transformation. +Plugin records that `appendPluginRecord()` writes are ignorable events in the `plugin:` namespace, which experimental packages read back by type. Carry them forward under their names when the target format can represent them, and state in the edge README which records it drops otherwise; the [ignorable events decision](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md) keeps them on a best-effort basis. Apply this policy even when a stored record has no declaration in the [experimental persistence catalog](../experimental-persistence-catalog.md). + Use native writer output as the conversion oracle: replay the same recorded LLM messages, tool outputs, and other inputs through the old and target writers. Compare migrated old output with raw target output before any format-normalizing helper; mask only documented volatile fields. For V4, ordinary tool results become flat tool-role messages with ordinary content blocks. A theoretically permitted old structure alone does not justify a new core content type. Alpha converters may explicitly refuse historical cases without an implemented, evidenced mapping, including otherwise valid logs. Keep the source unchanged and publish no partial successor. Prefer real corpora and reproducible writers when extending support; inspect user corpora read-only and create sanitized regression fixtures. diff --git a/docs/cookbook/adding-a-session-format-version.zh.md b/docs/cookbook/adding-a-session-format-version.zh.md index 398d77addd..bfadeab878 100644 --- a/docs/cookbook/adding-a-session-format-version.zh.md +++ b/docs/cookbook/adding-a-session-format-version.zh.md @@ -58,6 +58,8 @@ pnpm run gen-session-format-catalog 显式定义新迁移边的事件准入与变换规则。[V2 到 V3 源审计](../../packages/session/session-format-v2-to-v3/README.zh.md#source-audit)和 [Alpha V0→V1 规则](../../.agents/notes/implemented/architecture/2026-08-31-alpha-historical-unknown-event-refusal.zh.md)分别负责对应已发布迁移边的策略,而非新迁移边的策略。不要将任一策略推广到所有迁移边。结构或事件位置变化时,必须分类源事件、载荷成员与引用,并显式判断不透明数据能否保持有效。[同版本保留](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md)本身不能证明结构变换安全。校验目标语义,并为每个新增可接受案例提供一个被拒绝的反例;绝不放宽旧迁移边来掩盖不受支持的转换。 +`appendPluginRecord()` 写入的插件记录是 `plugin:` 命名空间中的 ignorable 事件,实验性包按类型把它们读回。目标格式能够表示它们时,以原名称继续携带;否则在迁移边 README 中说明它丢弃哪些记录。[ignorable 事件决策](../../.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.zh.md)以尽力而为的方式保留这些记录。即使已存记录在[实验性持久化目录](../experimental-persistence-catalog.zh.md)中没有声明,也适用这一策略。 + 以原生 writer 输出作为转换对照:用相同录制的 LLM 消息、工具输出和其他输入回放旧 writer 与目标 writer。任何格式归一化 helper 运行前,比较迁移后的旧输出与目标原始输出,只屏蔽已说明的易变字段。V4 的普通工具结果变为包含普通内容块的平铺 tool-role 消息。旧类型理论上允许某种结构,本身不足以支持新增核心内容类型。 Alpha 转换器可以明确拒绝尚无已实现、有证据映射的历史场景,包括原本有效的日志。源文件保持不变,且不发布部分 successor。扩展支持时优先使用真实语料和可复现 writer;只读检查用户语料,并创建脱敏回归 fixture。 diff --git a/docs/cookbook/adding-a-tool.i18n.yaml b/docs/cookbook/adding-a-tool.i18n.yaml index ccfb5a1d66..70e290a43c 100644 --- a/docs/cookbook/adding-a-tool.i18n.yaml +++ b/docs/cookbook/adding-a-tool.i18n.yaml @@ -12,8 +12,8 @@ en: 0b85f46f6a5bd8af zh: 56eb8d05198333dc /tool-authoring-reference/long-running-work: - en: d81167332dfd05b7 - zh: 1c878b60f70728c2 + en: 5f6b0c15cb91e21b + zh: 633a2192df186ac3 /tool-authoring-reference/execution-policy-and-observation: en: 9f894e50199d7ec5 zh: 934e4526f34c27fe diff --git a/docs/cookbook/adding-a-tool.md b/docs/cookbook/adding-a-tool.md index a94fc497f5..ebef4b6b03 100644 --- a/docs/cookbook/adding-a-tool.md +++ b/docs/cookbook/adding-a-tool.md @@ -52,7 +52,7 @@ Registration is effect-based: disposing the plugin fiber unregisters the tool. S Gate `run_in_background` with producer config, then register through `ctx.jobs.start({ kind, label, owner: exec.agent, run })`. The registry rejects a pre-aborted invocation before the producer body; the runtime validates ownership and task-controller availability before `run()` starts work, then supplies the id, session fence, generic control tools, notices, and owner cleanup. A successful background branch returns a typed canonical handle such as `{ kind: 'background', jobId }`; its Native renderer may keep human prose such as `started background job bash-1`, but PTC mode must never parse that prose to recover the id. -The spec supplies synchronous `cancel`, a non-rejecting `done` that settles after resource cleanup, and either pull `output` sources the registry pumps into the job's output ring or pushes through the `JobHandle` its starter receives; `dsh-tool-jobs` renders the model's consuming read from that ring. A pre-aborted call is a failure because no task exists whose id could satisfy the successful output schema. Once `ctx.jobs.start()` publishes the id, use a task-owned cancellation signal rather than `exec.signal`: later outer-call cancellation stops waiting for the call but does not kill published work; `job_kill`, owner disposal, and service teardown own that lifetime. Foreground work remains coupled to `exec.signal`. See the [background job runtime Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and `dsh-tool-bash` for a stream producer. +The spec supplies synchronous `cancel`, a non-rejecting `done` that settles after resource cleanup, and either pull `output` sources the registry pumps into the job's output ring or pushes through the `JobHandle` its starter receives; `dsh-tool-jobs` renders the model's consuming read from that ring. A pre-aborted call is a failure because no task exists whose id could satisfy the successful output schema. Once `ctx.jobs.start()` publishes the id, use a task-owned cancellation signal rather than `exec.signal`: later outer-call cancellation stops waiting for the call but does not kill published work; `job_kill`, owner disposal, and service teardown own that lifetime. Foreground work remains coupled to `exec.signal`. See the [background job runtime reference](../../packages/jobs/jobs/README.md) and `dsh-tool-bash` for a stream producer. ## Execution policy and observation diff --git a/docs/cookbook/adding-a-tool.zh.md b/docs/cookbook/adding-a-tool.zh.md index 1050ccd6f3..28329e6382 100644 --- a/docs/cookbook/adding-a-tool.zh.md +++ b/docs/cookbook/adding-a-tool.zh.md @@ -52,7 +52,7 @@ export function apply(ctx: Context) { 通过 producer 配置控制 `run_in_background`,然后使用 `ctx.jobs.start({ kind, label, owner: exec.agent, run })` 注册任务。注册表会在进入 producer 主体前将已预先中止的调用判为失败;运行时会在 `run()` 启动工作前校验 owner 和任务控制器是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。成功的后台分支会返回类型化的规范句柄,如 `{ kind: 'background', jobId }`;其 Native 渲染器可以保留 `started background job bash-1` 这类供人阅读的自然语言,但 PTC mode 绝不能通过解析该文本取得 id。 -spec 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及由注册表泵入 job 输出环的拉取式 `output` 源或经 starter 收到的 `JobHandle` 推送的追加;模型的消费式读取由 `dsh-tool-jobs` 从该环渲染。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.jobs.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `job_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md)与 `dsh-tool-bash`。 +spec 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及由注册表泵入 job 输出环的拉取式 `output` 源或经 starter 收到的 `JobHandle` 推送的追加;模型的消费式读取由 `dsh-tool-jobs` 从该环渲染。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.jobs.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `job_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 reference](../../packages/jobs/jobs/README.zh.md)与 `dsh-tool-bash`。 diff --git a/docs/cookbook/extension-cookbook.i18n.yaml b/docs/cookbook/extension-cookbook.i18n.yaml index f65dda80a5..e841296f69 100644 --- a/docs/cookbook/extension-cookbook.i18n.yaml +++ b/docs/cookbook/extension-cookbook.i18n.yaml @@ -21,5 +21,5 @@ en: f0a28d07c0bb4d1b zh: 840962f512cb4031 /cookbook-extension-plugin-shapes/the-feature-mechanism-map: - en: 0504676a42dd1445 - zh: b33fba88ec2ace9c + en: fa917d46c071fad2 + zh: 4062805237e2c847 diff --git a/docs/cookbook/extension-cookbook.md b/docs/cookbook/extension-cookbook.md index 736a18b791..006b6b7092 100644 --- a/docs/cookbook/extension-cookbook.md +++ b/docs/cookbook/extension-cookbook.md @@ -97,7 +97,7 @@ Shipped applications contribute profile layers through `packages/bundle/*/cordis ## The feature → mechanism map -Every product feature maps to a listener on a documented extension point — the microkernel claim made checkable ([microkernel Agent Note](../../.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md)). No row modifies the loop. +Every product feature maps to a listener on a documented extension point — the microkernel claim made checkable ([microkernel reference](../architecture.md)). No row modifies the loop. `system-prompt/assemble` is an expert cooperative whole-assembly transform: its returned assembly is authoritative, so listener authors own preserving active PTC mode and structured-output protocol contributions. Prefer `ctx.tools.restrict()` for tool filtering that must stay aligned across presentation, lookup, and execution. @@ -108,7 +108,7 @@ Every product feature maps to a listener on a documented extension point — the | `/loop` | on the `turn/end` session event, `followup()` the next iteration; or force-continue | | Dynamic workflow | `ctx.workflowEngine` + the PTC workflow engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and the structured-output execution's monotonic `concludeTurn()` marker | | Queued + steering messages | core `Agent.followup()` / `Agent.steer()` | -| Context compaction (auto + manual) | the `ctx.compaction` seam + `dsh-compaction-basic`; automatic pressure runs on serial `agent/pre-step`, canonical overflow recovery runs on `agent/request-error`, and manual callers use the same compact service ([compaction Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)) | +| Context compaction (auto + manual) | the `ctx.compaction` seam + `dsh-compaction-basic`; automatic pressure runs on serial `agent/pre-step`, canonical overflow recovery runs on `agent/request-error`, and manual callers use the same compact service ([compaction reference](../../packages/compaction/compaction/README.md)) | | System prompt configurability | `ctx.systemPrompt.section()` with ordering and scope-local shadowing | | AGENTS.md (root) | a section provider reading the file | | AGENTS.md (subdir, on-touch) + file-change notices | `agent.inject()` from a watcher / tool-result listener | diff --git a/docs/cookbook/extension-cookbook.zh.md b/docs/cookbook/extension-cookbook.zh.md index 77d10bfba6..826510f6a5 100644 --- a/docs/cookbook/extension-cookbook.zh.md +++ b/docs/cookbook/extension-cookbook.zh.md @@ -101,7 +101,7 @@ export function apply(ctx: Context) { ## 功能→机制映射 -每个产品功能都映射到一个文档化扩展点上的监听器——微内核声明由此可验证([微内核 Agent Note](../../.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md))。没有任何一行修改循环本身。 +每个产品功能都映射到一个文档化扩展点上的监听器——微内核声明由此可验证([微内核 reference](../architecture.zh.md))。没有任何一行修改循环本身。 `system-prompt/assemble` 是一个专家协作式的整体装配变换:其返回的装配结果具有权威性,因此监听器作者有责任保留活跃的 PTC mode 和结构化输出协议的贡献。对于需要在展示、查找和执行之间保持对齐的工具过滤,优先使用 `ctx.tools.restrict()`。 @@ -112,7 +112,7 @@ export function apply(ctx: Context) { | `/loop` | 在 `turn/end` 会话事件上 `followup()` 下一次迭代;或强制继续 | | 动态工作流 | `ctx.workflowEngine` + PTC 工作流引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的提示词/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 | | 排队消息 + steering | 核心 `Agent.followup()` / `Agent.steer()` | -| 上下文压缩(context compaction)(自动 + 手动) | `ctx.compaction` seam + `dsh-compaction-basic`;自动压力检查运行在串行 `agent/pre-step`,标准的溢出恢复机制运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md)) | +| 上下文压缩(context compaction)(自动 + 手动) | `ctx.compaction` seam + `dsh-compaction-basic`;自动压力检查运行在串行 `agent/pre-step`,标准的溢出恢复机制运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 参考](../../packages/compaction/compaction/README.zh.md)) | | 系统提示词可配置性 | `ctx.systemPrompt.section()`,支持排序与作用域局部覆盖 | | AGENTS.md(根目录) | 一个读取该文件的 section 提供方 | | AGENTS.md(子目录,按需触发)+ 文件变更通知 | 从 watcher / 工具结果监听器调用 `agent.inject()` | diff --git a/docs/cookbook/reviewing-persistence-type-changes.md b/docs/cookbook/reviewing-persistence-type-changes.md index bf3810453c..15b6c9fab9 100644 --- a/docs/cookbook/reviewing-persistence-type-changes.md +++ b/docs/cookbook/reviewing-persistence-type-changes.md @@ -57,12 +57,12 @@ Save the input as `.artifacts/persistence-change.prose.json`, creating the ignor "en": { "summary": "Makes the persisted hook audit decision optional.", "compatibility": "Existing records remain valid. Hook execution consumes HookOutput instead of replaying this audit field. Producers still write decisions, and absence does not imply pass.", - "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts: 10 tests passed." + "verification": "pnpm exec vitest run packages/experimental/hook-protocol/tests/events.spec.ts: 10 tests passed." }, "zh": { "summary": "将持久化的钩子审计决策改为可选。", "compatibility": "已有记录仍然有效。钩子执行消费 HookOutput,不回放此审计字段。写入方仍然记录决策,缺失不代表 pass。", - "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts:10 个测试通过。" + "verification": "pnpm exec vitest run packages/experimental/hook-protocol/tests/events.spec.ts:10 个测试通过。" } } ``` diff --git a/docs/cookbook/reviewing-persistence-type-changes.zh.md b/docs/cookbook/reviewing-persistence-type-changes.zh.md index 4e31658b6b..ed82decae9 100644 --- a/docs/cookbook/reviewing-persistence-type-changes.zh.md +++ b/docs/cookbook/reviewing-persistence-type-changes.zh.md @@ -57,12 +57,12 @@ pnpm --silent run persistence-review --before .artifacts/base.schema.json --afte "en": { "summary": "Makes the persisted hook audit decision optional.", "compatibility": "Existing records remain valid. Hook execution consumes HookOutput instead of replaying this audit field. Producers still write decisions, and absence does not imply pass.", - "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts: 10 tests passed." + "verification": "pnpm exec vitest run packages/experimental/hook-protocol/tests/events.spec.ts: 10 tests passed." }, "zh": { "summary": "将持久化的钩子审计决策改为可选。", "compatibility": "已有记录仍然有效。钩子执行消费 HookOutput,不回放此审计字段。写入方仍然记录决策,缺失不代表 pass。", - "verification": "pnpm exec vitest run packages/hooks/hook-protocol/tests/events.spec.ts:10 个测试通过。" + "verification": "pnpm exec vitest run packages/experimental/hook-protocol/tests/events.spec.ts:10 个测试通过。" } } ``` diff --git a/docs/deepseek-llm-api-wire-extensions.i18n.yaml b/docs/deepseek-llm-api-wire-extensions.i18n.yaml index 64bde5b974..ca1ec47310 100644 --- a/docs/deepseek-llm-api-wire-extensions.i18n.yaml +++ b/docs/deepseek-llm-api-wire-extensions.i18n.yaml @@ -15,8 +15,8 @@ en: f0b1443c364a431c zh: 16d16ddf5c451e8e /official-deepseek-llm-api-wire-extensions/dsh-plugin-packages: - en: 49d6f011e9d985a7 - zh: bbf61b4e4480c555 + en: f89059050613f3e8 + zh: da5cf2a4eeb81b75 /official-deepseek-llm-api-wire-extensions/dsh-session-log: en: de1a3f2c377d74e3 zh: 7c803345f71117c5 diff --git a/docs/deepseek-llm-api-wire-extensions.md b/docs/deepseek-llm-api-wire-extensions.md index 790b51fd88..216725d71d 100644 --- a/docs/deepseek-llm-api-wire-extensions.md +++ b/docs/deepseek-llm-api-wire-extensions.md @@ -61,11 +61,11 @@ After the configured endpoint returns HTTP 2xx, the adapter runs the prepared `a | `version` | `1` | Schema version for `dsh_plugin_packages` | | `packages` | array | Complete active set for this request | | `packages[].name` | string | Exact non-empty npm package name from the owning manifest | -| `packages[].version` | string | Exact non-empty package version from the same manifest | +| `packages[].version` | optional string | Exact non-blank package version from the same manifest; absent when unavailable | -Every request re-reads active non-group Loader entries from the host tree and, when available for the request Session, its standing agent-preset tree. Relative and absolute modules use their nearest owning manifest; bare package entries follow the Loader resolution base that activated them. A named manifest without a non-empty version fails request preparation. +Every request re-reads active non-group Loader entries from the host tree and, when available for the request Session, its standing agent-preset tree. Relative and absolute modules use their nearest owning manifest; bare package entries follow the Loader resolution base that activated them. A named manifest without a non-blank string version contributes `{ name }`, regardless of `private`. Missing names and unreadable manifests omit only the affected entries; package metadata does not block requests. -The sender deduplicates exact `(name, version)` pairs and sorts first by `name`, then by `version`, with a locale-independent text comparison. Simultaneously active versions of one package remain separate entries. Receivers must not collapse the array by package name or infer package activation from array order. +The sender deduplicates exact `(name, optional version)` pairs and sorts first by `name`, then by `version`, with a locale-independent text comparison and absent versions first. Simultaneously active versions of one package remain separate entries, including a name-only entry. Receivers must accept absent versions, must not collapse the array by package name, and must not infer package activation from array order. Disabled, pending, failed, unloading, disposed, and structural Loader entries are absent. Ordinary dependencies, loose modules without a named owning package, programmatically mounted child fibers, and in-memory dynamic plugins are also absent because they have no authoritative Loader-backed package identity. diff --git a/docs/deepseek-llm-api-wire-extensions.zh.md b/docs/deepseek-llm-api-wire-extensions.zh.md index 08be493fcc..33c8908ae5 100644 --- a/docs/deepseek-llm-api-wire-extensions.zh.md +++ b/docs/deepseek-llm-api-wire-extensions.zh.md @@ -61,11 +61,11 @@ | `version` | `1` | `dsh_plugin_packages` 的 schema 版本 | | `packages` | 数组 | 本次请求的完整存活集合 | | `packages[].name` | 字符串 | 来自所属 manifest(元数据清单)的确切非空 npm 包名 | -| `packages[].version` | 字符串 | 来自同一 manifest 的确切非空包版本 | +| `packages[].version` | 可选字符串 | 来自同一 manifest 的确切非空白包版本;不可用时省略 | -每个请求都会重新读取宿主树中的存活非分组 Loader 配置项;请求会话存在 standing agent-preset 树时,也会读取该树。相对与绝对模块使用距离自身最近的所属 manifest;裸包配置项使用激活自身的 Loader 解析基准。具名 manifest 未提供非空版本时,请求准备会失败。 +每个请求都会重新读取宿主树中的存活非分组 Loader 配置项;请求会话存在 standing agent-preset 树时,也会读取该树。相对与绝对模块使用距离自身最近的所属 manifest;裸包配置项使用激活自身的 Loader 解析基准。具名 manifest 的版本不是非空白字符串时,会贡献 `{ name }`,不受 `private` 影响。名称缺失或 manifest 不可读时,只省略对应配置项;包元数据不会阻塞请求。 -发送方会对确切 `(name, version)` 组合去重,并使用与 locale 无关的文本比较,先按 `name`、再按 `version` 排序。同一包的多个同时存活版本会保留为独立配置项。接收方不得按包名折叠该数组,也不得根据数组顺序推断包的激活关系。 +发送方会对确切 `(name, 可选 version)` 组合去重,并使用与 locale 无关的文本比较,先按 `name`、再按 `version` 排序,缺失版本排在前面。同一包的多个同时存活版本会保留为独立配置项,包括只有名称的配置项。接收方必须接受版本缺失,不得按包名折叠该数组,也不得根据数组顺序推断包的激活关系。 该清单不包含已禁用、pending、failed、unloading、disposed 和结构性 Loader 配置项。普通依赖、没有具名所属包的松散模块、以编程方式挂载的子 fiber,以及内存动态插件也不在其中,因为它们没有权威的 Loader 包来源信息。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 3a2f7f09ce..2a08605a85 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -9,20 +9,20 @@ en: 5207d17725c07a27 zh: 258884c4300ac63a /development-guide/setup-tutorial/prerequisites: - en: 6078798f0fda1347 - zh: ea73704c4ec4f2a9 + en: ae47d95cd48bbb1d + zh: 2378e6ed3305e28d /development-guide/setup-tutorial/windows-and-wsl-2: en: e86be579be6f1813 zh: 14f6a220337d0861 /development-guide/setup-tutorial/first-time-setup: - en: f82b635837db9627 - zh: ca23888533b0ffc1 + en: bb2bab0ef17bfc32 + zh: 8b7cf7e9e703e6af /development-guide/contributor-reference: en: 3221d1076554001f zh: 29f166be6a5c04d9 /development-guide/contributor-reference/typescript-project-layout: - en: de9ab4baab0c5c91 - zh: cde7e8ab4f698fcb + en: e3327c5492a593cb + zh: d43a5edcb11523ae /development-guide/contributor-reference/environment-variables: en: dba2c72ee6ffa930 zh: b65c27fbb9129ec8 @@ -30,8 +30,8 @@ en: dc98eda4f4021feb zh: b08766708803cf10 /development-guide/contributor-reference/ci-gates: - en: 731fcaffe9210a72 - zh: d682e221a71b0ef6 + en: b64a914e41b1d285 + zh: 6c031b507c3c0e84 /development-guide/contributor-reference/daily-commands: en: 08aa6ec1cdabfeb7 zh: dcce5ae9d3c829d0 diff --git a/docs/development.md b/docs/development.md index f5b2a100d6..668beec326 100644 --- a/docs/development.md +++ b/docs/development.md @@ -9,7 +9,8 @@ The setup tutorial takes a new contributor from prerequisites to a checked check ### Prerequisites - Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md). -- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack. +- Node.js TypeScript type stripping enabled. The repository build scripts load `tsdown.config.ts` with tsdown's native config loader, so they fail when `--no-experimental-strip-types` is in `NODE_OPTIONS` or the Node.js build lacks TypeScript support; `pnpm run build` checks this first and names the cause. +- Corepack-enabled pnpm. The repo pins `pnpm@11.28.5` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack. - Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension. - Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests. @@ -23,6 +24,8 @@ Install dependencies separately in each environment because native binaries and ### First-time setup +pnpm uses its strict symlinked linker so undeclared dependencies fail instead of relying on hoisting. Keep dependency build scripts explicitly allowlisted in `pnpm-workspace.yaml`. + Install dependencies from the repo root: ```sh @@ -31,6 +34,8 @@ pnpm install The install also configures worktree-local Lefthook hooks through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract. +The [DevTools frontend](../packages/experimental/inspector/README.md#use-this-package) is compiled locally from a pinned npm source package and Vite. After dependencies are installed, its build needs no network access or browser installation. Workspace and published-package installation run no DevTools resource-download hook; published Inspector packages contain the built frontend. + If the hooks are missing because dependencies were restored from cache or `postinstall` was skipped, install them manually: ```sh @@ -80,13 +85,15 @@ tsdown --env.DSH_BUILD_FACE client pnpm run build:web ``` +`pnpm run build --artifacts-only` emits the same project and application artifacts while checking and compiling only the existing package references of each compiler face. Required Linux and Windows normal builds and public `typecheck` also check the repository-wide test and script programs. CI benchmark preparation additionally uses `--noCheck` for its disposable library outputs, relying on those required builds for diagnostics; other artifact builders keep package checking ([rationale](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.md)). + Both tsdown passes match `vendor/*`, `packages/*/*`, and `apps/cli`; the Host pass also matches `apps/desktop-host`. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase. Tsdown builds the matched workspace members concurrently, so `apps/desktop`, whose main bundle inlines workspace devDependencies from their `lib/` output, bundles in its own step after the Host pass ([Desktop README](../apps/desktop/README.md#bundled-workspace-dependencies)). Typert runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start Typert. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. `pnpm run build` embeds the root package version, the seven-character source commit, and a dirty marker when Git reports local changes; it also inherits other caller-supplied `DSH_CLIENT_*` values. `pnpm run build:official` is the cross-platform local equivalent of the CI and release artifact build and omits the local dirty marker. Each successful complete build writes a gitignored record that binds the exact public values to the Vite output and dynamic client bundles; release packing and built Web tests reject a missing record or artifacts changed by a later partial build. `pnpm run dev:web` runs that complete build first (`--skip-build` reuses an existing artifact tree instead), then samples the current version and Git state once and shares that environment across every watcher stage for the session; it does not validate the complete-build record because the watcher stages rewrite its recorded artifacts. -Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already depends on the Typert contract-generation pass or the complete build. See the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership and the [Typert Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract. +Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already depends on the Typert contract-generation pass or the complete build. See the [Typert Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract. Business services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. @@ -127,9 +134,9 @@ Contributors can opt into the comprehensive local gate set with `pnpm run check: ### CI gates -The keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. Required benchmarks run separately on standard GitHub-hosted Linux; the [benchmark runner decision](../.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.md) owns routing and the job timeout. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory. +The keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. Coverage partitions reuse file timings scoped to their platform and runner pool to balance work within the configured worker limit. Required benchmarks run separately on standard GitHub-hosted Linux; the [benchmark runner reference](../benchmarks/AGENTS.md) owns routing and the job timeout. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory. -The credential-free dsh dependency-layout and dsh/vendor pack rehearsals use the existing Linux self-hosted pool only when `DSH_CI_FAILOVER_LINUX=selfhosted` and the event is a trusted master push or same-repository, non-fork, non-Dependabot pull request. All other cases, including manual dispatch, use `ubuntu-24.04`; manual publication stays hosted. See the [release rehearsal runner decision](../.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.md) for persistent-store isolation and fallback limits. +The credential-free dsh dependency-layout and dsh/vendor pack rehearsals use the existing Linux self-hosted pool only when `DSH_CI_FAILOVER_LINUX=selfhosted` and the event is a trusted master push or same-repository, non-fork, non-Dependabot pull request. All other cases, including manual dispatch, use `ubuntu-24.04`; manual publication stays hosted. See the [release rehearsal runner reference](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md) for persistent-store isolation and fallback limits. ### Daily commands diff --git a/docs/development.zh.md b/docs/development.zh.md index c8cc93f6ea..3154c4d988 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -11,7 +11,8 @@ ### 前置条件 - Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md)。 -- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。 +- 启用 Node.js TypeScript 类型剥离。仓库构建脚本用 tsdown 的 native 配置加载器加载 `tsdown.config.ts`,因此当 `NODE_OPTIONS` 含 `--no-experimental-strip-types` 或 Node.js 构建缺少 TypeScript 支持时会失败;`pnpm run build` 会先检查这一条件并指出原因。 +- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.28.5`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。 - Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。 - 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。 @@ -25,6 +26,8 @@ ### 首次搭建 +pnpm 使用严格的符号链接布局,让未声明的依赖直接失败,而不是依赖提升。依赖的构建脚本必须明确列入 `pnpm-workspace.yaml` 的允许列表。 + 在仓库根目录安装依赖: ```sh @@ -33,6 +36,8 @@ pnpm install 安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.zh.md) 负责钩子路径的安全约定。 +[DevTools 前端](../packages/experimental/inspector/README.zh.md#use-this-package)通过固定版本的 npm 源码包和 Vite 在本地编译。依赖安装完成后,构建不需要网络连接或安装浏览器。workspace 与发布包的安装过程都不运行 DevTools 资源下载 hook;发布的 Inspector 包包含已构建的前端。 + 如果依赖是从缓存恢复或 `postinstall` 被跳过而导致钩子缺失,请手动安装: ```sh @@ -84,13 +89,15 @@ tsdown --env.DSH_BUILD_FACE client pnpm run build:web ``` +`pnpm run build --artifacts-only` 产出相同的 project 和应用产物,但只检查并编译各 compiler face 的现有包 reference。必需的 Linux 与 Windows 普通构建以及公开 `typecheck` 还检查仓库级测试和脚本 program。CI benchmark 准备阶段还对其临时 library 输出使用 `--noCheck`,由这些必需构建负责诊断;其他产物 builder 保留包检查([理由](../.agents/notes/implemented/process/2026-10-05-pr-artifact-typechecks.zh.md))。 + 两次 tsdown 都匹配 `vendor/*`、`packages/*/*` 与 `apps/cli`,Host 阶段另外匹配 `apps/desktop-host`;两者都不扫描构建产物来发现 Client 包,也不维护 Host/Client 包过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client 插件在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。tsdown 并发构建匹配到的 workspace 成员;`apps/desktop` 的主进程 bundle 需要从工作区 devDependencies 的 `lib/` 产物内联这些包,因此在 Host 阶段之后单独一步打 bundle([Desktop README](../apps/desktop/README.zh.md#bundled-workspace-dependencies))。 Typert 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 Typert。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。 `pnpm run build` 会内联根包版本、七位源码 commit,并在 Git 报告本地变化时内联 dirty 标记;调用方提供的其他 `DSH_CLIENT_*` 值也会被继承。`pnpm run build:official` 是与 CI 和 release 产物构建等价的跨平台本地命令,并省略本地 dirty 标记。每次完整构建成功后都会写入一份被 gitignore 的记录,把精确公开值与 Vite 输出及动态 client bundle 绑定;release 打包和 built Web 测试会拒绝缺少记录或被后续局部构建改动的产物。`pnpm run dev:web` 会先执行这次完整构建(`--skip-build` 则复用现有产物树),再读取一次当前版本和 Git 状态,并在本次会话的所有 watcher stage 之间共享该环境;它不会校验完整构建记录,因为 watcher stage 会重写记录覆盖的产物。 -静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本假定调用它的公共命令或调度器门禁已经依赖 Typert 约定生成阶段或完整构建。tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.zh.md),门禁准备约定见 [Typert Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md)。 +静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本假定调用它的公共命令或调度器门禁已经依赖 Typert 约定生成阶段或完整构建。门禁准备约定见 [Typert Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md)。 业务服务在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.zh.md)。 @@ -131,9 +138,9 @@ vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `v ### CI 门禁 -keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。必需 benchmark 在标准 GitHub 托管 Linux 上独立运行;[benchmark 运行器决策](../.agents/notes/implemented/testing/2026-09-06-standard-hosted-benchmark-runner.zh.md)拥有路由及 job 超时。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。 +keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。覆盖率分区复用按平台和运行器池隔离的文件耗时,在配置的 worker 上限内均衡工作量。必需 benchmark 在标准 GitHub 托管 Linux 上独立运行;[benchmark 运行器说明](../benchmarks/AGENTS.md)拥有路由及 job 超时。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。 -不带凭据的 dsh 依赖布局检查与 dsh/vendor 打包演练仅在 `DSH_CI_FAILOVER_LINUX=selfhosted`,且事件为受信任的 master 推送或同仓库、非 fork、非 Dependabot 拉取请求时使用现有 Linux 自托管池。其余情况(包括手动触发)均使用 `ubuntu-24.04`;手动发布仍使用托管运行器。持久化存储隔离与回退限制见[发布演练运行器决策](../.agents/notes/implemented/process/2026-09-06-release-rehearsal-selfhosted.zh.md)。 +不带凭据的 dsh 依赖布局检查与 dsh/vendor 打包演练仅在 `DSH_CI_FAILOVER_LINUX=selfhosted`,且事件为受信任的 master 推送或同仓库、非 fork、非 Dependabot 拉取请求时使用现有 Linux 自托管池。其余情况(包括手动触发)均使用 `ubuntu-24.04`;手动发布仍使用托管运行器。持久化存储隔离与回退限制见[发布演练运行器说明](../.agents/notes/implemented/process/2026-07-26-ci-failover-runbook.zh.md)。 ### 日常命令 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 86ce5c214c..5adc51ba90 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -11,25 +11,25 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:69`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/experimental/webhook) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:611`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:591`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:618`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:597`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:52`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | +| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:56`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` | -| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) | +| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` | | `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | | `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - | @@ -39,54 +39,54 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:401`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:371`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | -| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | -| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` | -| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | -| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | +| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | +| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | +| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:95`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | +| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:91`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | -| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` | +| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | -| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | +| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:77`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` | -| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:59`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | +| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:59`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `goal/activation-changed` | `emit` | [`packages/goal/goal/src/types.ts:150`](../packages/goal/goal/src/types.ts) | [`goal`](../packages/goal/goal) (`emit`) | `remotes` | | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:30`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`llm`](../packages/llm/llm), `remotes` | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | +| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:32`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | +| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:37`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | +| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` | -| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:248`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | -| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:254`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | -| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:261`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:270`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:277`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:478`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:55`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:77`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:86`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:82`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:174`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) | -| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:165`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) | -| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:166`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), `server` | +| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:142`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:158`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`working-directory`](../packages/session/working-directory) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | -| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | -| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:176`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | -| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:153`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:190`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:198`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:209`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:165`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `claude-code-mods`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | +| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:177`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | +| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:154`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | +| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | -| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workflow/log` | `emit` | [`packages/workflow/workflow/src/index.ts:58`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | | `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:51`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | -| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workspace/session-activity` | `waterfall` | [`packages/workspace/workspace/src/index.ts:129`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`waterfall`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | | `workspace/session-stop` | `parallel` | [`packages/workspace/workspace/src/index.ts:147`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`parallel`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | @@ -97,10 +97,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac | Event string | Dispatchers | Listeners | | --- | --- | --- | | `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | -| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) | | `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` | -| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` | -| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) | +| `internal/service` | - | `gateway` | +| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, `translator`, [`web`](../packages/web/web) | | `internal/update` | - | [`app-boot`](../packages/boot/app-boot) | | `loader/volatile-update` | - | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `product-analytics`, `speech-to-text` | | `slots/changed` | `ui-renderer` (`emit`) | - | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index c0150370fb..0133716910 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -13,25 +13,25 @@ | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:69`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:307`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/webhook/webhook) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:288`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:320`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:337`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent), [`webhook`](../packages/experimental/webhook) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:611`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:591`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:618`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:597`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | -| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:52`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | +| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:56`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) | | `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` | -| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) | +| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - | | `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` | | `compaction/summary-error` | `waterfall` | [`packages/compaction/compaction/src/index.ts:106`](../packages/compaction/compaction/src/index.ts) | [`compaction-basic`](../packages/compaction/compaction-basic) (`waterfall`) | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | | `connection/request` | `waterfall` | [`packages/client/connection/src/index.ts:67`](../packages/client/connection/src/index.ts) | `connection` (`waterfall`) | - | @@ -41,54 +41,54 @@ | `cordis/inspect-query-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:401`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:371`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | | `cordis/request-run-resolved` | `emit` | [`packages/extensions/cordis-host-runner/src/types.ts:377`](../packages/extensions/cordis-host-runner/src/types.ts) | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) (`emit`) | `remotes` | -| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:102`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | -| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` | -| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:90`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | -| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:86`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | +| `credentials/record-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:99`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization), [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform), `remotes` | +| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:87`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `remotes` | +| `deepseek-account/model-sign-in-required` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:95`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account`](../packages/credentials/deepseek-account) (`emit`) | `remotes` | +| `deepseek-account/session-expired` | `emit` | [`packages/credentials/deepseek-account/src/types.ts:91`](../packages/credentials/deepseek-account/src/types.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | `account-controller`, `remotes` | | `deepseek-account/signed-out` | `emit` | [`packages/credentials/deepseek-account/src/index.ts:12`](../packages/credentials/deepseek-account/src/index.ts) | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) (`emit`) | [`deepseek-account`](../packages/credentials/deepseek-account) | -| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` | +| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `workspace-controller` | | `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | -| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | +| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:77`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` | -| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:59`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | +| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:59`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `goal/activation-changed` | `emit` | [`packages/goal/goal/src/types.ts:150`](../packages/goal/goal/src/types.ts) | [`goal`](../packages/goal/goal) (`emit`) | `remotes` | | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) | -| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:30`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:35`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | -| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`llm`](../packages/llm/llm), `remotes` | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | +| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:32`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | +| `hmr/reload` | `emit` | [`packages/boot/hmr/src/index.ts:37`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - | +| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | | `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` | -| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:248`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | -| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:254`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | -| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:261`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:270`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | +| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:277`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` | | `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:478`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` | | `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:55`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:65`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:77`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:86`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, `claude-code-mods`, [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`loader-smoke`](../packages/test-support/loader-smoke), `product-analytics`, `server`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter), [`user-questions`](../packages/interaction/user-questions), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:82`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:75`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`emit`) | `remotes` | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:296`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:174`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), `server`, [`subagent`](../packages/subagent/subagent) | -| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:154`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | -| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:165`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) | -| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:166`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), `server` | +| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:142`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:148`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`tool-subagent`](../packages/subagent/tool-subagent) | +| `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:158`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | +| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`working-directory`](../packages/session/working-directory) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | -| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | -| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:176`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | -| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:153`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | -| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:190`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:198`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver), [`tool-present`](../packages/deliverables/tool-present) | +| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:209`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:165`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `claude-code-mods`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` | +| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:177`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `claude-code-mods`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | +| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:154`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `tools/ptc-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | +| `tools/result` | `emit` | [`packages/core/tools/src/index.ts:199`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, [`subagent`](../packages/subagent/subagent), [`tool-present`](../packages/deliverables/tool-present) | | `user-questions/request` | `waterfall` | [`packages/interaction/user-questions/src/types.ts:157`](../packages/interaction/user-questions/src/types.ts) | [`user-questions`](../packages/interaction/user-questions) (`waterfall`) | `remotes` | | `webserver/index-inject` | `emit` | [`packages/host/webserver/src/index.ts:34`](../packages/host/webserver/src/index.ts) | `webserver` (`emit`) | `connection`, `inspector`, `modules`, `shortcuts`, `ui-sidebar-documentpreview`, `ui-theme` | -| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow), [`workflow`](../packages/workflow/workflow) | -| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/agent-end` | `emit` | [`packages/workflow/workflow/src/index.ts:79`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/agent-start` | `emit` | [`packages/workflow/workflow/src/index.ts:68`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | +| `workflow/end` | `emit` | [`packages/workflow/workflow/src/index.ts:89`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workflow/log` | `emit` | [`packages/workflow/workflow/src/index.ts:58`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | | `workflow/phase` | `emit` | [`packages/workflow/workflow/src/index.ts:51`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`tool-workflow`](../packages/workflow/tool-workflow) | -| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | [`workflow`](../packages/workflow/workflow) | +| `workflow/start` | `emit` | [`packages/workflow/workflow/src/index.ts:43`](../packages/workflow/workflow/src/index.ts) | [`workflow`](../packages/workflow/workflow) (`events.dispatch`) | - | | `workspace/session-activity` | `waterfall` | [`packages/workspace/workspace/src/index.ts:129`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`waterfall`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | | `workspace/session-stop` | `parallel` | [`packages/workspace/workspace/src/index.ts:147`](../packages/workspace/workspace/src/index.ts) | [`workspace`](../packages/workspace/workspace) (`parallel`) | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent) | @@ -99,10 +99,10 @@ | Event string | Dispatchers | Listeners | | --- | --- | --- | | `internal/config` | [`config-editor`](../packages/boot/config-editor) (`waterfall`) | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | -| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | [`terminal-bash`](../packages/terminal/terminal-bash) | | `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` | -| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` | -| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) | +| `internal/service` | - | `gateway` | +| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, `translator`, [`web`](../packages/web/web) | | `internal/update` | - | [`app-boot`](../packages/boot/app-boot) | | `loader/volatile-update` | - | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `product-analytics`, `speech-to-text` | | `slots/changed` | `ui-renderer` (`emit`) | - | diff --git a/docs/experimental-persistence-catalog.i18n.yaml b/docs/experimental-persistence-catalog.i18n.yaml new file mode 100644 index 0000000000..a65825ab71 --- /dev/null +++ b/docs/experimental-persistence-catalog.i18n.yaml @@ -0,0 +1,10 @@ +# Bilingual-pair consistency record for experimental-persistence-catalog.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/experimental-persistence-catalog.md +/: + en: a11f106978c9dce7 + zh: a11f106978c9dce7 +/experimental-plugin-record-catalog: + en: 84dbc9c06ab92877 + zh: b82ef2a46292ab0e diff --git a/docs/experimental-persistence-catalog.md b/docs/experimental-persistence-catalog.md new file mode 100644 index 0000000000..0868849c51 --- /dev/null +++ b/docs/experimental-persistence-catalog.md @@ -0,0 +1,17 @@ + + +# Experimental Plugin Record Catalog + +English | [中文](experimental-persistence-catalog.zh.md) + +These current-source PluginRecordMap declarations describe ignorable, log-only records owned by experimental packages. Payload annotations link to their owning source without expanding referenced types. Records stay outside the known-event set, persistence schema fingerprints, and compatibility history; format migrations retain them on a best-effort basis. + +Run `pnpm run gen-persistence-catalog` to regenerate both persistence catalogs, their language pairs, and associated artifacts. `pnpm run verify-persistence-catalog` checks all generated files. + +Shared Session event declarations and resolved schemas appear in the [Session persistence event catalog](persistence-catalog.md). + +| Record | Owner | Purpose | Payload and source | +|---|---|---|---| +| `plugin:translator/request` | `@deepseek-ai/dsh-experimental-translator` | Provider-independent translation input, retained before dispatch. | TranslationRequestRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | +| `plugin:translator/result` | `@deepseek-ai/dsh-experimental-translator` | Successful translation, retained before it is returned to the consumer. | TranslationResultRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | diff --git a/docs/experimental-persistence-catalog.zh.md b/docs/experimental-persistence-catalog.zh.md new file mode 100644 index 0000000000..32cb1d0533 --- /dev/null +++ b/docs/experimental-persistence-catalog.zh.md @@ -0,0 +1,17 @@ + + +# 实验性插件记录目录 + +[English](experimental-persistence-catalog.md) | 中文 + +这些当前源码中的 PluginRecordMap 声明描述实验性包拥有的 ignorable、仅日志记录。载荷类型注解链接到所属源码,不展开引用类型。记录不进入已知事件集合、持久化 schema 指纹或兼容性历史;格式迁移以尽力而为的方式保留它们。 + +运行 `pnpm run gen-persistence-catalog` 可重新生成两份持久化目录及其中英配对和关联制品。`pnpm run verify-persistence-catalog` 检查所有生成文件。 + +共享 Session 事件声明与解析后的 schema 见[会话持久化事件目录](persistence-catalog.zh.md)。 + +| 记录 | 所属包 | 用途 | 载荷与源码 | +|---|---|---|---| +| `plugin:translator/request` | `@deepseek-ai/dsh-experimental-translator` | Provider-independent translation input, retained before dispatch. | TranslationRequestRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | +| `plugin:translator/result` | `@deepseek-ai/dsh-experimental-translator` | Successful translation, retained before it is returned to the consumer. | TranslationResultRecord · [`packages/experimental/translator/src/types.ts`](../packages/experimental/translator/src/types.ts) | diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 4c794f49bd..ccb3f9a8ba 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -6,8 +6,8 @@ en: f9829ce537be0f72 zh: 5ab1c534d35fb0b3 /bilingual-documentation/the-pairing-contract: - en: 218dd8d0cdf826e4 - zh: 8df922ca48c52387 + en: f67fecc8791baa0b + zh: cfb74225cd88a468 /bilingual-documentation/the-gate-verify-translation-pairing: en: 08386e96eebf06d9 zh: c6d2b41199b0c466 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 4c23fac67e..7ed0a4bf7c 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -21,7 +21,7 @@ This repo's documentation is read by people and agents both inside and outside t Section `i` of one side corresponds to section `i` of the other. Each side's hash covers every top-level block of the section except fenced code blocks and generated regions, the two kinds of content the gate already requires to be identical on both sides; content that merely happens to be identical is still hashed. A section with no remaining blocks has no entry. Regenerating a region or editing a code block on both sides leaves the record unchanged, and edits to different sections change separate record lines, so Git's default text merge, including GitHub's, composes them. A record conflicts only when both branches changed hashed content in the same section; resolve the Markdown and re-record the pair. An out-of-sync pair is updated by patching the counterpart minimally against the edited side's diff, never by re-translating whole files; the gate names each changed section. Routine work makes that patch directly; when the user explicitly invokes the extended workflow, `pnpm run gen-translation-brief ` recovers the last-confirmed text from the newest commit whose contents produce the recorded entries, assembles the update at the narrowest safely aligned granularity, and `--apply` can splice a code-fence-only change after structural validation. After bringing the pair back in line, `pnpm run verify-translation-pairing --write ` re-records the entries; that yaml diff is the reviewable act of confirming consistency, which is why `--write` requires naming the pairs you confirmed (`--write --all` is the explicit corpus-wide form). - The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. The [section-keyed pairing records Agent Note](../../.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.md) owns the record format and its alternatives. + The Chinese file must retain its English backlink; an authored English source must retain its Chinese link, while a listed generated English source is exempt. The [archived section-keyed pairing records Agent Note](../../.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.md) records the format choice and its alternatives. - **Language switcher.** The Chinese file always links back immediately after its H1 heading with `[English](foo.md) | 中文`. An authored English file reciprocates there with `English | [中文](foo.zh.md)`; a listed generated English source omits that line so it remains byte-identical to generator output. A README published outside GitHub, such as PyPI project metadata, may use the canonical `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL to the same counterpart so the switcher still resolves there. - **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, semantic link targets with exact query/fragment suffixes, and verbatim code blocks match one to one across the pair. When a relative document link targets the active bilingual corpus, the English side uses its `.md` path and the Chinese side uses its `.zh.md` path. A missing counterpart in that corpus is a pair-completeness error rather than a fallback; targets outside the active corpus keep the authored path. See [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`). diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index a905a0bdae..b098af1072 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -23,7 +23,7 @@ 一侧的第 `i` 个分节对应另一侧的第 `i` 个分节。每侧的 hash 覆盖该分节的全部顶层块,只排除围栏代码块和生成区域——门禁已要求这两类内容在两侧一致;只是碰巧相同的内容仍计入 hash。没有剩余块的分节没有条目。重新生成区域或在两侧同时修改代码块不会改变记录;修改不同分节会改动记录中不同的行,因此 Git 默认文本合并(包括 GitHub 的合并)能组合它们。只有两个分支都修改了同一分节中计入 hash 的内容时,记录才会冲突;解决 Markdown 冲突后重新记录该配对。失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译;门禁会指出每个发生变化的分节。日常工作会直接完成这份修补;用户显式调用扩展工作流时,`pnpm run gen-translation-brief ` 会从内容能生成所记录条目的最新提交中还原上次确认的文本,以能安全对齐的最窄粒度汇集这次更新,并可由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动。两侧对齐后,`pnpm run verify-translation-pairing --write ` 重新记录这些条目;那份 YAML diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。 - 中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。[按分节记录配对 Agent Note](../../.agents/notes/implemented/process/2026-09-23-section-keyed-translation-pairing-records.zh.md) 负责记录该记录格式与备选方案。 + 中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。[已归档的按分节记录配对 Agent Note](../../.agents/notes/archived/process/2026-09-23-section-keyed-translation-pairing-records.md) 记录了格式选择与备选方案。 - **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。发布到 GitHub 以外位置的 README(例如 PyPI 项目元数据)可以改用指向同一对侧文件的规范 `https://github.com/deepseek-ai/deepseek-harness/blob/master/` URL,使切换行在该位置仍可访问。 - **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、保留原样 query/fragment 后缀的语义链接目标,以及逐字节一致的代码块在配对两侧一一对应。相对文档链接的目标属于活跃双语语料时,英文侧使用其 `.md` 路径,中文侧使用其 `.zh.md` 路径。该范围内缺少对侧属于配对完整性错误,不得回退;范围外的目标保留原路径。完整保持规则见 [translation-rules.md](translation-rules.zh.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。 diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index be61b27830..47bc8ca1d8 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -18,13 +18,13 @@ ## Few-shot 金标 -流水线使用**整篇文档**的中英对照作为 few-shot,不是模板内嵌的句子级正误例。以下 5 组配对文档均经过人工评审,以仓库当前版本为准、随仓库更新: +流水线使用**整篇文档**的中英对照作为 few-shot,不是模板内嵌的句子级正误例。以下 5 组配对文档均经过人工评审;前四组随当前文档更新,第五组保留冻结的历史样例: - `README.md` ↔ `README.zh.md` - `docs/development.md` ↔ `docs/development.zh.md` - `docs/i18n/README.md` ↔ `docs/i18n/README.zh.md` - `docs/i18n/translation-rules.md` ↔ `docs/i18n/translation-rules.zh.md` -- `.agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md` ↔ 对应 `.zh.md` +- `.agents/notes/archived/process/2026-07-02-bilingual-docs-and-pairing-gate.md` ↔ 对应 `.zh.md` 注入方式:在系统消息(本模板)之后、待译文档之前,每组作为一轮示例对话——user 消息为源文档全文,assistant 消息为定稿译文全文(裸文本,不带三段 XML 包装;只有真实请求要求三段输出)。上下文不足时按上列顺序从后往前删减组数。这 5 组也是评审校准锚点(见 [style-samples.md](style-samples.md)),改动任何一组即改变流水线行为。 diff --git a/docs/module-graph.md b/docs/module-graph.md index 821dae7198..63a00c948d 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -64,7 +64,6 @@ flowchart TD end subgraph group_skill["packages/skill"] pkg_skill["skill"] - pkg_skill_badge["skill-badge"] pkg_skill_filesystem["skill-filesystem"] pkg_skill_office["skill-office"] pkg_tool_skill["tool-skill"] @@ -77,7 +76,6 @@ flowchart TD pkg_subagent_codex["subagent-codex"] pkg_subagent_dsh_sdk["subagent-dsh-sdk"] pkg_subagent_fork_in_process["subagent-fork-in-process"] - pkg_subagent_in_process_driver["subagent-in-process-driver"] pkg_subagent_spawn_in_process["subagent-spawn-in-process"] pkg_tool_subagent["tool-subagent"] pkg_tool_subagent_control["tool-subagent-control"] @@ -101,16 +99,10 @@ flowchart TD subgraph group_plan["packages/plan"] pkg_plan_mode["plan-mode"] end - subgraph group_hooks["packages/hooks"] - pkg_hook_protocol["hook-protocol"] - pkg_hooks_claude_code["hooks-claude-code"] - pkg_hooks_codex["hooks-codex"] - end subgraph group_session_query["packages/session-query"] pkg_session_log_export["session-log-export"] pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] - pkg_tool_session_query["tool-session-query"] end subgraph group_acp["packages/acp"] pkg_acp["acp"] @@ -250,22 +242,46 @@ flowchart TD pkg_experimental_agent_team_profile["experimental-agent-team-profile"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] pkg_experimental_auto_review["experimental-auto-review"] + pkg_experimental_badge_skill_bundle["experimental-badge-skill-bundle"] pkg_experimental_browser_use_chrome_devtools_mcp["experimental-browser-use-chrome-devtools-mcp"] pkg_experimental_browser_use_playwright_mcp["experimental-browser-use-playwright-mcp"] pkg_experimental_browser_use_runtime["experimental-browser-use-runtime"] pkg_experimental_browser_use_stagehand_native["experimental-browser-use-stagehand-native"] + pkg_experimental_claude_code_mods["experimental-claude-code-mods"] pkg_experimental_client_ui_agent_team["experimental-client-ui-agent-team"] + pkg_experimental_client_ui_claude_code_mods["experimental-client-ui-claude-code-mods"] + pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] pkg_experimental_client_ui_voice_input["experimental-client-ui-voice-input"] pkg_experimental_computer_use_cua_driver_mcp["experimental-computer-use-cua-driver-mcp"] pkg_experimental_computer_use_cua_driver_native["experimental-computer-use-cua-driver-native"] + pkg_experimental_cot_translation_bundle["experimental-cot-translation-bundle"] pkg_experimental_inspector["experimental-inspector"] + pkg_experimental_inspector_profile["experimental-inspector-profile"] pkg_experimental_ptc_runtime_python["experimental-ptc-runtime-python"] + pkg_experimental_ralph_bundle["experimental-ralph-bundle"] + pkg_experimental_session_inspector["experimental-session-inspector"] + pkg_experimental_session_search["experimental-session-search"] + pkg_experimental_session_titles_bundle["experimental-session-titles-bundle"] pkg_experimental_speech_to_text["experimental-speech-to-text"] pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] + pkg_experimental_terminal_bundle["experimental-terminal-bundle"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] + pkg_experimental_tool_worktree["experimental-tool-worktree"] + pkg_experimental_translator["experimental-translator"] pkg_experimental_voice_input_bundle["experimental-voice-input-bundle"] pkg_experimental_webworker_packer["experimental-webworker-packer"] pkg_experimental_webworker_runtime["experimental-webworker-runtime"] + pkg_experimental_worktree["experimental-worktree"] + pkg_hook_protocol["hook-protocol"] + pkg_hooks_claude_code["hooks-claude-code"] + pkg_hooks_codex["hooks-codex"] + pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] + pkg_skill_badge["skill-badge"] + pkg_tool_ralph["tool-ralph"] + pkg_tool_session_query["tool-session-query"] + pkg_tool_terminal["tool-terminal"] + pkg_webhook["webhook"] + pkg_webhook_github["webhook-github"] end subgraph group_extensions["packages/extensions"] pkg_client_ui_cordis["client-ui-cordis"] @@ -325,9 +341,6 @@ flowchart TD pkg_ptc_runtime["ptc-runtime"] pkg_ptc_runtime_node["ptc-runtime-node"] end - subgraph group_runtime_diagnostics["packages/runtime-diagnostics"] - pkg_invariants["invariants"] - end subgraph group_sandbox["packages/sandbox"] pkg_sandbox["sandbox"] pkg_sandbox_local["sandbox-local"] @@ -360,10 +373,11 @@ flowchart TD pkg_session_telemetry["session-telemetry"] pkg_session_telemetry_otel["session-telemetry-otel"] pkg_session_title["session-title"] - pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] pkg_session_title_first_prompt_llm["session-title-first-prompt-llm"] pkg_session_title_llm["session-title-llm"] pkg_session_turn_outline["session-turn-outline"] + pkg_tool_working_directory["tool-working-directory"] + pkg_working_directory["working-directory"] end subgraph group_settings["packages/settings"] pkg_settings["settings"] @@ -403,7 +417,6 @@ flowchart TD subgraph group_terminal["packages/terminal"] pkg_terminal["terminal"] pkg_terminal_bash["terminal-bash"] - pkg_tool_terminal["tool-terminal"] end subgraph group_test_support["packages/test-support"] pkg_agent_loop_testkit["agent-loop-testkit"] @@ -420,12 +433,7 @@ flowchart TD pkg_typert_protocol["typert-protocol"] pkg_typert_registry["typert-registry"] end - subgraph group_webhook["packages/webhook"] - pkg_webhook["webhook"] - pkg_webhook_github["webhook-github"] - end subgraph group_workflow["packages/workflow"] - pkg_tool_ralph["tool-ralph"] pkg_tool_workflow["tool-workflow"] pkg_workflow["workflow"] pkg_workflow_ptc["workflow-ptc"] @@ -433,14 +441,23 @@ flowchart TD subgraph group_workspace["packages/workspace"] pkg_workspace["workspace"] end - pkg_scope --> pkg_invariants + pkg_session --> pkg_scope + pkg_system_prompt --> pkg_llm + pkg_system_prompt --> pkg_scope + pkg_skill --> pkg_llm + pkg_skill --> pkg_scope pkg_web --> pkg_llm + pkg_api_remotes --> pkg_scope pkg_attachment --> pkg_brand pkg_browser_use --> pkg_brand + pkg_client_connection --> pkg_scope pkg_computer_use --> pkg_brand - pkg_credentials --> pkg_invariants - pkg_experimental_inspector --> pkg_client_modules - pkg_experimental_inspector --> pkg_host_webserver + pkg_authorization --> pkg_credentials + pkg_authorization --> pkg_llm + pkg_credentials_local --> pkg_atomic_write + pkg_credentials_local --> pkg_credentials + pkg_credentials_local --> pkg_home_paths + pkg_credentials_local --> pkg_launch_environment pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_native pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse @@ -450,19 +467,13 @@ flowchart TD pkg_anonymous_user_id --> pkg_home_paths pkg_lsp --> pkg_brand pkg_lsp --> pkg_llm - pkg_storage_domain --> pkg_invariants pkg_storage_domain --> pkg_storage pkg_storage_json --> pkg_storage pkg_storage_sqlite --> pkg_storage pkg_subprocess --> pkg_http_proxy pkg_remote_mock --> pkg_typert_protocol pkg_typert_loader --> pkg_typert_registry - pkg_session --> pkg_scope - pkg_system_prompt --> pkg_invariants - pkg_system_prompt --> pkg_llm - pkg_system_prompt --> pkg_scope - pkg_skill --> pkg_llm - pkg_skill --> pkg_scope + pkg_skill_office --> pkg_skill pkg_web_fetch_http --> pkg_http_proxy pkg_web_fetch_http --> pkg_timeout pkg_web_fetch_http --> pkg_web @@ -470,34 +481,24 @@ flowchart TD pkg_web_search_exa --> pkg_web pkg_web_search_perplexity --> pkg_launch_environment pkg_web_search_perplexity --> pkg_web - pkg_api_remotes --> pkg_scope - pkg_api_terminal_controller --> pkg_subprocess - pkg_attachment_local --> pkg_attachment - pkg_attachment_local --> pkg_home_paths - pkg_client_connection --> pkg_scope - pkg_client_file_upload --> pkg_scope - pkg_authorization --> pkg_credentials - pkg_authorization --> pkg_invariants - pkg_authorization --> pkg_llm - pkg_credentials_local --> pkg_atomic_write - pkg_credentials_local --> pkg_credentials - pkg_credentials_local --> pkg_home_paths - pkg_credentials_local --> pkg_launch_environment - pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use - pkg_subprocess_local --> pkg_subprocess - pkg_subprocess_local --> pkg_timeout - pkg_skill_badge --> pkg_skill - pkg_skill_office --> pkg_skill pkg_spill --> pkg_brand pkg_spill --> pkg_llm pkg_spill --> pkg_session pkg_api_gateway --> pkg_client_connection + pkg_api_terminal_controller --> pkg_subprocess + pkg_attachment_local --> pkg_attachment + pkg_attachment_local --> pkg_home_paths pkg_app_boot --> pkg_home_paths pkg_app_boot --> pkg_launch_environment pkg_app_boot --> pkg_system_prompt + pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use + pkg_experimental_inspector --> pkg_client_connection + pkg_experimental_inspector --> pkg_client_modules + pkg_experimental_inspector --> pkg_host_webserver pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver + pkg_skill_badge --> pkg_skill pkg_host_frontend_static --> pkg_client_connection pkg_host_frontend_static --> pkg_host_webserver pkg_persona --> pkg_system_prompt @@ -507,20 +508,19 @@ flowchart TD pkg_sandbox_windows_acl --> pkg_subprocess pkg_session_format_catalog --> pkg_session pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions - pkg_session_log_deepseek --> pkg_invariants pkg_session_log_deepseek --> pkg_session pkg_session_persistence --> pkg_brand pkg_session_persistence --> pkg_session pkg_session_persistence --> pkg_timeout pkg_session_projection --> pkg_session pkg_settings --> pkg_brand - pkg_settings --> pkg_invariants pkg_settings --> pkg_session + pkg_subprocess_local --> pkg_subprocess + pkg_subprocess_local --> pkg_timeout pkg_otel --> pkg_session pkg_session_snapshot --> pkg_http_proxy pkg_session_snapshot --> pkg_session pkg_fs --> pkg_brand - pkg_fs --> pkg_invariants pkg_fs --> pkg_llm pkg_fs --> pkg_sandbox pkg_spill_local --> pkg_spill @@ -529,6 +529,13 @@ flowchart TD pkg_hmr --> pkg_app_boot pkg_hmr --> pkg_cmdline pkg_experimental_speech_to_text --> pkg_settings + pkg_experimental_translator --> pkg_credentials + pkg_experimental_translator --> pkg_launch_environment + pkg_experimental_translator --> pkg_llm + pkg_experimental_translator --> pkg_session + pkg_experimental_translator --> pkg_session_persistence + pkg_experimental_translator --> pkg_timeout + pkg_experimental_translator --> pkg_util_values pkg_host_product_telemetry_otel --> pkg_otel pkg_ptc_runtime --> pkg_sandbox pkg_sandbox_local --> pkg_llm @@ -547,7 +554,6 @@ flowchart TD pkg_session_turn_outline --> pkg_session_projection pkg_shell --> pkg_sandbox pkg_shell --> pkg_subprocess - pkg_workspace --> pkg_invariants pkg_workspace --> pkg_session pkg_workspace --> pkg_session_persistence pkg_workspace --> pkg_storage @@ -569,7 +575,6 @@ flowchart TD pkg_llm_pi_ai --> pkg_launch_environment pkg_llm_pi_ai --> pkg_llm pkg_llm_pi_ai --> pkg_timeout - pkg_agent --> pkg_invariants pkg_agent --> pkg_llm pkg_agent --> pkg_scope pkg_agent --> pkg_session @@ -583,9 +588,6 @@ flowchart TD pkg_skill_filesystem --> pkg_fs pkg_skill_filesystem --> pkg_home_paths pkg_skill_filesystem --> pkg_skill - pkg_hook_protocol --> pkg_invariants - pkg_hook_protocol --> pkg_session - pkg_hook_protocol --> pkg_shell pkg_api_workspace_controller --> pkg_api_gateway pkg_api_workspace_controller --> pkg_client_connection pkg_api_workspace_controller --> pkg_host_directory_picker @@ -597,11 +599,10 @@ flowchart TD pkg_config_editor --> pkg_hmr pkg_experimental_api_speech_to_text --> pkg_experimental_speech_to_text pkg_experimental_api_speech_to_text --> pkg_typert_protocol - pkg_experimental_ptc_runtime_python --> pkg_ptc_runtime - pkg_experimental_ptc_runtime_python --> pkg_timeout - pkg_experimental_ptc_runtime_python --> pkg_util_values pkg_experimental_speech_to_text_sensevoice --> pkg_experimental_speech_to_text pkg_experimental_speech_to_text_sensevoice --> pkg_subprocess + pkg_hook_protocol --> pkg_session + pkg_hook_protocol --> pkg_shell pkg_lsp_stdio --> pkg_brand pkg_lsp_stdio --> pkg_fs pkg_lsp_stdio --> pkg_llm @@ -620,7 +621,6 @@ flowchart TD pkg_llm_deepseek_api_key --> pkg_llm_deepseek pkg_llm_retry --> pkg_agent pkg_llm_retry --> pkg_brand - pkg_llm_retry --> pkg_invariants pkg_llm_retry --> pkg_llm pkg_llm_retry --> pkg_session pkg_llm_retry --> pkg_session_projection @@ -629,7 +629,6 @@ flowchart TD pkg_agent_default_model --> pkg_llm pkg_goal --> pkg_agent pkg_goal --> pkg_brand - pkg_goal --> pkg_invariants pkg_goal --> pkg_llm pkg_goal --> pkg_scope pkg_goal --> pkg_session @@ -637,7 +636,6 @@ flowchart TD pkg_goal --> pkg_typert_protocol pkg_file_reference --> pkg_agent pkg_time_context --> pkg_agent - pkg_time_context --> pkg_invariants pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_time_context --> pkg_session_projection @@ -651,14 +649,12 @@ flowchart TD pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand - pkg_commands --> pkg_invariants pkg_commands --> pkg_llm pkg_commands --> pkg_scope pkg_commands --> pkg_session pkg_commands --> pkg_typert_protocol pkg_user_approval --> pkg_agent pkg_user_approval --> pkg_brand - pkg_user_approval --> pkg_invariants pkg_user_approval --> pkg_llm pkg_user_approval --> pkg_scope pkg_user_approval --> pkg_session @@ -671,11 +667,9 @@ flowchart TD pkg_user_questions --> pkg_typert_protocol pkg_jobs --> pkg_agent pkg_jobs --> pkg_brand - pkg_jobs --> pkg_invariants pkg_jobs --> pkg_session pkg_jobs --> pkg_workspace pkg_sandbox_policy --> pkg_agent - pkg_sandbox_policy --> pkg_invariants pkg_sandbox_policy --> pkg_sandbox pkg_sandbox_policy --> pkg_session pkg_sandbox_policy --> pkg_session_projection @@ -684,10 +678,15 @@ flowchart TD pkg_session_telemetry --> pkg_session pkg_session_title --> pkg_agent pkg_session_title --> pkg_brand - pkg_session_title --> pkg_invariants pkg_session_title --> pkg_llm pkg_session_title --> pkg_session pkg_session_title --> pkg_session_projection + pkg_working_directory --> pkg_agent + pkg_working_directory --> pkg_fs + pkg_working_directory --> pkg_llm + pkg_working_directory --> pkg_session + pkg_working_directory --> pkg_session_projection + pkg_working_directory --> pkg_system_prompt pkg_terminal --> pkg_agent pkg_terminal --> pkg_brand pkg_loader_smoke --> pkg_agent @@ -696,7 +695,6 @@ flowchart TD pkg_loader_smoke --> pkg_session pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand - pkg_workflow --> pkg_invariants pkg_workflow --> pkg_llm pkg_workflow --> pkg_session pkg_llm_deepseek_account --> pkg_deepseek_account @@ -704,7 +702,6 @@ flowchart TD pkg_llm_deepseek_account --> pkg_llm pkg_llm_deepseek_account --> pkg_llm_deepseek pkg_tools --> pkg_agent - pkg_tools --> pkg_invariants pkg_tools --> pkg_llm pkg_tools --> pkg_ptc_runtime pkg_tools --> pkg_sandbox @@ -713,12 +710,12 @@ flowchart TD pkg_tools --> pkg_session pkg_tools --> pkg_system_prompt pkg_tools --> pkg_user_approval + pkg_tools --> pkg_working_directory pkg_command_goal --> pkg_commands pkg_command_goal --> pkg_goal pkg_command_goal --> pkg_llm pkg_goal_round_driver --> pkg_agent pkg_goal_round_driver --> pkg_goal - pkg_goal_round_driver --> pkg_invariants pkg_goal_round_driver --> pkg_llm pkg_goal_round_driver --> pkg_session pkg_fs_sandbox --> pkg_fs @@ -736,19 +733,28 @@ flowchart TD pkg_api_account_controller --> pkg_typert_protocol pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands - pkg_compaction --> pkg_invariants pkg_compaction --> pkg_llm pkg_compaction --> pkg_session pkg_deepseek_account_platform --> pkg_authorization pkg_deepseek_account_platform --> pkg_credentials pkg_deepseek_account_platform --> pkg_deepseek_account pkg_deepseek_account_platform --> pkg_host_webserver + pkg_experimental_ptc_runtime_python --> pkg_ptc_runtime + pkg_experimental_ptc_runtime_python --> pkg_sandbox + pkg_experimental_ptc_runtime_python --> pkg_sandbox_policy + pkg_experimental_ptc_runtime_python --> pkg_timeout + pkg_experimental_ptc_runtime_python --> pkg_util_values + pkg_experimental_worktree --> pkg_agent + pkg_experimental_worktree --> pkg_fs + pkg_experimental_worktree --> pkg_sandbox + pkg_experimental_worktree --> pkg_sandbox_policy + pkg_experimental_worktree --> pkg_subprocess + pkg_experimental_worktree --> pkg_working_directory pkg_command_feedback --> pkg_anonymous_user_id pkg_command_feedback --> pkg_commands pkg_command_feedback --> pkg_session pkg_command_feedback --> pkg_typert_protocol pkg_permission_presets --> pkg_commands - pkg_permission_presets --> pkg_invariants pkg_permission_presets --> pkg_sandbox pkg_permission_presets --> pkg_sandbox_policy pkg_permission_presets --> pkg_session @@ -786,8 +792,8 @@ flowchart TD pkg_terminal_bash --> pkg_session_projection pkg_terminal_bash --> pkg_subprocess pkg_terminal_bash --> pkg_terminal + pkg_terminal_bash --> pkg_working_directory pkg_agent_loop --> pkg_agent - pkg_agent_loop --> pkg_invariants pkg_agent_loop --> pkg_llm pkg_agent_loop --> pkg_scope pkg_agent_loop --> pkg_session @@ -812,6 +818,7 @@ flowchart TD pkg_tool_fs --> pkg_system_prompt pkg_tool_fs --> pkg_tools pkg_tool_fs --> pkg_user_approval + pkg_tool_fs --> pkg_working_directory pkg_tool_fs_search --> pkg_llm pkg_tool_fs_search --> pkg_output_retention pkg_tool_fs_search --> pkg_session @@ -820,6 +827,7 @@ flowchart TD pkg_tool_fs_search --> pkg_system_prompt pkg_tool_fs_search --> pkg_timeout pkg_tool_fs_search --> pkg_tools + pkg_tool_fs_search --> pkg_working_directory pkg_tool_str_replace_editor --> pkg_fs pkg_tool_str_replace_editor --> pkg_output_retention pkg_tool_str_replace_editor --> pkg_sandbox @@ -829,31 +837,24 @@ flowchart TD pkg_tool_skill --> pkg_llm pkg_tool_skill --> pkg_skill pkg_tool_skill --> pkg_tools + pkg_tool_skill --> pkg_working_directory pkg_tool_workspace_dependencies --> pkg_tools pkg_tool_web --> pkg_llm pkg_tool_web --> pkg_system_prompt pkg_tool_web --> pkg_tools pkg_tool_web --> pkg_web pkg_tool_todo --> pkg_agent - pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session pkg_tool_todo --> pkg_session_projection pkg_tool_todo --> pkg_tools pkg_plan_mode --> pkg_agent pkg_plan_mode --> pkg_commands - pkg_plan_mode --> pkg_invariants pkg_plan_mode --> pkg_llm pkg_plan_mode --> pkg_session pkg_plan_mode --> pkg_session_projection pkg_plan_mode --> pkg_system_prompt pkg_plan_mode --> pkg_tools pkg_plan_mode --> pkg_user_questions - pkg_hooks_codex --> pkg_agent - pkg_hooks_codex --> pkg_hook_protocol - pkg_hooks_codex --> pkg_llm - pkg_hooks_codex --> pkg_session - pkg_hooks_codex --> pkg_session_projection - pkg_hooks_codex --> pkg_tools pkg_command_compact --> pkg_commands pkg_command_compact --> pkg_compaction pkg_compaction_image_offload --> pkg_agent @@ -867,17 +868,21 @@ flowchart TD pkg_agent_instructions --> pkg_session pkg_agent_instructions --> pkg_session_projection pkg_agent_instructions --> pkg_tools + pkg_agent_instructions --> pkg_working_directory pkg_file_reference_local --> pkg_agent pkg_file_reference_local --> pkg_file_reference pkg_file_reference_local --> pkg_system_prompt pkg_file_reference_local --> pkg_tools + pkg_file_reference_local --> pkg_working_directory pkg_tool_present --> pkg_agent pkg_tool_present --> pkg_fs pkg_tool_present --> pkg_llm pkg_tool_present --> pkg_session pkg_tool_present --> pkg_session_projection pkg_tool_present --> pkg_tools + pkg_tool_present --> pkg_working_directory pkg_workspace_changes --> pkg_agent + pkg_workspace_changes --> pkg_fs pkg_workspace_changes --> pkg_session pkg_workspace_changes --> pkg_subprocess pkg_workspace_changes --> pkg_tools @@ -885,10 +890,12 @@ flowchart TD pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_browser_use pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_system_prompt pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_tools + pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_working_directory pkg_experimental_browser_use_playwright_mcp --> pkg_agent pkg_experimental_browser_use_playwright_mcp --> pkg_browser_use pkg_experimental_browser_use_playwright_mcp --> pkg_system_prompt pkg_experimental_browser_use_playwright_mcp --> pkg_tools + pkg_experimental_browser_use_playwright_mcp --> pkg_working_directory pkg_experimental_browser_use_stagehand_native --> pkg_agent pkg_experimental_browser_use_stagehand_native --> pkg_browser_use pkg_experimental_browser_use_stagehand_native --> pkg_system_prompt @@ -896,6 +903,27 @@ flowchart TD pkg_experimental_computer_use_cua_driver_native --> pkg_computer_use pkg_experimental_computer_use_cua_driver_native --> pkg_system_prompt pkg_experimental_computer_use_cua_driver_native --> pkg_tools + pkg_experimental_tool_worktree --> pkg_experimental_worktree + pkg_experimental_tool_worktree --> pkg_tools + pkg_hooks_codex --> pkg_agent + pkg_hooks_codex --> pkg_hook_protocol + pkg_hooks_codex --> pkg_llm + pkg_hooks_codex --> pkg_session + pkg_hooks_codex --> pkg_session_projection + pkg_hooks_codex --> pkg_tools + pkg_hooks_codex --> pkg_working_directory + pkg_session_title_all_prompts_llm --> pkg_llm + pkg_session_title_all_prompts_llm --> pkg_session + pkg_session_title_all_prompts_llm --> pkg_session_title + pkg_session_title_all_prompts_llm --> pkg_session_title_llm + pkg_tool_terminal --> pkg_agent + pkg_tool_terminal --> pkg_jobs + pkg_tool_terminal --> pkg_llm + pkg_tool_terminal --> pkg_output_retention + pkg_tool_terminal --> pkg_system_prompt + pkg_tool_terminal --> pkg_terminal + pkg_tool_terminal --> pkg_tools + pkg_tool_terminal --> pkg_working_directory pkg_cordis_host_runner --> pkg_agent pkg_cordis_host_runner --> pkg_brand pkg_cordis_host_runner --> pkg_llm @@ -928,12 +956,12 @@ flowchart TD pkg_tool_lsp --> pkg_system_prompt pkg_tool_lsp --> pkg_timeout pkg_tool_lsp --> pkg_tools + pkg_tool_lsp --> pkg_working_directory pkg_mcp_resources --> pkg_llm pkg_mcp_resources --> pkg_scope pkg_mcp_resources --> pkg_system_prompt pkg_mcp_resources --> pkg_tools pkg_agent_preset_registry --> pkg_agent - pkg_agent_preset_registry --> pkg_invariants pkg_agent_preset_registry --> pkg_scope pkg_agent_preset_registry --> pkg_session pkg_agent_preset_registry --> pkg_session_projection @@ -946,14 +974,12 @@ flowchart TD pkg_session_checkpoint_policy --> pkg_session pkg_session_checkpoint_policy --> pkg_session_persistence pkg_session_checkpoint_policy --> pkg_tools - pkg_session_title_all_prompts_llm --> pkg_llm - pkg_session_title_all_prompts_llm --> pkg_session - pkg_session_title_all_prompts_llm --> pkg_session_title - pkg_session_title_all_prompts_llm --> pkg_session_title_llm pkg_session_title_first_prompt_llm --> pkg_llm pkg_session_title_first_prompt_llm --> pkg_session pkg_session_title_first_prompt_llm --> pkg_session_title pkg_session_title_first_prompt_llm --> pkg_session_title_llm + pkg_tool_working_directory --> pkg_tools + pkg_tool_working_directory --> pkg_working_directory pkg_shell_env --> pkg_app_boot pkg_shell_env --> pkg_home_paths pkg_shell_env --> pkg_shell @@ -963,11 +989,13 @@ flowchart TD pkg_tool_bash_persistent --> pkg_terminal pkg_tool_bash_persistent --> pkg_timeout pkg_tool_bash_persistent --> pkg_tools + pkg_tool_bash_persistent --> pkg_working_directory pkg_tool_pwsh_persistent --> pkg_agent pkg_tool_pwsh_persistent --> pkg_output_retention pkg_tool_pwsh_persistent --> pkg_terminal pkg_tool_pwsh_persistent --> pkg_timeout pkg_tool_pwsh_persistent --> pkg_tools + pkg_tool_pwsh_persistent --> pkg_working_directory pkg_ssh --> pkg_brand pkg_ssh --> pkg_fs pkg_ssh --> pkg_fs_local @@ -978,19 +1006,11 @@ flowchart TD pkg_ssh --> pkg_session_projection pkg_ssh --> pkg_subprocess pkg_ssh --> pkg_subprocess_local - pkg_tool_terminal --> pkg_agent - pkg_tool_terminal --> pkg_jobs - pkg_tool_terminal --> pkg_llm - pkg_tool_terminal --> pkg_output_retention - pkg_tool_terminal --> pkg_system_prompt - pkg_tool_terminal --> pkg_terminal - pkg_tool_terminal --> pkg_tools pkg_llm_replay --> pkg_compaction pkg_llm_replay --> pkg_deepseek_llm_api_extensions pkg_llm_replay --> pkg_llm pkg_llm_replay --> pkg_session pkg_tool_workflow --> pkg_agent - pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_jobs pkg_tool_workflow --> pkg_llm pkg_tool_workflow --> pkg_session @@ -1029,6 +1049,15 @@ flowchart TD pkg_experimental_auto_review --> pkg_permission_presets pkg_experimental_auto_review --> pkg_session pkg_experimental_auto_review --> pkg_tools + pkg_experimental_auto_review --> pkg_working_directory + pkg_webhook --> pkg_agent + pkg_webhook --> pkg_agent_default_model + pkg_webhook --> pkg_agent_preset_registry + pkg_webhook --> pkg_llm + pkg_webhook --> pkg_permission_presets + pkg_webhook --> pkg_session + pkg_webhook --> pkg_session_title + pkg_webhook --> pkg_workspace pkg_tool_cordis --> pkg_agent pkg_tool_cordis --> pkg_app_boot pkg_tool_cordis --> pkg_cordis_host_runner @@ -1062,6 +1091,7 @@ flowchart TD pkg_tool_bash --> pkg_system_prompt pkg_tool_bash --> pkg_tools pkg_tool_bash --> pkg_user_approval + pkg_tool_bash --> pkg_working_directory pkg_tool_pwsh --> pkg_agent pkg_tool_pwsh --> pkg_jobs pkg_tool_pwsh --> pkg_llm @@ -1072,6 +1102,7 @@ flowchart TD pkg_tool_pwsh --> pkg_system_prompt pkg_tool_pwsh --> pkg_tools pkg_tool_pwsh --> pkg_user_approval + pkg_tool_pwsh --> pkg_working_directory pkg_fs_ssh --> pkg_fs pkg_fs_ssh --> pkg_sandbox pkg_fs_ssh --> pkg_sandbox_policy @@ -1083,25 +1114,16 @@ flowchart TD pkg_subprocess_ssh --> pkg_subprocess_local pkg_agent_loop_testkit --> pkg_agent pkg_agent_loop_testkit --> pkg_agent_loop + pkg_agent_loop_testkit --> pkg_fs_local pkg_agent_loop_testkit --> pkg_llm pkg_agent_loop_testkit --> pkg_session pkg_agent_loop_testkit --> pkg_session_projection pkg_agent_loop_testkit --> pkg_system_prompt pkg_agent_loop_testkit --> pkg_tools - pkg_webhook --> pkg_agent - pkg_webhook --> pkg_agent_default_model - pkg_webhook --> pkg_agent_preset_registry - pkg_webhook --> pkg_invariants - pkg_webhook --> pkg_llm - pkg_webhook --> pkg_permission_presets - pkg_webhook --> pkg_session - pkg_webhook --> pkg_session_title - pkg_webhook --> pkg_workspace + pkg_agent_loop_testkit --> pkg_working_directory pkg_subagent --> pkg_agent pkg_subagent --> pkg_agent_preset_registry pkg_subagent --> pkg_attachment - pkg_subagent --> pkg_invariants - pkg_subagent --> pkg_jobs pkg_subagent --> pkg_llm pkg_subagent --> pkg_permission_presets pkg_subagent --> pkg_sandbox @@ -1116,6 +1138,7 @@ flowchart TD pkg_subagent --> pkg_typert_protocol pkg_subagent --> pkg_user_approval pkg_subagent --> pkg_util_time + pkg_subagent --> pkg_working_directory pkg_subagent --> pkg_workspace pkg_spill_policy --> pkg_attachment pkg_spill_policy --> pkg_fs @@ -1128,14 +1151,6 @@ flowchart TD pkg_session_query_sqlite --> pkg_session pkg_session_query_sqlite --> pkg_session_persistence pkg_session_query_sqlite --> pkg_session_query - pkg_tool_session_query --> pkg_agent - pkg_tool_session_query --> pkg_llm - pkg_tool_session_query --> pkg_session - pkg_tool_session_query --> pkg_session_projection - pkg_tool_session_query --> pkg_session_query - pkg_tool_session_query --> pkg_system_prompt - pkg_tool_session_query --> pkg_timeout - pkg_tool_session_query --> pkg_tools pkg_acp --> pkg_agent pkg_acp --> pkg_attachment pkg_acp --> pkg_llm @@ -1144,6 +1159,7 @@ flowchart TD pkg_acp --> pkg_session_persistence pkg_acp --> pkg_token_meter pkg_acp --> pkg_user_approval + pkg_plugin_manager --> pkg_agent_preset_registry pkg_plugin_manager --> pkg_brand pkg_plugin_manager --> pkg_hmr pkg_plugin_manager --> pkg_host_plugin_inventory @@ -1153,12 +1169,6 @@ flowchart TD pkg_plugin_manager --> pkg_tools pkg_plugin_manager --> pkg_typert_protocol pkg_plugin_manager --> pkg_user_approval - pkg_headless --> pkg_agent - pkg_headless --> pkg_agent_default_model - pkg_headless --> pkg_fs - pkg_headless --> pkg_llm - pkg_headless --> pkg_session - pkg_headless --> pkg_session_query pkg_compaction_tool_result_pruner --> pkg_compaction pkg_compaction_tool_result_pruner --> pkg_llm pkg_compaction_tool_result_pruner --> pkg_session @@ -1169,6 +1179,29 @@ flowchart TD pkg_experimental_browser_use_runtime --> pkg_scope pkg_experimental_browser_use_runtime --> pkg_system_prompt pkg_experimental_browser_use_runtime --> pkg_tools + pkg_experimental_browser_use_runtime --> pkg_working_directory + pkg_experimental_claude_code_mods --> pkg_agent + pkg_experimental_claude_code_mods --> pkg_commands + pkg_experimental_claude_code_mods --> pkg_fs + pkg_experimental_claude_code_mods --> pkg_llm + pkg_experimental_claude_code_mods --> pkg_scope + pkg_experimental_claude_code_mods --> pkg_session + pkg_experimental_claude_code_mods --> pkg_session_projection + pkg_experimental_claude_code_mods --> pkg_storage_domain + pkg_experimental_claude_code_mods --> pkg_subprocess + pkg_experimental_claude_code_mods --> pkg_token_meter + pkg_experimental_claude_code_mods --> pkg_tools + pkg_experimental_claude_code_mods --> pkg_typert_protocol + pkg_experimental_claude_code_mods --> pkg_user_questions + pkg_experimental_claude_code_mods --> pkg_working_directory + pkg_tool_session_query --> pkg_agent + pkg_tool_session_query --> pkg_llm + pkg_tool_session_query --> pkg_session + pkg_tool_session_query --> pkg_session_projection + pkg_tool_session_query --> pkg_session_query + pkg_tool_session_query --> pkg_system_prompt + pkg_tool_session_query --> pkg_timeout + pkg_tool_session_query --> pkg_tools pkg_webhook_github --> pkg_credentials pkg_webhook_github --> pkg_host_webserver pkg_webhook_github --> pkg_session @@ -1189,15 +1222,11 @@ flowchart TD pkg_subagent_codex --> pkg_subagent pkg_subagent_codex --> pkg_subprocess pkg_subagent_codex --> pkg_timeout - pkg_subagent_in_process_driver --> pkg_agent - pkg_subagent_in_process_driver --> pkg_llm - pkg_subagent_in_process_driver --> pkg_session - pkg_subagent_in_process_driver --> pkg_subagent - pkg_subagent_in_process_driver --> pkg_system_prompt - pkg_subagent_in_process_driver --> pkg_tools + pkg_subagent_fork_in_process --> pkg_agent + pkg_subagent_fork_in_process --> pkg_session + pkg_subagent_fork_in_process --> pkg_subagent + pkg_subagent_spawn_in_process --> pkg_subagent pkg_tool_subagent --> pkg_agent - pkg_tool_subagent --> pkg_invariants - pkg_tool_subagent --> pkg_jobs pkg_tool_subagent --> pkg_llm pkg_tool_subagent --> pkg_scope pkg_tool_subagent --> pkg_session @@ -1209,13 +1238,6 @@ flowchart TD pkg_tool_subagent_control --> pkg_session pkg_tool_subagent_control --> pkg_subagent pkg_tool_subagent_control --> pkg_tools - pkg_hooks_claude_code --> pkg_agent - pkg_hooks_claude_code --> pkg_hook_protocol - pkg_hooks_claude_code --> pkg_llm - pkg_hooks_claude_code --> pkg_session - pkg_hooks_claude_code --> pkg_session_projection - pkg_hooks_claude_code --> pkg_subagent - pkg_hooks_claude_code --> pkg_tools pkg_api_session_controller --> pkg_agent pkg_api_session_controller --> pkg_agent_default_model pkg_api_session_controller --> pkg_agent_preset_registry @@ -1244,7 +1266,15 @@ flowchart TD pkg_api_session_controller --> pkg_util_time pkg_api_session_controller --> pkg_util_values pkg_api_session_controller --> pkg_util_workspace_path + pkg_api_session_controller --> pkg_working_directory pkg_api_session_controller --> pkg_workspace + pkg_headless --> pkg_agent + pkg_headless --> pkg_agent_default_model + pkg_headless --> pkg_fs + pkg_headless --> pkg_llm + pkg_headless --> pkg_session + pkg_headless --> pkg_session_query + pkg_headless --> pkg_subagent pkg_compaction_basic --> pkg_agent pkg_compaction_basic --> pkg_commands pkg_compaction_basic --> pkg_compaction @@ -1266,21 +1296,28 @@ flowchart TD pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_invariants pkg_experimental_agent_team --> pkg_llm pkg_experimental_agent_team --> pkg_session pkg_experimental_agent_team --> pkg_session_persistence pkg_experimental_agent_team --> pkg_session_projection pkg_experimental_agent_team --> pkg_subagent - pkg_sdk_protocol --> pkg_llm - pkg_sdk_protocol --> pkg_session - pkg_sdk_protocol --> pkg_subagent + pkg_hooks_claude_code --> pkg_agent + pkg_hooks_claude_code --> pkg_hook_protocol + pkg_hooks_claude_code --> pkg_llm + pkg_hooks_claude_code --> pkg_session + pkg_hooks_claude_code --> pkg_session_projection + pkg_hooks_claude_code --> pkg_subagent + pkg_hooks_claude_code --> pkg_tools + pkg_hooks_claude_code --> pkg_working_directory pkg_tool_ralph --> pkg_agent pkg_tool_ralph --> pkg_llm pkg_tool_ralph --> pkg_subagent pkg_tool_ralph --> pkg_system_prompt pkg_tool_ralph --> pkg_tools pkg_tool_ralph --> pkg_workflow + pkg_sdk_protocol --> pkg_llm + pkg_sdk_protocol --> pkg_session + pkg_sdk_protocol --> pkg_subagent pkg_workflow_ptc --> pkg_agent pkg_workflow_ptc --> pkg_llm pkg_workflow_ptc --> pkg_ptc_runtime @@ -1290,12 +1327,7 @@ flowchart TD pkg_workflow_ptc --> pkg_subagent pkg_workflow_ptc --> pkg_tools pkg_workflow_ptc --> pkg_workflow - pkg_subagent_fork_in_process --> pkg_agent - pkg_subagent_fork_in_process --> pkg_session - pkg_subagent_fork_in_process --> pkg_subagent - pkg_subagent_fork_in_process --> pkg_subagent_in_process_driver - pkg_subagent_spawn_in_process --> pkg_subagent - pkg_subagent_spawn_in_process --> pkg_subagent_in_process_driver + pkg_workflow_ptc --> pkg_working_directory pkg_experimental_client_ui_agent_team --> pkg_api_session_controller pkg_experimental_client_ui_agent_team --> pkg_client_locale pkg_experimental_client_ui_agent_team --> pkg_client_ui_conversation @@ -1306,6 +1338,22 @@ flowchart TD pkg_experimental_client_ui_agent_team --> pkg_client_ui_workspace pkg_experimental_client_ui_agent_team --> pkg_experimental_agent_team pkg_experimental_client_ui_agent_team --> pkg_session + pkg_experimental_client_ui_claude_code_mods --> pkg_api_remotes + pkg_experimental_client_ui_claude_code_mods --> pkg_api_session_controller + pkg_experimental_client_ui_claude_code_mods --> pkg_client_locale + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_conversation + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_primitives + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_renderer + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_session + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_slots + pkg_experimental_client_ui_claude_code_mods --> pkg_experimental_claude_code_mods + pkg_experimental_client_ui_claude_code_mods --> pkg_session + pkg_experimental_client_ui_claude_code_mods --> pkg_typert_protocol + pkg_experimental_client_ui_cot_translation --> pkg_api_session_controller + pkg_experimental_client_ui_cot_translation --> pkg_experimental_translator + pkg_experimental_client_ui_cot_translation --> pkg_session + pkg_experimental_client_ui_cot_translation --> pkg_settings + pkg_experimental_client_ui_cot_translation --> pkg_typert_protocol pkg_experimental_client_ui_voice_input --> pkg_api_gateway pkg_experimental_client_ui_voice_input --> pkg_api_remotes pkg_experimental_client_ui_voice_input --> pkg_api_session_controller @@ -1329,7 +1377,6 @@ flowchart TD pkg_schedule --> pkg_agent pkg_schedule --> pkg_api_session_controller pkg_schedule --> pkg_brand - pkg_schedule --> pkg_invariants pkg_schedule --> pkg_llm pkg_schedule --> pkg_session pkg_schedule --> pkg_session_persistence @@ -1350,6 +1397,7 @@ flowchart TD pkg_sdk_jsonrpc_server --> pkg_sdk_protocol pkg_sdk_jsonrpc_server --> pkg_session pkg_sdk_jsonrpc_server --> pkg_subagent + pkg_sdk_jsonrpc_server --> pkg_working_directory pkg_client_test_runtime --> pkg_api_gateway pkg_client_test_runtime --> pkg_api_session_controller pkg_client_test_runtime --> pkg_api_workspace_controller @@ -1403,6 +1451,7 @@ flowchart TD | [`util-workspace-path`](../packages/util/workspace-path) | `util` | — | | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — | | [`llm`](../packages/llm/llm) | `llm` | — | +| [`scope`](../packages/core/scope) | `core` | — | | [`api-job-controller`](../packages/api/job-controller) | `api` | — | | [`api-workspace-files`](../packages/api/workspace-files) | `api` | — | | [`cmdline`](../packages/boot/cmdline) | `boot` | — | @@ -1410,6 +1459,7 @@ flowchart TD | [`base`](../packages/bundle/base) | `bundle` | — | | [`sdk-app`](../packages/bundle/sdk-app) | `bundle` | — | | [`sdk-minimal`](../packages/bundle/sdk-minimal) | `bundle` | — | +| [`client-file-upload`](../packages/client/file-upload) | `client` | — | | [`client-hmr`](../packages/client/hmr) | `client` | — | | [`client-locale`](../packages/client/locale) | `client` | — | | [`client-modules`](../packages/client/modules) | `client` | — | @@ -1471,8 +1521,17 @@ flowchart TD | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — | | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — | | [`client-web`](../packages/client/web) | `client` | — | +| [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | +| [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | +| [`experimental-cot-translation-bundle`](../packages/experimental/cot-translation-bundle) | `experimental` | — | +| [`experimental-inspector-profile`](../packages/experimental/inspector-profile) | `experimental` | — | +| [`experimental-ralph-bundle`](../packages/experimental/ralph-bundle) | `experimental` | — | +| [`experimental-session-inspector`](../packages/experimental/session-inspector) | `experimental` | — | +| [`experimental-session-search`](../packages/experimental/session-search) | `experimental` | — | +| [`experimental-session-titles-bundle`](../packages/experimental/session-titles-bundle) | `experimental` | — | +| [`experimental-terminal-bundle`](../packages/experimental/terminal-bundle) | `experimental` | — | | [`experimental-voice-input-bundle`](../packages/experimental/voice-input-bundle) | `experimental` | — | | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — | | [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | — | @@ -1482,7 +1541,6 @@ flowchart TD | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — | | [`host-open-in-app`](../packages/host/open-in-app) | `host` | — | | [`host-webserver`](../packages/host/webserver) | `host` | — | -| [`invariants`](../packages/runtime-diagnostics/invariants) | `runtime-diagnostics` | — | | [`session-format`](../packages/session/session-format) | `session` | — | | [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — | | [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — | @@ -1494,59 +1552,57 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | — | | [`typert-protocol`](../packages/typert/protocol) | `typert` | — | | [`typert-registry`](../packages/typert/registry) | `typert` | — | -| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) | +| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) | -| [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) | +| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) | | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) | +| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-json`](../packages/storage/storage-json) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`storage`](../packages/storage/storage) | | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy) | | [`remote-mock`](../packages/test-support/remote-mock) | `test-support` | [`typert-protocol`](../packages/typert/protocol) | | [`typert-loader`](../packages/typert/loader) | `typert` | [`typert-registry`](../packages/typert/registry) | -| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | -| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | -| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | -| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | -| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | -| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | -| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | -| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | -| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | -| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) | +| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | +| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) | +| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | +| [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`skill-badge`](../packages/experimental/skill-badge) | `experimental` | [`skill`](../packages/skill/skill) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | | [`persona`](../packages/preset/persona) | `preset` | [`system-prompt`](../packages/core/system-prompt) | | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`skill`](../packages/skill/skill), [`subprocess`](../packages/subprocess/subprocess) | | [`session-format-catalog`](../packages/session/session-format-catalog) | `session` | [`session`](../packages/core/session) | -| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`session-projection`](../packages/session/session-projection) | `session` | [`session`](../packages/core/session) | -| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`session`](../packages/core/session) | +| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`otel`](../packages/telemetry/otel) | `telemetry` | [`session`](../packages/core/session) | | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`session`](../packages/core/session) | -| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) | | [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | | [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) | | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | `experimental` | [`settings`](../packages/settings/settings) | +| [`experimental-translator`](../packages/experimental/translator) | `experimental` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`host-product-telemetry-otel`](../packages/host/product-telemetry-otel) | `host` | [`otel`](../packages/telemetry/otel) | | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | `ptc-runtime` | [`sandbox`](../packages/sandbox/sandbox) | | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) | @@ -1555,148 +1611,154 @@ flowchart TD | [`session-stats`](../packages/session/session-stats) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-turn-outline`](../packages/session/session-turn-outline) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`shell`](../packages/shell/shell) | `shell` | [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | -| [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | +| [`workspace`](../packages/workspace/workspace) | `workspace` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | -| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | +| [`agent`](../packages/core/agent) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs) | | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`skill`](../packages/skill/skill) | -| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`config-editor`](../packages/boot/config-editor) | `boot` | [`app-boot`](../packages/boot/app-boot), [`hmr`](../packages/boot/hmr) | | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | `experimental` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`subprocess`](../packages/subprocess/subprocess) | +| [`hook-protocol`](../packages/experimental/hook-protocol) | `experimental` | [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`bash-local`](../packages/shell/bash-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key) | `llm` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | +| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | | [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | -| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | +| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | | [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | -| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | -| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | +| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | +| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`session`](../packages/core/session) | -| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`working-directory`](../packages/session/working-directory) | `session` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand) | | [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`llm-deepseek-account`](../packages/llm/llm-deepseek-account) | `llm` | [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | -| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`api-account-controller`](../packages/api/account-controller) | `api` | [`agent`](../packages/core/agent), [`deepseek-account`](../packages/credentials/deepseek-account), [`typert-protocol`](../packages/typert/protocol) | -| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | `credentials` | [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`host-webserver`](../packages/host/webserver) | +| [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | `experimental` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | +| [`experimental-worktree`](../packages/experimental/worktree) | `experimental` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`working-directory`](../packages/session/working-directory) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | +| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) | | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node) | `ptc-runtime` | [`fs`](../packages/fs/fs), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | -| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) | -| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal), [`working-directory`](../packages/session/working-directory) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | +| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`output-retention`](../packages/util/output-retention), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) | -| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | +| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-workspace-dependencies`](../packages/skill/tool-workspace-dependencies) | `skill` | [`tools`](../packages/core/tools) | | [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | -| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction) | | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | `compaction` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | -| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | +| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | `experimental` | [`computer-use`](../packages/computer-use/computer-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`tools`](../packages/core/tools) | +| [`hooks-codex`](../packages/experimental/hooks-codex) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`session-title-all-prompts-llm`](../packages/experimental/session-title-all-prompts-llm) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | +| [`tool-terminal`](../packages/experimental/tool-terminal) | `experimental` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`typert-protocol`](../packages/typert/protocol) | | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | | [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | -| [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | +| [`tool-working-directory`](../packages/session/tool-working-directory) | `session` | [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`shell-env`](../packages/shell/shell-env) | `shell` | [`app-boot`](../packages/boot/app-boot), [`home-paths`](../packages/util/home-paths), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) | -| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | -| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | -| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) | | [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) | | [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | -| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`webhook`](../packages/experimental/webhook) | `experimental` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`app-boot`](../packages/boot/app-boot), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`tools`](../packages/core/tools) | | [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) | | [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-resources`](../packages/mcp/mcp-resources), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`agent-preset`](../packages/preset/agent-preset) | `preset` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`otel`](../packages/telemetry/otel), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) | -| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | +| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | | [`fs-ssh`](../packages/ssh/fs-ssh) | `ssh` | [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`ssh`](../packages/ssh/ssh) | | [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) | | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | -| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | +| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | -| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) | -| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | -| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | +| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | +| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) | +| [`tool-session-query`](../packages/experimental/tool-session-query) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`webhook-github`](../packages/experimental/webhook-github) | `experimental` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/experimental/webhook) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent) | +| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) | +| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | +| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`subagent`](../packages/subagent/subagent) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-ralph`](../packages/experimental/tool-ralph) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | -| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | -| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | +| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow), [`working-directory`](../packages/session/working-directory) | | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session) | +| [`experimental-client-ui-claude-code-mods`](../packages/experimental/client-ui-claude-code-mods) | `experimental` | [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`experimental-translator`](../packages/experimental/translator), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | [`api-gateway`](../packages/api/gateway), [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-store`](../packages/client/store), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text), [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | +| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | -| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`working-directory`](../packages/session/working-directory) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`tool-schedule`](../packages/schedule/tool-schedule) | `schedule` | [`llm`](../packages/llm/llm), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index e4be628e11..9b3a1e2036 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -66,7 +66,6 @@ flowchart TD end subgraph group_skill["packages/skill"] pkg_skill["skill"] - pkg_skill_badge["skill-badge"] pkg_skill_filesystem["skill-filesystem"] pkg_skill_office["skill-office"] pkg_tool_skill["tool-skill"] @@ -79,7 +78,6 @@ flowchart TD pkg_subagent_codex["subagent-codex"] pkg_subagent_dsh_sdk["subagent-dsh-sdk"] pkg_subagent_fork_in_process["subagent-fork-in-process"] - pkg_subagent_in_process_driver["subagent-in-process-driver"] pkg_subagent_spawn_in_process["subagent-spawn-in-process"] pkg_tool_subagent["tool-subagent"] pkg_tool_subagent_control["tool-subagent-control"] @@ -103,16 +101,10 @@ flowchart TD subgraph group_plan["packages/plan"] pkg_plan_mode["plan-mode"] end - subgraph group_hooks["packages/hooks"] - pkg_hook_protocol["hook-protocol"] - pkg_hooks_claude_code["hooks-claude-code"] - pkg_hooks_codex["hooks-codex"] - end subgraph group_session_query["packages/session-query"] pkg_session_log_export["session-log-export"] pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] - pkg_tool_session_query["tool-session-query"] end subgraph group_acp["packages/acp"] pkg_acp["acp"] @@ -252,22 +244,46 @@ flowchart TD pkg_experimental_agent_team_profile["experimental-agent-team-profile"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] pkg_experimental_auto_review["experimental-auto-review"] + pkg_experimental_badge_skill_bundle["experimental-badge-skill-bundle"] pkg_experimental_browser_use_chrome_devtools_mcp["experimental-browser-use-chrome-devtools-mcp"] pkg_experimental_browser_use_playwright_mcp["experimental-browser-use-playwright-mcp"] pkg_experimental_browser_use_runtime["experimental-browser-use-runtime"] pkg_experimental_browser_use_stagehand_native["experimental-browser-use-stagehand-native"] + pkg_experimental_claude_code_mods["experimental-claude-code-mods"] pkg_experimental_client_ui_agent_team["experimental-client-ui-agent-team"] + pkg_experimental_client_ui_claude_code_mods["experimental-client-ui-claude-code-mods"] + pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] pkg_experimental_client_ui_voice_input["experimental-client-ui-voice-input"] pkg_experimental_computer_use_cua_driver_mcp["experimental-computer-use-cua-driver-mcp"] pkg_experimental_computer_use_cua_driver_native["experimental-computer-use-cua-driver-native"] + pkg_experimental_cot_translation_bundle["experimental-cot-translation-bundle"] pkg_experimental_inspector["experimental-inspector"] + pkg_experimental_inspector_profile["experimental-inspector-profile"] pkg_experimental_ptc_runtime_python["experimental-ptc-runtime-python"] + pkg_experimental_ralph_bundle["experimental-ralph-bundle"] + pkg_experimental_session_inspector["experimental-session-inspector"] + pkg_experimental_session_search["experimental-session-search"] + pkg_experimental_session_titles_bundle["experimental-session-titles-bundle"] pkg_experimental_speech_to_text["experimental-speech-to-text"] pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] + pkg_experimental_terminal_bundle["experimental-terminal-bundle"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] + pkg_experimental_tool_worktree["experimental-tool-worktree"] + pkg_experimental_translator["experimental-translator"] pkg_experimental_voice_input_bundle["experimental-voice-input-bundle"] pkg_experimental_webworker_packer["experimental-webworker-packer"] pkg_experimental_webworker_runtime["experimental-webworker-runtime"] + pkg_experimental_worktree["experimental-worktree"] + pkg_hook_protocol["hook-protocol"] + pkg_hooks_claude_code["hooks-claude-code"] + pkg_hooks_codex["hooks-codex"] + pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] + pkg_skill_badge["skill-badge"] + pkg_tool_ralph["tool-ralph"] + pkg_tool_session_query["tool-session-query"] + pkg_tool_terminal["tool-terminal"] + pkg_webhook["webhook"] + pkg_webhook_github["webhook-github"] end subgraph group_extensions["packages/extensions"] pkg_client_ui_cordis["client-ui-cordis"] @@ -327,9 +343,6 @@ flowchart TD pkg_ptc_runtime["ptc-runtime"] pkg_ptc_runtime_node["ptc-runtime-node"] end - subgraph group_runtime_diagnostics["packages/runtime-diagnostics"] - pkg_invariants["invariants"] - end subgraph group_sandbox["packages/sandbox"] pkg_sandbox["sandbox"] pkg_sandbox_local["sandbox-local"] @@ -362,10 +375,11 @@ flowchart TD pkg_session_telemetry["session-telemetry"] pkg_session_telemetry_otel["session-telemetry-otel"] pkg_session_title["session-title"] - pkg_session_title_all_prompts_llm["session-title-all-prompts-llm"] pkg_session_title_first_prompt_llm["session-title-first-prompt-llm"] pkg_session_title_llm["session-title-llm"] pkg_session_turn_outline["session-turn-outline"] + pkg_tool_working_directory["tool-working-directory"] + pkg_working_directory["working-directory"] end subgraph group_settings["packages/settings"] pkg_settings["settings"] @@ -405,7 +419,6 @@ flowchart TD subgraph group_terminal["packages/terminal"] pkg_terminal["terminal"] pkg_terminal_bash["terminal-bash"] - pkg_tool_terminal["tool-terminal"] end subgraph group_test_support["packages/test-support"] pkg_agent_loop_testkit["agent-loop-testkit"] @@ -422,12 +435,7 @@ flowchart TD pkg_typert_protocol["typert-protocol"] pkg_typert_registry["typert-registry"] end - subgraph group_webhook["packages/webhook"] - pkg_webhook["webhook"] - pkg_webhook_github["webhook-github"] - end subgraph group_workflow["packages/workflow"] - pkg_tool_ralph["tool-ralph"] pkg_tool_workflow["tool-workflow"] pkg_workflow["workflow"] pkg_workflow_ptc["workflow-ptc"] @@ -435,14 +443,23 @@ flowchart TD subgraph group_workspace["packages/workspace"] pkg_workspace["workspace"] end - pkg_scope --> pkg_invariants + pkg_session --> pkg_scope + pkg_system_prompt --> pkg_llm + pkg_system_prompt --> pkg_scope + pkg_skill --> pkg_llm + pkg_skill --> pkg_scope pkg_web --> pkg_llm + pkg_api_remotes --> pkg_scope pkg_attachment --> pkg_brand pkg_browser_use --> pkg_brand + pkg_client_connection --> pkg_scope pkg_computer_use --> pkg_brand - pkg_credentials --> pkg_invariants - pkg_experimental_inspector --> pkg_client_modules - pkg_experimental_inspector --> pkg_host_webserver + pkg_authorization --> pkg_credentials + pkg_authorization --> pkg_llm + pkg_credentials_local --> pkg_atomic_write + pkg_credentials_local --> pkg_credentials + pkg_credentials_local --> pkg_home_paths + pkg_credentials_local --> pkg_launch_environment pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_browse pkg_host_directory_picker_auto --> pkg_client_ui_directory_picker_native pkg_host_directory_picker_auto --> pkg_host_directory_picker_browse @@ -452,19 +469,13 @@ flowchart TD pkg_anonymous_user_id --> pkg_home_paths pkg_lsp --> pkg_brand pkg_lsp --> pkg_llm - pkg_storage_domain --> pkg_invariants pkg_storage_domain --> pkg_storage pkg_storage_json --> pkg_storage pkg_storage_sqlite --> pkg_storage pkg_subprocess --> pkg_http_proxy pkg_remote_mock --> pkg_typert_protocol pkg_typert_loader --> pkg_typert_registry - pkg_session --> pkg_scope - pkg_system_prompt --> pkg_invariants - pkg_system_prompt --> pkg_llm - pkg_system_prompt --> pkg_scope - pkg_skill --> pkg_llm - pkg_skill --> pkg_scope + pkg_skill_office --> pkg_skill pkg_web_fetch_http --> pkg_http_proxy pkg_web_fetch_http --> pkg_timeout pkg_web_fetch_http --> pkg_web @@ -472,34 +483,24 @@ flowchart TD pkg_web_search_exa --> pkg_web pkg_web_search_perplexity --> pkg_launch_environment pkg_web_search_perplexity --> pkg_web - pkg_api_remotes --> pkg_scope - pkg_api_terminal_controller --> pkg_subprocess - pkg_attachment_local --> pkg_attachment - pkg_attachment_local --> pkg_home_paths - pkg_client_connection --> pkg_scope - pkg_client_file_upload --> pkg_scope - pkg_authorization --> pkg_credentials - pkg_authorization --> pkg_invariants - pkg_authorization --> pkg_llm - pkg_credentials_local --> pkg_atomic_write - pkg_credentials_local --> pkg_credentials - pkg_credentials_local --> pkg_home_paths - pkg_credentials_local --> pkg_launch_environment - pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use - pkg_subprocess_local --> pkg_subprocess - pkg_subprocess_local --> pkg_timeout - pkg_skill_badge --> pkg_skill - pkg_skill_office --> pkg_skill pkg_spill --> pkg_brand pkg_spill --> pkg_llm pkg_spill --> pkg_session pkg_api_gateway --> pkg_client_connection + pkg_api_terminal_controller --> pkg_subprocess + pkg_attachment_local --> pkg_attachment + pkg_attachment_local --> pkg_home_paths pkg_app_boot --> pkg_home_paths pkg_app_boot --> pkg_launch_environment pkg_app_boot --> pkg_system_prompt + pkg_experimental_computer_use_cua_driver_mcp --> pkg_computer_use + pkg_experimental_inspector --> pkg_client_connection + pkg_experimental_inspector --> pkg_client_modules + pkg_experimental_inspector --> pkg_host_webserver pkg_experimental_webworker_runtime --> pkg_client_connection pkg_experimental_webworker_runtime --> pkg_client_modules pkg_experimental_webworker_runtime --> pkg_host_webserver + pkg_skill_badge --> pkg_skill pkg_host_frontend_static --> pkg_client_connection pkg_host_frontend_static --> pkg_host_webserver pkg_persona --> pkg_system_prompt @@ -509,20 +510,19 @@ flowchart TD pkg_sandbox_windows_acl --> pkg_subprocess pkg_session_format_catalog --> pkg_session pkg_session_log_deepseek --> pkg_deepseek_llm_api_extensions - pkg_session_log_deepseek --> pkg_invariants pkg_session_log_deepseek --> pkg_session pkg_session_persistence --> pkg_brand pkg_session_persistence --> pkg_session pkg_session_persistence --> pkg_timeout pkg_session_projection --> pkg_session pkg_settings --> pkg_brand - pkg_settings --> pkg_invariants pkg_settings --> pkg_session + pkg_subprocess_local --> pkg_subprocess + pkg_subprocess_local --> pkg_timeout pkg_otel --> pkg_session pkg_session_snapshot --> pkg_http_proxy pkg_session_snapshot --> pkg_session pkg_fs --> pkg_brand - pkg_fs --> pkg_invariants pkg_fs --> pkg_llm pkg_fs --> pkg_sandbox pkg_spill_local --> pkg_spill @@ -531,6 +531,13 @@ flowchart TD pkg_hmr --> pkg_app_boot pkg_hmr --> pkg_cmdline pkg_experimental_speech_to_text --> pkg_settings + pkg_experimental_translator --> pkg_credentials + pkg_experimental_translator --> pkg_launch_environment + pkg_experimental_translator --> pkg_llm + pkg_experimental_translator --> pkg_session + pkg_experimental_translator --> pkg_session_persistence + pkg_experimental_translator --> pkg_timeout + pkg_experimental_translator --> pkg_util_values pkg_host_product_telemetry_otel --> pkg_otel pkg_ptc_runtime --> pkg_sandbox pkg_sandbox_local --> pkg_llm @@ -549,7 +556,6 @@ flowchart TD pkg_session_turn_outline --> pkg_session_projection pkg_shell --> pkg_sandbox pkg_shell --> pkg_subprocess - pkg_workspace --> pkg_invariants pkg_workspace --> pkg_session pkg_workspace --> pkg_session_persistence pkg_workspace --> pkg_storage @@ -571,7 +577,6 @@ flowchart TD pkg_llm_pi_ai --> pkg_launch_environment pkg_llm_pi_ai --> pkg_llm pkg_llm_pi_ai --> pkg_timeout - pkg_agent --> pkg_invariants pkg_agent --> pkg_llm pkg_agent --> pkg_scope pkg_agent --> pkg_session @@ -585,9 +590,6 @@ flowchart TD pkg_skill_filesystem --> pkg_fs pkg_skill_filesystem --> pkg_home_paths pkg_skill_filesystem --> pkg_skill - pkg_hook_protocol --> pkg_invariants - pkg_hook_protocol --> pkg_session - pkg_hook_protocol --> pkg_shell pkg_api_workspace_controller --> pkg_api_gateway pkg_api_workspace_controller --> pkg_client_connection pkg_api_workspace_controller --> pkg_host_directory_picker @@ -599,11 +601,10 @@ flowchart TD pkg_config_editor --> pkg_hmr pkg_experimental_api_speech_to_text --> pkg_experimental_speech_to_text pkg_experimental_api_speech_to_text --> pkg_typert_protocol - pkg_experimental_ptc_runtime_python --> pkg_ptc_runtime - pkg_experimental_ptc_runtime_python --> pkg_timeout - pkg_experimental_ptc_runtime_python --> pkg_util_values pkg_experimental_speech_to_text_sensevoice --> pkg_experimental_speech_to_text pkg_experimental_speech_to_text_sensevoice --> pkg_subprocess + pkg_hook_protocol --> pkg_session + pkg_hook_protocol --> pkg_shell pkg_lsp_stdio --> pkg_brand pkg_lsp_stdio --> pkg_fs pkg_lsp_stdio --> pkg_llm @@ -622,7 +623,6 @@ flowchart TD pkg_llm_deepseek_api_key --> pkg_llm_deepseek pkg_llm_retry --> pkg_agent pkg_llm_retry --> pkg_brand - pkg_llm_retry --> pkg_invariants pkg_llm_retry --> pkg_llm pkg_llm_retry --> pkg_session pkg_llm_retry --> pkg_session_projection @@ -631,7 +631,6 @@ flowchart TD pkg_agent_default_model --> pkg_llm pkg_goal --> pkg_agent pkg_goal --> pkg_brand - pkg_goal --> pkg_invariants pkg_goal --> pkg_llm pkg_goal --> pkg_scope pkg_goal --> pkg_session @@ -639,7 +638,6 @@ flowchart TD pkg_goal --> pkg_typert_protocol pkg_file_reference --> pkg_agent pkg_time_context --> pkg_agent - pkg_time_context --> pkg_invariants pkg_time_context --> pkg_llm pkg_time_context --> pkg_session pkg_time_context --> pkg_session_projection @@ -653,14 +651,12 @@ flowchart TD pkg_commands --> pkg_agent pkg_commands --> pkg_attachment pkg_commands --> pkg_brand - pkg_commands --> pkg_invariants pkg_commands --> pkg_llm pkg_commands --> pkg_scope pkg_commands --> pkg_session pkg_commands --> pkg_typert_protocol pkg_user_approval --> pkg_agent pkg_user_approval --> pkg_brand - pkg_user_approval --> pkg_invariants pkg_user_approval --> pkg_llm pkg_user_approval --> pkg_scope pkg_user_approval --> pkg_session @@ -673,11 +669,9 @@ flowchart TD pkg_user_questions --> pkg_typert_protocol pkg_jobs --> pkg_agent pkg_jobs --> pkg_brand - pkg_jobs --> pkg_invariants pkg_jobs --> pkg_session pkg_jobs --> pkg_workspace pkg_sandbox_policy --> pkg_agent - pkg_sandbox_policy --> pkg_invariants pkg_sandbox_policy --> pkg_sandbox pkg_sandbox_policy --> pkg_session pkg_sandbox_policy --> pkg_session_projection @@ -686,10 +680,15 @@ flowchart TD pkg_session_telemetry --> pkg_session pkg_session_title --> pkg_agent pkg_session_title --> pkg_brand - pkg_session_title --> pkg_invariants pkg_session_title --> pkg_llm pkg_session_title --> pkg_session pkg_session_title --> pkg_session_projection + pkg_working_directory --> pkg_agent + pkg_working_directory --> pkg_fs + pkg_working_directory --> pkg_llm + pkg_working_directory --> pkg_session + pkg_working_directory --> pkg_session_projection + pkg_working_directory --> pkg_system_prompt pkg_terminal --> pkg_agent pkg_terminal --> pkg_brand pkg_loader_smoke --> pkg_agent @@ -698,7 +697,6 @@ flowchart TD pkg_loader_smoke --> pkg_session pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand - pkg_workflow --> pkg_invariants pkg_workflow --> pkg_llm pkg_workflow --> pkg_session pkg_llm_deepseek_account --> pkg_deepseek_account @@ -706,7 +704,6 @@ flowchart TD pkg_llm_deepseek_account --> pkg_llm pkg_llm_deepseek_account --> pkg_llm_deepseek pkg_tools --> pkg_agent - pkg_tools --> pkg_invariants pkg_tools --> pkg_llm pkg_tools --> pkg_ptc_runtime pkg_tools --> pkg_sandbox @@ -715,12 +712,12 @@ flowchart TD pkg_tools --> pkg_session pkg_tools --> pkg_system_prompt pkg_tools --> pkg_user_approval + pkg_tools --> pkg_working_directory pkg_command_goal --> pkg_commands pkg_command_goal --> pkg_goal pkg_command_goal --> pkg_llm pkg_goal_round_driver --> pkg_agent pkg_goal_round_driver --> pkg_goal - pkg_goal_round_driver --> pkg_invariants pkg_goal_round_driver --> pkg_llm pkg_goal_round_driver --> pkg_session pkg_fs_sandbox --> pkg_fs @@ -738,19 +735,28 @@ flowchart TD pkg_api_account_controller --> pkg_typert_protocol pkg_compaction --> pkg_brand pkg_compaction --> pkg_commands - pkg_compaction --> pkg_invariants pkg_compaction --> pkg_llm pkg_compaction --> pkg_session pkg_deepseek_account_platform --> pkg_authorization pkg_deepseek_account_platform --> pkg_credentials pkg_deepseek_account_platform --> pkg_deepseek_account pkg_deepseek_account_platform --> pkg_host_webserver + pkg_experimental_ptc_runtime_python --> pkg_ptc_runtime + pkg_experimental_ptc_runtime_python --> pkg_sandbox + pkg_experimental_ptc_runtime_python --> pkg_sandbox_policy + pkg_experimental_ptc_runtime_python --> pkg_timeout + pkg_experimental_ptc_runtime_python --> pkg_util_values + pkg_experimental_worktree --> pkg_agent + pkg_experimental_worktree --> pkg_fs + pkg_experimental_worktree --> pkg_sandbox + pkg_experimental_worktree --> pkg_sandbox_policy + pkg_experimental_worktree --> pkg_subprocess + pkg_experimental_worktree --> pkg_working_directory pkg_command_feedback --> pkg_anonymous_user_id pkg_command_feedback --> pkg_commands pkg_command_feedback --> pkg_session pkg_command_feedback --> pkg_typert_protocol pkg_permission_presets --> pkg_commands - pkg_permission_presets --> pkg_invariants pkg_permission_presets --> pkg_sandbox pkg_permission_presets --> pkg_sandbox_policy pkg_permission_presets --> pkg_session @@ -788,8 +794,8 @@ flowchart TD pkg_terminal_bash --> pkg_session_projection pkg_terminal_bash --> pkg_subprocess pkg_terminal_bash --> pkg_terminal + pkg_terminal_bash --> pkg_working_directory pkg_agent_loop --> pkg_agent - pkg_agent_loop --> pkg_invariants pkg_agent_loop --> pkg_llm pkg_agent_loop --> pkg_scope pkg_agent_loop --> pkg_session @@ -814,6 +820,7 @@ flowchart TD pkg_tool_fs --> pkg_system_prompt pkg_tool_fs --> pkg_tools pkg_tool_fs --> pkg_user_approval + pkg_tool_fs --> pkg_working_directory pkg_tool_fs_search --> pkg_llm pkg_tool_fs_search --> pkg_output_retention pkg_tool_fs_search --> pkg_session @@ -822,6 +829,7 @@ flowchart TD pkg_tool_fs_search --> pkg_system_prompt pkg_tool_fs_search --> pkg_timeout pkg_tool_fs_search --> pkg_tools + pkg_tool_fs_search --> pkg_working_directory pkg_tool_str_replace_editor --> pkg_fs pkg_tool_str_replace_editor --> pkg_output_retention pkg_tool_str_replace_editor --> pkg_sandbox @@ -831,31 +839,24 @@ flowchart TD pkg_tool_skill --> pkg_llm pkg_tool_skill --> pkg_skill pkg_tool_skill --> pkg_tools + pkg_tool_skill --> pkg_working_directory pkg_tool_workspace_dependencies --> pkg_tools pkg_tool_web --> pkg_llm pkg_tool_web --> pkg_system_prompt pkg_tool_web --> pkg_tools pkg_tool_web --> pkg_web pkg_tool_todo --> pkg_agent - pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session pkg_tool_todo --> pkg_session_projection pkg_tool_todo --> pkg_tools pkg_plan_mode --> pkg_agent pkg_plan_mode --> pkg_commands - pkg_plan_mode --> pkg_invariants pkg_plan_mode --> pkg_llm pkg_plan_mode --> pkg_session pkg_plan_mode --> pkg_session_projection pkg_plan_mode --> pkg_system_prompt pkg_plan_mode --> pkg_tools pkg_plan_mode --> pkg_user_questions - pkg_hooks_codex --> pkg_agent - pkg_hooks_codex --> pkg_hook_protocol - pkg_hooks_codex --> pkg_llm - pkg_hooks_codex --> pkg_session - pkg_hooks_codex --> pkg_session_projection - pkg_hooks_codex --> pkg_tools pkg_command_compact --> pkg_commands pkg_command_compact --> pkg_compaction pkg_compaction_image_offload --> pkg_agent @@ -869,17 +870,21 @@ flowchart TD pkg_agent_instructions --> pkg_session pkg_agent_instructions --> pkg_session_projection pkg_agent_instructions --> pkg_tools + pkg_agent_instructions --> pkg_working_directory pkg_file_reference_local --> pkg_agent pkg_file_reference_local --> pkg_file_reference pkg_file_reference_local --> pkg_system_prompt pkg_file_reference_local --> pkg_tools + pkg_file_reference_local --> pkg_working_directory pkg_tool_present --> pkg_agent pkg_tool_present --> pkg_fs pkg_tool_present --> pkg_llm pkg_tool_present --> pkg_session pkg_tool_present --> pkg_session_projection pkg_tool_present --> pkg_tools + pkg_tool_present --> pkg_working_directory pkg_workspace_changes --> pkg_agent + pkg_workspace_changes --> pkg_fs pkg_workspace_changes --> pkg_session pkg_workspace_changes --> pkg_subprocess pkg_workspace_changes --> pkg_tools @@ -887,10 +892,12 @@ flowchart TD pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_browser_use pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_system_prompt pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_tools + pkg_experimental_browser_use_chrome_devtools_mcp --> pkg_working_directory pkg_experimental_browser_use_playwright_mcp --> pkg_agent pkg_experimental_browser_use_playwright_mcp --> pkg_browser_use pkg_experimental_browser_use_playwright_mcp --> pkg_system_prompt pkg_experimental_browser_use_playwright_mcp --> pkg_tools + pkg_experimental_browser_use_playwright_mcp --> pkg_working_directory pkg_experimental_browser_use_stagehand_native --> pkg_agent pkg_experimental_browser_use_stagehand_native --> pkg_browser_use pkg_experimental_browser_use_stagehand_native --> pkg_system_prompt @@ -898,6 +905,27 @@ flowchart TD pkg_experimental_computer_use_cua_driver_native --> pkg_computer_use pkg_experimental_computer_use_cua_driver_native --> pkg_system_prompt pkg_experimental_computer_use_cua_driver_native --> pkg_tools + pkg_experimental_tool_worktree --> pkg_experimental_worktree + pkg_experimental_tool_worktree --> pkg_tools + pkg_hooks_codex --> pkg_agent + pkg_hooks_codex --> pkg_hook_protocol + pkg_hooks_codex --> pkg_llm + pkg_hooks_codex --> pkg_session + pkg_hooks_codex --> pkg_session_projection + pkg_hooks_codex --> pkg_tools + pkg_hooks_codex --> pkg_working_directory + pkg_session_title_all_prompts_llm --> pkg_llm + pkg_session_title_all_prompts_llm --> pkg_session + pkg_session_title_all_prompts_llm --> pkg_session_title + pkg_session_title_all_prompts_llm --> pkg_session_title_llm + pkg_tool_terminal --> pkg_agent + pkg_tool_terminal --> pkg_jobs + pkg_tool_terminal --> pkg_llm + pkg_tool_terminal --> pkg_output_retention + pkg_tool_terminal --> pkg_system_prompt + pkg_tool_terminal --> pkg_terminal + pkg_tool_terminal --> pkg_tools + pkg_tool_terminal --> pkg_working_directory pkg_cordis_host_runner --> pkg_agent pkg_cordis_host_runner --> pkg_brand pkg_cordis_host_runner --> pkg_llm @@ -930,12 +958,12 @@ flowchart TD pkg_tool_lsp --> pkg_system_prompt pkg_tool_lsp --> pkg_timeout pkg_tool_lsp --> pkg_tools + pkg_tool_lsp --> pkg_working_directory pkg_mcp_resources --> pkg_llm pkg_mcp_resources --> pkg_scope pkg_mcp_resources --> pkg_system_prompt pkg_mcp_resources --> pkg_tools pkg_agent_preset_registry --> pkg_agent - pkg_agent_preset_registry --> pkg_invariants pkg_agent_preset_registry --> pkg_scope pkg_agent_preset_registry --> pkg_session pkg_agent_preset_registry --> pkg_session_projection @@ -948,14 +976,12 @@ flowchart TD pkg_session_checkpoint_policy --> pkg_session pkg_session_checkpoint_policy --> pkg_session_persistence pkg_session_checkpoint_policy --> pkg_tools - pkg_session_title_all_prompts_llm --> pkg_llm - pkg_session_title_all_prompts_llm --> pkg_session - pkg_session_title_all_prompts_llm --> pkg_session_title - pkg_session_title_all_prompts_llm --> pkg_session_title_llm pkg_session_title_first_prompt_llm --> pkg_llm pkg_session_title_first_prompt_llm --> pkg_session pkg_session_title_first_prompt_llm --> pkg_session_title pkg_session_title_first_prompt_llm --> pkg_session_title_llm + pkg_tool_working_directory --> pkg_tools + pkg_tool_working_directory --> pkg_working_directory pkg_shell_env --> pkg_app_boot pkg_shell_env --> pkg_home_paths pkg_shell_env --> pkg_shell @@ -965,11 +991,13 @@ flowchart TD pkg_tool_bash_persistent --> pkg_terminal pkg_tool_bash_persistent --> pkg_timeout pkg_tool_bash_persistent --> pkg_tools + pkg_tool_bash_persistent --> pkg_working_directory pkg_tool_pwsh_persistent --> pkg_agent pkg_tool_pwsh_persistent --> pkg_output_retention pkg_tool_pwsh_persistent --> pkg_terminal pkg_tool_pwsh_persistent --> pkg_timeout pkg_tool_pwsh_persistent --> pkg_tools + pkg_tool_pwsh_persistent --> pkg_working_directory pkg_ssh --> pkg_brand pkg_ssh --> pkg_fs pkg_ssh --> pkg_fs_local @@ -980,19 +1008,11 @@ flowchart TD pkg_ssh --> pkg_session_projection pkg_ssh --> pkg_subprocess pkg_ssh --> pkg_subprocess_local - pkg_tool_terminal --> pkg_agent - pkg_tool_terminal --> pkg_jobs - pkg_tool_terminal --> pkg_llm - pkg_tool_terminal --> pkg_output_retention - pkg_tool_terminal --> pkg_system_prompt - pkg_tool_terminal --> pkg_terminal - pkg_tool_terminal --> pkg_tools pkg_llm_replay --> pkg_compaction pkg_llm_replay --> pkg_deepseek_llm_api_extensions pkg_llm_replay --> pkg_llm pkg_llm_replay --> pkg_session pkg_tool_workflow --> pkg_agent - pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_jobs pkg_tool_workflow --> pkg_llm pkg_tool_workflow --> pkg_session @@ -1031,6 +1051,15 @@ flowchart TD pkg_experimental_auto_review --> pkg_permission_presets pkg_experimental_auto_review --> pkg_session pkg_experimental_auto_review --> pkg_tools + pkg_experimental_auto_review --> pkg_working_directory + pkg_webhook --> pkg_agent + pkg_webhook --> pkg_agent_default_model + pkg_webhook --> pkg_agent_preset_registry + pkg_webhook --> pkg_llm + pkg_webhook --> pkg_permission_presets + pkg_webhook --> pkg_session + pkg_webhook --> pkg_session_title + pkg_webhook --> pkg_workspace pkg_tool_cordis --> pkg_agent pkg_tool_cordis --> pkg_app_boot pkg_tool_cordis --> pkg_cordis_host_runner @@ -1064,6 +1093,7 @@ flowchart TD pkg_tool_bash --> pkg_system_prompt pkg_tool_bash --> pkg_tools pkg_tool_bash --> pkg_user_approval + pkg_tool_bash --> pkg_working_directory pkg_tool_pwsh --> pkg_agent pkg_tool_pwsh --> pkg_jobs pkg_tool_pwsh --> pkg_llm @@ -1074,6 +1104,7 @@ flowchart TD pkg_tool_pwsh --> pkg_system_prompt pkg_tool_pwsh --> pkg_tools pkg_tool_pwsh --> pkg_user_approval + pkg_tool_pwsh --> pkg_working_directory pkg_fs_ssh --> pkg_fs pkg_fs_ssh --> pkg_sandbox pkg_fs_ssh --> pkg_sandbox_policy @@ -1085,25 +1116,16 @@ flowchart TD pkg_subprocess_ssh --> pkg_subprocess_local pkg_agent_loop_testkit --> pkg_agent pkg_agent_loop_testkit --> pkg_agent_loop + pkg_agent_loop_testkit --> pkg_fs_local pkg_agent_loop_testkit --> pkg_llm pkg_agent_loop_testkit --> pkg_session pkg_agent_loop_testkit --> pkg_session_projection pkg_agent_loop_testkit --> pkg_system_prompt pkg_agent_loop_testkit --> pkg_tools - pkg_webhook --> pkg_agent - pkg_webhook --> pkg_agent_default_model - pkg_webhook --> pkg_agent_preset_registry - pkg_webhook --> pkg_invariants - pkg_webhook --> pkg_llm - pkg_webhook --> pkg_permission_presets - pkg_webhook --> pkg_session - pkg_webhook --> pkg_session_title - pkg_webhook --> pkg_workspace + pkg_agent_loop_testkit --> pkg_working_directory pkg_subagent --> pkg_agent pkg_subagent --> pkg_agent_preset_registry pkg_subagent --> pkg_attachment - pkg_subagent --> pkg_invariants - pkg_subagent --> pkg_jobs pkg_subagent --> pkg_llm pkg_subagent --> pkg_permission_presets pkg_subagent --> pkg_sandbox @@ -1118,6 +1140,7 @@ flowchart TD pkg_subagent --> pkg_typert_protocol pkg_subagent --> pkg_user_approval pkg_subagent --> pkg_util_time + pkg_subagent --> pkg_working_directory pkg_subagent --> pkg_workspace pkg_spill_policy --> pkg_attachment pkg_spill_policy --> pkg_fs @@ -1130,14 +1153,6 @@ flowchart TD pkg_session_query_sqlite --> pkg_session pkg_session_query_sqlite --> pkg_session_persistence pkg_session_query_sqlite --> pkg_session_query - pkg_tool_session_query --> pkg_agent - pkg_tool_session_query --> pkg_llm - pkg_tool_session_query --> pkg_session - pkg_tool_session_query --> pkg_session_projection - pkg_tool_session_query --> pkg_session_query - pkg_tool_session_query --> pkg_system_prompt - pkg_tool_session_query --> pkg_timeout - pkg_tool_session_query --> pkg_tools pkg_acp --> pkg_agent pkg_acp --> pkg_attachment pkg_acp --> pkg_llm @@ -1146,6 +1161,7 @@ flowchart TD pkg_acp --> pkg_session_persistence pkg_acp --> pkg_token_meter pkg_acp --> pkg_user_approval + pkg_plugin_manager --> pkg_agent_preset_registry pkg_plugin_manager --> pkg_brand pkg_plugin_manager --> pkg_hmr pkg_plugin_manager --> pkg_host_plugin_inventory @@ -1155,12 +1171,6 @@ flowchart TD pkg_plugin_manager --> pkg_tools pkg_plugin_manager --> pkg_typert_protocol pkg_plugin_manager --> pkg_user_approval - pkg_headless --> pkg_agent - pkg_headless --> pkg_agent_default_model - pkg_headless --> pkg_fs - pkg_headless --> pkg_llm - pkg_headless --> pkg_session - pkg_headless --> pkg_session_query pkg_compaction_tool_result_pruner --> pkg_compaction pkg_compaction_tool_result_pruner --> pkg_llm pkg_compaction_tool_result_pruner --> pkg_session @@ -1171,6 +1181,29 @@ flowchart TD pkg_experimental_browser_use_runtime --> pkg_scope pkg_experimental_browser_use_runtime --> pkg_system_prompt pkg_experimental_browser_use_runtime --> pkg_tools + pkg_experimental_browser_use_runtime --> pkg_working_directory + pkg_experimental_claude_code_mods --> pkg_agent + pkg_experimental_claude_code_mods --> pkg_commands + pkg_experimental_claude_code_mods --> pkg_fs + pkg_experimental_claude_code_mods --> pkg_llm + pkg_experimental_claude_code_mods --> pkg_scope + pkg_experimental_claude_code_mods --> pkg_session + pkg_experimental_claude_code_mods --> pkg_session_projection + pkg_experimental_claude_code_mods --> pkg_storage_domain + pkg_experimental_claude_code_mods --> pkg_subprocess + pkg_experimental_claude_code_mods --> pkg_token_meter + pkg_experimental_claude_code_mods --> pkg_tools + pkg_experimental_claude_code_mods --> pkg_typert_protocol + pkg_experimental_claude_code_mods --> pkg_user_questions + pkg_experimental_claude_code_mods --> pkg_working_directory + pkg_tool_session_query --> pkg_agent + pkg_tool_session_query --> pkg_llm + pkg_tool_session_query --> pkg_session + pkg_tool_session_query --> pkg_session_projection + pkg_tool_session_query --> pkg_session_query + pkg_tool_session_query --> pkg_system_prompt + pkg_tool_session_query --> pkg_timeout + pkg_tool_session_query --> pkg_tools pkg_webhook_github --> pkg_credentials pkg_webhook_github --> pkg_host_webserver pkg_webhook_github --> pkg_session @@ -1191,15 +1224,11 @@ flowchart TD pkg_subagent_codex --> pkg_subagent pkg_subagent_codex --> pkg_subprocess pkg_subagent_codex --> pkg_timeout - pkg_subagent_in_process_driver --> pkg_agent - pkg_subagent_in_process_driver --> pkg_llm - pkg_subagent_in_process_driver --> pkg_session - pkg_subagent_in_process_driver --> pkg_subagent - pkg_subagent_in_process_driver --> pkg_system_prompt - pkg_subagent_in_process_driver --> pkg_tools + pkg_subagent_fork_in_process --> pkg_agent + pkg_subagent_fork_in_process --> pkg_session + pkg_subagent_fork_in_process --> pkg_subagent + pkg_subagent_spawn_in_process --> pkg_subagent pkg_tool_subagent --> pkg_agent - pkg_tool_subagent --> pkg_invariants - pkg_tool_subagent --> pkg_jobs pkg_tool_subagent --> pkg_llm pkg_tool_subagent --> pkg_scope pkg_tool_subagent --> pkg_session @@ -1211,13 +1240,6 @@ flowchart TD pkg_tool_subagent_control --> pkg_session pkg_tool_subagent_control --> pkg_subagent pkg_tool_subagent_control --> pkg_tools - pkg_hooks_claude_code --> pkg_agent - pkg_hooks_claude_code --> pkg_hook_protocol - pkg_hooks_claude_code --> pkg_llm - pkg_hooks_claude_code --> pkg_session - pkg_hooks_claude_code --> pkg_session_projection - pkg_hooks_claude_code --> pkg_subagent - pkg_hooks_claude_code --> pkg_tools pkg_api_session_controller --> pkg_agent pkg_api_session_controller --> pkg_agent_default_model pkg_api_session_controller --> pkg_agent_preset_registry @@ -1246,7 +1268,15 @@ flowchart TD pkg_api_session_controller --> pkg_util_time pkg_api_session_controller --> pkg_util_values pkg_api_session_controller --> pkg_util_workspace_path + pkg_api_session_controller --> pkg_working_directory pkg_api_session_controller --> pkg_workspace + pkg_headless --> pkg_agent + pkg_headless --> pkg_agent_default_model + pkg_headless --> pkg_fs + pkg_headless --> pkg_llm + pkg_headless --> pkg_session + pkg_headless --> pkg_session_query + pkg_headless --> pkg_subagent pkg_compaction_basic --> pkg_agent pkg_compaction_basic --> pkg_commands pkg_compaction_basic --> pkg_compaction @@ -1268,21 +1298,28 @@ flowchart TD pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_invariants pkg_experimental_agent_team --> pkg_llm pkg_experimental_agent_team --> pkg_session pkg_experimental_agent_team --> pkg_session_persistence pkg_experimental_agent_team --> pkg_session_projection pkg_experimental_agent_team --> pkg_subagent - pkg_sdk_protocol --> pkg_llm - pkg_sdk_protocol --> pkg_session - pkg_sdk_protocol --> pkg_subagent + pkg_hooks_claude_code --> pkg_agent + pkg_hooks_claude_code --> pkg_hook_protocol + pkg_hooks_claude_code --> pkg_llm + pkg_hooks_claude_code --> pkg_session + pkg_hooks_claude_code --> pkg_session_projection + pkg_hooks_claude_code --> pkg_subagent + pkg_hooks_claude_code --> pkg_tools + pkg_hooks_claude_code --> pkg_working_directory pkg_tool_ralph --> pkg_agent pkg_tool_ralph --> pkg_llm pkg_tool_ralph --> pkg_subagent pkg_tool_ralph --> pkg_system_prompt pkg_tool_ralph --> pkg_tools pkg_tool_ralph --> pkg_workflow + pkg_sdk_protocol --> pkg_llm + pkg_sdk_protocol --> pkg_session + pkg_sdk_protocol --> pkg_subagent pkg_workflow_ptc --> pkg_agent pkg_workflow_ptc --> pkg_llm pkg_workflow_ptc --> pkg_ptc_runtime @@ -1292,12 +1329,7 @@ flowchart TD pkg_workflow_ptc --> pkg_subagent pkg_workflow_ptc --> pkg_tools pkg_workflow_ptc --> pkg_workflow - pkg_subagent_fork_in_process --> pkg_agent - pkg_subagent_fork_in_process --> pkg_session - pkg_subagent_fork_in_process --> pkg_subagent - pkg_subagent_fork_in_process --> pkg_subagent_in_process_driver - pkg_subagent_spawn_in_process --> pkg_subagent - pkg_subagent_spawn_in_process --> pkg_subagent_in_process_driver + pkg_workflow_ptc --> pkg_working_directory pkg_experimental_client_ui_agent_team --> pkg_api_session_controller pkg_experimental_client_ui_agent_team --> pkg_client_locale pkg_experimental_client_ui_agent_team --> pkg_client_ui_conversation @@ -1308,6 +1340,22 @@ flowchart TD pkg_experimental_client_ui_agent_team --> pkg_client_ui_workspace pkg_experimental_client_ui_agent_team --> pkg_experimental_agent_team pkg_experimental_client_ui_agent_team --> pkg_session + pkg_experimental_client_ui_claude_code_mods --> pkg_api_remotes + pkg_experimental_client_ui_claude_code_mods --> pkg_api_session_controller + pkg_experimental_client_ui_claude_code_mods --> pkg_client_locale + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_conversation + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_primitives + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_renderer + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_session + pkg_experimental_client_ui_claude_code_mods --> pkg_client_ui_slots + pkg_experimental_client_ui_claude_code_mods --> pkg_experimental_claude_code_mods + pkg_experimental_client_ui_claude_code_mods --> pkg_session + pkg_experimental_client_ui_claude_code_mods --> pkg_typert_protocol + pkg_experimental_client_ui_cot_translation --> pkg_api_session_controller + pkg_experimental_client_ui_cot_translation --> pkg_experimental_translator + pkg_experimental_client_ui_cot_translation --> pkg_session + pkg_experimental_client_ui_cot_translation --> pkg_settings + pkg_experimental_client_ui_cot_translation --> pkg_typert_protocol pkg_experimental_client_ui_voice_input --> pkg_api_gateway pkg_experimental_client_ui_voice_input --> pkg_api_remotes pkg_experimental_client_ui_voice_input --> pkg_api_session_controller @@ -1331,7 +1379,6 @@ flowchart TD pkg_schedule --> pkg_agent pkg_schedule --> pkg_api_session_controller pkg_schedule --> pkg_brand - pkg_schedule --> pkg_invariants pkg_schedule --> pkg_llm pkg_schedule --> pkg_session pkg_schedule --> pkg_session_persistence @@ -1352,6 +1399,7 @@ flowchart TD pkg_sdk_jsonrpc_server --> pkg_sdk_protocol pkg_sdk_jsonrpc_server --> pkg_session pkg_sdk_jsonrpc_server --> pkg_subagent + pkg_sdk_jsonrpc_server --> pkg_working_directory pkg_client_test_runtime --> pkg_api_gateway pkg_client_test_runtime --> pkg_api_session_controller pkg_client_test_runtime --> pkg_api_workspace_controller @@ -1405,6 +1453,7 @@ flowchart TD | [`util-workspace-path`](../packages/util/workspace-path) | `util` | — | | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions) | `llm` | — | | [`llm`](../packages/llm/llm) | `llm` | — | +| [`scope`](../packages/core/scope) | `core` | — | | [`api-job-controller`](../packages/api/job-controller) | `api` | — | | [`api-workspace-files`](../packages/api/workspace-files) | `api` | — | | [`cmdline`](../packages/boot/cmdline) | `boot` | — | @@ -1412,6 +1461,7 @@ flowchart TD | [`base`](../packages/bundle/base) | `bundle` | — | | [`sdk-app`](../packages/bundle/sdk-app) | `bundle` | — | | [`sdk-minimal`](../packages/bundle/sdk-minimal) | `bundle` | — | +| [`client-file-upload`](../packages/client/file-upload) | `client` | — | | [`client-hmr`](../packages/client/hmr) | `client` | — | | [`client-locale`](../packages/client/locale) | `client` | — | | [`client-modules`](../packages/client/modules) | `client` | — | @@ -1473,8 +1523,17 @@ flowchart TD | [`client-ui-workflow-run`](../packages/client/ui-workflow-run) | `client` | — | | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | — | | [`client-web`](../packages/client/web) | `client` | — | +| [`credentials`](../packages/credentials/credentials) | `credentials` | — | | [`office-to-pdf`](../packages/document/office-to-pdf) | `document` | — | | [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | +| [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | +| [`experimental-cot-translation-bundle`](../packages/experimental/cot-translation-bundle) | `experimental` | — | +| [`experimental-inspector-profile`](../packages/experimental/inspector-profile) | `experimental` | — | +| [`experimental-ralph-bundle`](../packages/experimental/ralph-bundle) | `experimental` | — | +| [`experimental-session-inspector`](../packages/experimental/session-inspector) | `experimental` | — | +| [`experimental-session-search`](../packages/experimental/session-search) | `experimental` | — | +| [`experimental-session-titles-bundle`](../packages/experimental/session-titles-bundle) | `experimental` | — | +| [`experimental-terminal-bundle`](../packages/experimental/terminal-bundle) | `experimental` | — | | [`experimental-voice-input-bundle`](../packages/experimental/voice-input-bundle) | `experimental` | — | | [`experimental-webworker-packer`](../packages/experimental/webworker-packer) | `experimental` | — | | [`client-ui-cordis`](../packages/extensions/ui-cordis) | `extensions` | — | @@ -1484,7 +1543,6 @@ flowchart TD | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | — | | [`host-open-in-app`](../packages/host/open-in-app) | `host` | — | | [`host-webserver`](../packages/host/webserver) | `host` | — | -| [`invariants`](../packages/runtime-diagnostics/invariants) | `runtime-diagnostics` | — | | [`session-format`](../packages/session/session-format) | `session` | — | | [`session-format-v0-to-v1`](../packages/session/session-format-v0-to-v1) | `session` | — | | [`session-format-v1-to-v2`](../packages/session/session-format-v1-to-v2) | `session` | — | @@ -1496,59 +1554,57 @@ flowchart TD | [`typert-generator`](../packages/typert/generator) | `typert` | — | | [`typert-protocol`](../packages/typert/protocol) | `typert` | — | | [`typert-registry`](../packages/typert/registry) | `typert` | — | -| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants) | +| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | +| [`system-prompt`](../packages/core/system-prompt) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | | [`web`](../packages/web/web) | `web` | [`llm`](../packages/llm/llm) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand) | | [`browser-use`](../packages/browser-use/browser-use) | `browser-use` | [`brand`](../packages/util/brand) | +| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | | [`computer-use`](../packages/computer-use/computer-use) | `computer-use` | [`brand`](../packages/util/brand) | -| [`credentials`](../packages/credentials/credentials) | `credentials` | [`invariants`](../packages/runtime-diagnostics/invariants) | -| [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`llm`](../packages/llm/llm) | +| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver) | | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths) | | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) | +| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-json`](../packages/storage/storage-json) | `storage` | [`storage`](../packages/storage/storage) | | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`storage`](../packages/storage/storage) | | [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy) | | [`remote-mock`](../packages/test-support/remote-mock) | `test-support` | [`typert-protocol`](../packages/typert/protocol) | | [`typert-loader`](../packages/typert/loader) | `typert` | [`typert-registry`](../packages/typert/registry) | -| [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) | -| [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | -| [`skill`](../packages/skill/skill) | `skill` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | +| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) | | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) | -| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | -| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | -| [`client-connection`](../packages/client/connection) | `client` | [`scope`](../packages/core/scope) | -| [`client-file-upload`](../packages/client/file-upload) | `client` | [`scope`](../packages/core/scope) | -| [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) | -| [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment) | -| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | -| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`skill`](../packages/skill/skill) | -| [`skill-office`](../packages/skill/skill-office) | `skill` | [`skill`](../packages/skill/skill) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection) | +| [`api-terminal-controller`](../packages/api/terminal-controller) | `api` | [`subprocess`](../packages/subprocess/subprocess) | +| [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths) | | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) | +| [`experimental-computer-use-cua-driver-mcp`](../packages/experimental/computer-use-cua-driver-mcp) | `experimental` | [`computer-use`](../packages/computer-use/computer-use) | +| [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver) | +| [`skill-badge`](../packages/experimental/skill-badge) | `experimental` | [`skill`](../packages/skill/skill) | | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver) | | [`persona`](../packages/preset/persona) | `preset` | [`system-prompt`](../packages/core/system-prompt) | | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`sandbox-windows-acl`](../packages/sandbox/sandbox-windows-acl) | `sandbox` | [`skill`](../packages/skill/skill), [`subprocess`](../packages/subprocess/subprocess) | | [`session-format-catalog`](../packages/session/session-format-catalog) | `session` | [`session`](../packages/core/session) | -| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`session-projection`](../packages/session/session-projection) | `session` | [`session`](../packages/core/session) | -| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) | +| [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`session`](../packages/core/session) | +| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`otel`](../packages/telemetry/otel) | `telemetry` | [`session`](../packages/core/session) | | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`session`](../packages/core/session) | -| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | +| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) | | [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | | [`hmr`](../packages/boot/hmr) | `boot` | [`app-boot`](../packages/boot/app-boot), [`cmdline`](../packages/boot/cmdline) | | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | `experimental` | [`settings`](../packages/settings/settings) | +| [`experimental-translator`](../packages/experimental/translator) | `experimental` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`host-product-telemetry-otel`](../packages/host/product-telemetry-otel) | `host` | [`otel`](../packages/telemetry/otel) | | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime) | `ptc-runtime` | [`sandbox`](../packages/sandbox/sandbox) | | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) | @@ -1557,148 +1613,154 @@ flowchart TD | [`session-stats`](../packages/session/session-stats) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-turn-outline`](../packages/session/session-turn-outline) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`shell`](../packages/shell/shell) | `shell` | [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) | -| [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | +| [`workspace`](../packages/workspace/workspace) | `workspace` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) | | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) | -| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | +| [`agent`](../packages/core/agent) | `core` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol), [`util-values`](../packages/util/values), [`workspace`](../packages/workspace/workspace) | | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs) | | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs) | | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`skill`](../packages/skill/skill) | -| [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`config-editor`](../packages/boot/config-editor) | `boot` | [`app-boot`](../packages/boot/app-boot), [`hmr`](../packages/boot/hmr) | | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | `experimental` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | `experimental` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`subprocess`](../packages/subprocess/subprocess) | +| [`hook-protocol`](../packages/experimental/hook-protocol) | `experimental` | [`session`](../packages/core/session), [`shell`](../packages/shell/shell) | | [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`bash-local`](../packages/shell/bash-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key) | `llm` | [`credentials`](../packages/credentials/credentials), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | +| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) | | [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) | -| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | +| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | | [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent) | -| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) | | [`deepseek-account`](../packages/credentials/deepseek-account) | `credentials` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) | -| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | | [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) | -| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | -| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | +| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`session`](../packages/core/session), [`workspace`](../packages/workspace/workspace) | +| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`session`](../packages/core/session) | -| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`working-directory`](../packages/session/working-directory) | `session` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) | | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand) | | [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`llm-deepseek-account`](../packages/llm/llm-deepseek-account) | `llm` | [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm) | -| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`api-account-controller`](../packages/api/account-controller) | `api` | [`agent`](../packages/core/agent), [`deepseek-account`](../packages/credentials/deepseek-account), [`typert-protocol`](../packages/typert/protocol) | -| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | +| [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`deepseek-account-platform`](../packages/credentials/deepseek-account-platform) | `credentials` | [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`deepseek-account`](../packages/credentials/deepseek-account), [`host-webserver`](../packages/host/webserver) | +| [`experimental-ptc-runtime-python`](../packages/experimental/ptc-runtime-python) | `experimental` | [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`timeout`](../packages/util/timeout), [`util-values`](../packages/util/values) | +| [`experimental-worktree`](../packages/experimental/worktree) | `experimental` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`working-directory`](../packages/session/working-directory) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | -| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | +| [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) | | [`ptc-runtime-node`](../packages/ptc-runtime/ptc-runtime-node) | `ptc-runtime` | [`fs`](../packages/fs/fs), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | | [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) | -| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) | -| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal), [`working-directory`](../packages/session/working-directory) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | +| [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`output-retention`](../packages/util/output-retention), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) | -| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | +| [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-workspace-dependencies`](../packages/skill/tool-workspace-dependencies) | `skill` | [`tools`](../packages/core/tools) | | [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | -| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction) | | [`compaction-image-offload`](../packages/compaction/compaction-image-offload) | `compaction` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | -| [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | -| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-present`](../packages/deliverables/tool-present) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`workspace-changes`](../packages/deliverables/workspace-changes) | `deliverables` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | +| [`experimental-browser-use-chrome-devtools-mcp`](../packages/experimental/browser-use-chrome-devtools-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`experimental-browser-use-playwright-mcp`](../packages/experimental/browser-use-playwright-mcp) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`experimental-browser-use-stagehand-native`](../packages/experimental/browser-use-stagehand-native) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`experimental-computer-use-cua-driver-native`](../packages/experimental/computer-use-cua-driver-native) | `experimental` | [`computer-use`](../packages/computer-use/computer-use), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`experimental-tool-worktree`](../packages/experimental/tool-worktree) | `experimental` | [`experimental-worktree`](../packages/experimental/worktree), [`tools`](../packages/core/tools) | +| [`hooks-codex`](../packages/experimental/hooks-codex) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`session-title-all-prompts-llm`](../packages/experimental/session-title-all-prompts-llm) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | +| [`tool-terminal`](../packages/experimental/tool-terminal) | `experimental` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`typert-protocol`](../packages/typert/protocol) | | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | | [`tool-call-timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) | | [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | +| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) | | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | -| [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | +| [`tool-working-directory`](../packages/session/tool-working-directory) | `session` | [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`shell-env`](../packages/shell/shell-env) | `shell` | [`app-boot`](../packages/boot/app-boot), [`home-paths`](../packages/util/home-paths), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) | -| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | -| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`output-retention`](../packages/util/output-retention), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`ssh`](../packages/ssh/ssh) | `ssh` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-local`](../packages/sandbox/sandbox-local), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | -| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) | | [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | +| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) | | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) | | [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) | -| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | +| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`webhook`](../packages/experimental/webhook) | `experimental` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | | [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`app-boot`](../packages/boot/app-boot), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`tools`](../packages/core/tools) | | [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) | | [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-resources`](../packages/mcp/mcp-resources), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`agent-preset`](../packages/preset/agent-preset) | `preset` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`otel`](../packages/telemetry/otel), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) | -| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | -| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | +| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`working-directory`](../packages/session/working-directory) | | [`fs-ssh`](../packages/ssh/fs-ssh) | `ssh` | [`fs`](../packages/fs/fs), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`ssh`](../packages/ssh/ssh) | | [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) | | [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) | -| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | +| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) | | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | -| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) | -| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | -| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | +| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) | | [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`webhook-github`](../packages/webhook/webhook-github) | `webhook` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/webhook/webhook) | +| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) | +| [`tool-session-query`](../packages/experimental/tool-session-query) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`webhook-github`](../packages/experimental/webhook-github) | `experimental` | [`credentials`](../packages/credentials/credentials), [`host-webserver`](../packages/host/webserver), [`session`](../packages/core/session), [`webhook`](../packages/experimental/webhook) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent) | +| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) | +| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`credentials`](../packages/credentials/credentials), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`settings`](../packages/settings/settings), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) | +| [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`subagent`](../packages/subagent/subagent) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | +| [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | +| [`tool-ralph`](../packages/experimental/tool-ralph) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | -| [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process) | `subagent` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | -| [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process) | `subagent` | [`subagent`](../packages/subagent/subagent), [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | +| [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow), [`working-directory`](../packages/session/working-directory) | | [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session) | +| [`experimental-client-ui-claude-code-mods`](../packages/experimental/client-ui-claude-code-mods) | `experimental` | [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | +| [`experimental-client-ui-cot-translation`](../packages/experimental/client-ui-cot-translation) | `experimental` | [`api-session-controller`](../packages/api/session-controller), [`experimental-translator`](../packages/experimental/translator), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | [`api-gateway`](../packages/api/gateway), [`api-remotes`](../packages/api/remotes), [`api-session-controller`](../packages/api/session-controller), [`client-locale`](../packages/client/locale), [`client-store`](../packages/client/store), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-plugin-manager`](../packages/client/ui-plugin-manager), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-slots`](../packages/client/ui-slots), [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text), [`experimental-speech-to-text`](../packages/experimental/speech-to-text), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | +| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | -| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`sdk-jsonrpc-server`](../packages/sdk/server) | `sdk` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`llm-deepseek-api-key`](../packages/llm/llm-deepseek-api-key), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`working-directory`](../packages/session/working-directory) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`tool-schedule`](../packages/schedule/tool-schedule) | `schedule` | [`llm`](../packages/llm/llm), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index 664979dbe3..66b5407eca 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -6,11 +6,11 @@ en: a11f106978c9dce7 zh: a11f106978c9dce7 /session-persistence-event-catalog: - en: e7c80d1de4d328b4 - zh: d9f368c27fd9f998 + en: 1387c4505dde2faf + zh: 48570fa1c13d6910 /session-persistence-event-catalog/persistence-type-fingerprints: - en: 432dabeb96013cb8 - zh: e8a7c3ea1cc50c16 + en: 1985ad3caf7d8134 + zh: 756101dea110f658 /session-persistence-event-catalog/event-envelope: en: 7ebb1a2e0dd1f916 zh: 8946f415dda5c1b8 @@ -108,11 +108,11 @@ en: 8d6fa78d450a0e56 zh: 8d6fa78d450a0e56 /session-persistence-event-catalog/events/hook/hook-invoked-log-only: - en: 7e87780c1e9328b7 - zh: 79b535ed8a26bc4f + en: 705ffb04951178a1 + zh: 6031b9e7b48f5549 /session-persistence-event-catalog/events/hook/hook-result-log-only: - en: 5479d86486adbc40 - zh: b9df61258535c7da + en: b84c2cbfca534935 + zh: ee1d71fad6dfb896 /session-persistence-event-catalog/events/image: en: 4378f94cd30a2ba8 zh: 4378f94cd30a2ba8 @@ -153,8 +153,8 @@ en: 4294f78d26ed985b zh: 4294f78d26ed985b /session-persistence-event-catalog/events/plan/plan-mode-log-only: - en: 9ae5537eeae238de - zh: 6be761c7fd83fdce + en: 862175bfe8692ef7 + zh: a8ab123bf674cf77 /session-persistence-event-catalog/events/request: en: a8df82440ebdf5f8 zh: a8df82440ebdf5f8 @@ -186,8 +186,8 @@ en: 6c6a27578dff5828 zh: 9a5add2bc0df2d0a /session-persistence-event-catalog/events/session/session-title-llm-request-log-only: - en: 201b220320c466ab - zh: 2823df91f681793d + en: 5eb108772a8498c9 + zh: 1fbc45e377084bae /session-persistence-event-catalog/events/session-log-deepseek: en: 7b827672c80aecdd zh: 7b827672c80aecdd @@ -207,8 +207,8 @@ en: 9630752e97ddbfbc zh: 9630752e97ddbfbc /session-persistence-event-catalog/events/subagent/subagent-catalog-log-only: - en: b0d8c66ca02aea4d - zh: 74adeca7962a61ed + en: e6ce4b5a98ff2f52 + zh: 9a8e112754679aef /session-persistence-event-catalog/events/subagent/subagent-descriptor-log-only: en: f5d53639dc5afdfc zh: 8c553e7397d08832 @@ -249,11 +249,11 @@ en: 96cc0c05d3a2d01b zh: f6f86d5bd29db6a5 /session-persistence-event-catalog/events/tool/tool-ptc-dispatch-log-only: - en: 1c5fbea27fd81894 - zh: 0188fe2faf5b263c + en: 3d5d72b99e702a8c + zh: e8c3b82cd5361c4a /session-persistence-event-catalog/events/tool/tool-ptc-dispatch-start-log-only: - en: 14548299cbb98be3 - zh: 3b081f53e8591fc0 + en: de38cf70d81ad7c3 + zh: dd0a2a8be099271d /session-persistence-event-catalog/events/tool/tool-result-surface: en: 2857bd718f6e7264 zh: 61aac43cff6451b6 @@ -293,6 +293,12 @@ /session-persistence-event-catalog/events/web/web-deepseek-search-llm-request-log-only: en: f12c598242992aa3 zh: 08609553e15a91f3 +/session-persistence-event-catalog/events/working-directory: + en: e8dd0cebbb930eb7 + zh: e8dd0cebbb930eb7 +/session-persistence-event-catalog/events/working-directory/working-directory-change-log-only: + en: 075369b880bd304b + zh: 0eca94dbaf5d9090 /session-persistence-event-catalog/events/workspace: en: 63ee1a85e4b59ae3 zh: 63ee1a85e4b59ae3 @@ -468,8 +474,11 @@ en: 0a0098cf30464dcf zh: 0a0098cf30464dcf /session-persistence-event-catalog/resolved-persistence-types/every: - en: eeb0f1af5d02bc22 - zh: eeb0f1af5d02bc22 + en: ea3afecddf32f372 + zh: ea3afecddf32f372 +/session-persistence-event-catalog/resolved-persistence-types/external: + en: 2b8208db8e7bfa49 + zh: 2b8208db8e7bfa49 /session-persistence-event-catalog/resolved-persistence-types/failed: en: 43330e69d3defaab zh: 43330e69d3defaab @@ -912,8 +921,14 @@ en: a773058b4f0685f6 zh: a773058b4f0685f6 /session-persistence-event-catalog/resolved-persistence-types/webhook: - en: 390b9ebc0521ee93 - zh: 390b9ebc0521ee93 + en: e7b2736a2adb5db0 + zh: e7b2736a2adb5db0 +/session-persistence-event-catalog/resolved-persistence-types/working-directory: + en: 6aabd13040c9e590 + zh: 6aabd13040c9e590 +/session-persistence-event-catalog/resolved-persistence-types/working-directory-change: + en: 8fe8f07c8548191d + zh: 8fe8f07c8548191d /session-persistence-event-catalog/resolved-persistence-types/workspace-write: en: a529f422b9889f7d zh: a529f422b9889f7d @@ -933,17 +948,17 @@ en: 9ec6b8bd755f3e33 zh: 9ec6b8bd755f3e33 /session-persistence-event-catalog/resolved-persistence-types/agentinstructionchange: - en: c79fd9b4b7a0a5ba - zh: d196895911636220 + en: 98710438ecb65dd6 + zh: 82c06bda2056f000 /session-persistence-event-catalog/resolved-persistence-types/agentinstructionchange~2: en: dba6b1babdde0cfe zh: 99b209824b99b693 /session-persistence-event-catalog/resolved-persistence-types/agentinstructionsource: - en: 4b46cc6c412c939d - zh: 77fc7f61972ab194 + en: ef87a859325357fc + zh: f132439b0161ece6 /session-persistence-event-catalog/resolved-persistence-types/agentmessagesource: - en: 69385a93c3abf7f5 - zh: 0adbb7fd29dccb2e + en: a1f024101085f6f1 + zh: eedf4dc28c048838 /session-persistence-event-catalog/resolved-persistence-types/approvaloutcome: en: bb7b3652609ac651 zh: bdde4e652413c5eb @@ -984,11 +999,11 @@ en: 586743dfeab78c55 zh: 26ccef4a02dd7a58 /session-persistence-event-catalog/resolved-persistence-types/deepseeksearchllmrequest: - en: ea25cd944b4b4020 - zh: 87f8944f766d3a85 + en: 8bb149f81f7c2544 + zh: 7e9c410f530b7c82 /session-persistence-event-catalog/resolved-persistence-types/developermessage: - en: af7ce240db9e3463 - zh: cd15cd0cd369c2c0 + en: da88473da1299be0 + zh: fe756703e34fcc42 /session-persistence-event-catalog/resolved-persistence-types/epochheader: en: d31020fa1fda6fa4 zh: 77984c4bf09443fb @@ -1032,11 +1047,11 @@ en: d13de5c951afd4e5 zh: 44069b6d0f3b8303 /session-persistence-event-catalog/resolved-persistence-types/headerline: - en: 0228dc7dcff3ff5a - zh: bfab168767453f00 + en: d0e4b453af58af7f + zh: 8209d2206840b137 /session-persistence-event-catalog/resolved-persistence-types/hookdialect: - en: c8420c48f6167ad9 - zh: a6dc3dc1ee50ea81 + en: c5418d1e906e9114 + zh: 0bc528427386d8a4 /session-persistence-event-catalog/resolved-persistence-types/imageattachmentref: en: 76ee1e266cd651da zh: 55c7c95a223ccbed @@ -1086,11 +1101,11 @@ en: d2a3b6594fc71ea0 zh: a5a7df42594fdb57 /session-persistence-event-catalog/resolved-persistence-types/llmcallconfig: - en: aa3a91341bf04b9e - zh: d767b45d45846333 + en: 620752606b81ad3b + zh: 336903c12d1d3bdc /session-persistence-event-catalog/resolved-persistence-types/llmcallconfigadapterdefaults: - en: 5cdc7289f276f93e - zh: 11b399ac1a145e9c + en: 8f669d7819805099 + zh: 391d51d7835afe02 /session-persistence-event-catalog/resolved-persistence-types/llmfailure: en: a18d2091f7e64726 zh: 550fc25036bd096e @@ -1098,11 +1113,11 @@ en: 6dbe099c62f7c226 zh: 6fd788f63c038c91 /session-persistence-event-catalog/resolved-persistence-types/llmretrystartedeventdata: - en: f4202ef53de39006 - zh: f8f1d0bfc0451c31 + en: cc63fb8b8fad7d63 + zh: 2026e3da98cad54e /session-persistence-event-catalog/resolved-persistence-types/message: - en: a3d2a5be822c5a86 - zh: 871cc3cafabe99be + en: e478c640a9c17a4f + zh: b4c6a4930a3789a1 /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackdelete: en: c582953270d4ca44 zh: 966ccfe2e7660a2a @@ -1113,14 +1128,14 @@ en: f58575a1fc96df42 zh: 9f0c6ed9033e5c9d /session-persistence-event-catalog/resolved-persistence-types/messagefeedbackrating: - en: ef7b940d5654ce5a - zh: 72d4c5b3f6e2ff17 + en: 46f4cbb41e7c053a + zh: 511e10d1be567bc5 /session-persistence-event-catalog/resolved-persistence-types/messagesource: - en: f1789e91fd4338e3 - zh: 8250eb33cda749b5 + en: c4ed18665fbc1e15 + zh: 6db2b051fc329408 /session-persistence-event-catalog/resolved-persistence-types/message~2: - en: 412cc1296786d489 - zh: f457e8629515a526 + en: 2c79aaf7cec7d9d6 + zh: 1403dd3cf9ed4a82 /session-persistence-event-catalog/resolved-persistence-types/modelmessagesource: en: 3b677f021d7dd4d3 zh: 2a875742b22879a2 @@ -1140,14 +1155,14 @@ en: 3e36e2f29ff1b506 zh: 3dcc1a1dced59d9f /session-persistence-event-catalog/resolved-persistence-types/presentedfile~2: - en: 45eec434ea27986c - zh: c8d52b34de4df04f + en: 807fde1cf42ddef3 + zh: 2ea131667b990d1e /session-persistence-event-catalog/resolved-persistence-types/ptcdispatcheventdata: - en: 78e72b973fe1c580 - zh: 9947fbdb2ad284d8 + en: 81e66c5a07e17dd6 + zh: 0ecf3cfceba14d2b /session-persistence-event-catalog/resolved-persistence-types/ptcdispatchstarteventdata: - en: 3e120cbeff3f99ac - zh: 19cf00f4b5ef2121 + en: fbbf650635960ec8 + zh: 9457953f4cbe07c6 /session-persistence-event-catalog/resolved-persistence-types/reasoningblock: en: 07e04c3e336f880f zh: 6f8986ede9994fa1 @@ -1179,11 +1194,11 @@ en: 9a54c4478ad3b620 zh: 02c4c1fd7e252770 /session-persistence-event-catalog/resolved-persistence-types/sessiontitleeventdata: - en: 2c684a3db9219c45 - zh: 202000f582ca1ec8 + en: 4ff17eae0b30056d + zh: 6642afbaa87da3ab /session-persistence-event-catalog/resolved-persistence-types/sessiontitlellmrequesteventdata: - en: 7b180deead48738e - zh: 35d43b7ef489f97b + en: 390c1daad2342ce8 + zh: 07ce68854dce8ea4 /session-persistence-event-catalog/resolved-persistence-types/sessiontitlemodelidentity: en: 9531468264499498 zh: 9da23c0ad2776563 @@ -1194,23 +1209,23 @@ en: 2642015fb4518c8c zh: 152b294f41fbdcca /session-persistence-event-catalog/resolved-persistence-types/skillcatalogsource: - en: 3e76d9f3d5bf82a0 - zh: fd037f5c12ee6330 + en: de04d42a8b0004df + zh: db64863dd7da1e26 /session-persistence-event-catalog/resolved-persistence-types/skillinvocationsource: en: ecc15ebadb77afdf zh: 97ac27d6a4175f6f /session-persistence-event-catalog/resolved-persistence-types/streamchunk: - en: e865a9a3b7749c1a - zh: 16800bf55135c89e + en: 0d571899222eb04f + zh: de28c6d67d1763c1 /session-persistence-event-catalog/resolved-persistence-types/subagentcatalogevent: - en: e9c8f82e0497e5a5 - zh: 55ab13b1a5067759 + en: 725539dcd5c335ed + zh: 5ca08d3667f84ff9 /session-persistence-event-catalog/resolved-persistence-types/subagentdescriptordata: en: c56821e57987ae9e zh: 0364f59a472d51da /session-persistence-event-catalog/resolved-persistence-types/subagentsettledmessagesource: - en: a5e19707264c7449 - zh: 4c619e931997d3f0 + en: d34acab659e74957 + zh: 25cabc6c9fb44a7e /session-persistence-event-catalog/resolved-persistence-types/surfaceop: en: 4f3349f21317ba18 zh: 508c26670085953a @@ -1263,8 +1278,8 @@ en: f43a635a14568780 zh: 8d22226b045122eb /session-persistence-event-catalog/resolved-persistence-types/toolrestriction: - en: b6f6001a25bcbf82 - zh: bd2b3c9fe0ad2ba8 + en: d7d3c0238d9b373e + zh: f49d93ee54e7a780 /session-persistence-event-catalog/resolved-persistence-types/toolresultmessage: en: 2258683c3cfa4ce3 zh: 5663aa76c911761b @@ -1290,14 +1305,14 @@ en: 544292eddeff5faa zh: 4e36e575a5030fb2 /session-persistence-event-catalog/resolved-persistence-types/turnendreason: - en: 7fd4741c641f9252 - zh: be115df1a8ee59ce + en: cdf0bbf6399a3677 + zh: f4422bf67b216bae /session-persistence-event-catalog/resolved-persistence-types/usermessage: - en: c9090b73ac4ce05b - zh: beb1f58404b0a2b8 + en: b1c19f6d8f6f31fc + zh: 3843640f47897934 /session-persistence-event-catalog/resolved-persistence-types/usermessage~2: - en: a3974d05a5d357d3 - zh: 99e73822f538c99d + en: 2ee253b3743ee6a2 + zh: 5ee8b08850d93d8e /session-persistence-event-catalog/resolved-persistence-types/workflowagentoutcome: en: d0a3f9aab18c3de5 zh: 76ac69a835ca4e29 @@ -1320,11 +1335,11 @@ en: 5e7d82a493a1d4d8 zh: d2d785891dd766fc /session-persistence-event-catalog/resolved-persistence-types/number~2: - en: 79970e4b1cfc8bde - zh: f2d6387547ce499b + en: a80b187744cc8374 + zh: c115c644aac2c802 /session-persistence-event-catalog/resolved-persistence-types/string: - en: e69452dc62a96e8a - zh: d7de5aaf1c00e98e + en: c2d10e67ca221c64 + zh: b7f58ee3db797d07 /session-persistence-event-catalog/resolved-persistence-types/string~2: en: 0089620bf4638263 zh: 1922bef1713094bb @@ -1374,8 +1389,8 @@ en: a74e3dfa51c987af zh: 6c3c87974d7a7260 /session-persistence-event-catalog/resolved-persistence-types/active~2: - en: c861888d6239b700 - zh: a9b2271d3c6b8737 + en: a3de6a6fe9672c7a + zh: a357481e27b3b81f /session-persistence-event-catalog/resolved-persistence-types/agentpreset: en: b8a0f8aac9708056 zh: f5f004bd205b41e1 @@ -1413,11 +1428,14 @@ en: dc8738014c719f4e zh: c773fd60c0090231 /session-persistence-event-catalog/resolved-persistence-types/childcreatedat-childid-label-mode~5: - en: 296001b5c7359a11 - zh: b5fdd40b7068497e + en: 43cf5cd666b591a0 + zh: 7c7a3011f8681f12 +/session-persistence-event-catalog/resolved-persistence-types/childcreatedat-childid-label-mode~6: + en: a1f188c9058cf621 + zh: eb43c0f045fb9afb /session-persistence-event-catalog/resolved-persistence-types/code-name-reason: - en: 9615c513ac256ad8 - zh: a492b49c787d4549 + en: 5b7790906008fcba + zh: 3d1a6bdb82d82fde /session-persistence-event-catalog/resolved-persistence-types/commandid-kind-sourceeventseq-text: en: 87b03e1c73d2a326 zh: d7b9f06863cad891 @@ -1431,11 +1449,14 @@ en: 45f1d815198508e1 zh: 5c2818b0e38e1356 /session-persistence-event-catalog/resolved-persistence-types/compactionid-sourcecommandid-turn: - en: 4ba70efccc268763 - zh: 06ba9b3e824c80a7 + en: bef51e0442ebb903 + zh: 240ebeb160533d8c +/session-persistence-event-catalog/resolved-persistence-types/cwd: + en: 1bc55be85a68f0a3 + zh: 9e84a55ed11bba5d /session-persistence-event-catalog/resolved-persistence-types/decision-durationms-exitcode-handlerid: - en: ed8ca43bb7622393 - zh: 697e1ccbe9439aa9 + en: a99ac57bbec0370f + zh: 9073329e324a66e0 /session-persistence-event-catalog/resolved-persistence-types/delayms-failure-maxretries-mode: en: 27987dc154def5d8 zh: 7dd493f04daa654f @@ -1443,14 +1464,14 @@ en: 10a2d594b106f6fe zh: 8408379e744ad987 /session-persistence-event-catalog/resolved-persistence-types/description-name: - en: 024bacc58243ccc7 - zh: 0672c4360841916f + en: e501ab0e92f12d77 + zh: 087fb91a04f85398 /session-persistence-event-catalog/resolved-persistence-types/description-name~2: en: a07969828eed2e6f zh: e06893743af544b5 /session-persistence-event-catalog/resolved-persistence-types/dialect-handlerid-matcher-point: - en: afb33a900f77ddf6 - zh: 90edc32643550430 + en: eeba88520aacd126 + zh: 8e2ca4e2bd2c1e3a /session-persistence-event-catalog/resolved-persistence-types/end-start: en: 4b7a8793663d3fef zh: 249459c825d330c3 @@ -1470,11 +1491,11 @@ en: 51af619ed8532a43 zh: a5ad878c63d0e046 /session-persistence-event-catalog/resolved-persistence-types/header-reason-startsseries: - en: 2b549aa36fe3ab7b - zh: 069ef4498112c4e7 + en: 562fcce1a6c17ade + zh: 15828c9bf4dcbe6e /session-persistence-event-catalog/resolved-persistence-types/headerseq-message-step-turn: - en: 97dc4446fd0b02ac - zh: 57ec4b27d0221957 + en: 884dda8acdf28a83 + zh: 53ef06336629743c /session-persistence-event-catalog/resolved-persistence-types/height-width: en: a5df50710083ac25 zh: aad3ccf6f6de70e4 @@ -1491,11 +1512,11 @@ en: 41f73c58a77dfa28 zh: af77f4aee3cb4a40 /session-persistence-event-catalog/resolved-persistence-types/inherited: - en: 7dcf0c1ade316952 - zh: 3c9cd7564659ec3d + en: f46de4c8d413b567 + zh: af0283688492ac43 /session-persistence-event-catalog/resolved-persistence-types/inserted-outcome-removedcount-start: - en: f2167c4cf3aa9b4d - zh: b4ed04a9a526f49e + en: 62e1cb96abf41fd5 + zh: c24c03766f6790d8 /session-persistence-event-catalog/resolved-persistence-types/interrupted-message-step-stream: en: f460572284c92755 zh: bae7561ce887b040 @@ -1800,8 +1821,8 @@ en: 08be4349548406a9 zh: 8e7cf9966198ef78 /session-persistence-event-catalog/resolved-persistence-types/kind-tool-registry: - en: 77a2e15c8413b18c - zh: 91d39eed9dfd96d1 + en: 79db83dd2c430faf + zh: 173f568241f5ce86 /session-persistence-event-catalog/resolved-persistence-types/kind-user: en: aacb77f1cead58ba zh: e6ce9dac4bdd07b7 @@ -1830,8 +1851,11 @@ en: 77a8fc6a2f3a9319 zh: 4dc1de1358b51a8d /session-persistence-event-catalog/resolved-persistence-types/kind-webhook-form-notice: - en: 4cd5dc7523fefebb - zh: f467561fbd0ae394 + en: 0b74295f7c5c8f11 + zh: 5f1213cec87f6be0 +/session-persistence-event-catalog/resolved-persistence-types/kind-working-directory: + en: 9848d783b7b5af16 + zh: f161eaf1a841cd55 /session-persistence-event-catalog/resolved-persistence-types/max-tokens-messages-model-tools: en: afedf4a31a67eb14 zh: 98d3bc882f2b61c3 @@ -1887,11 +1911,11 @@ en: a79b2ab16b9ed215 zh: 764cd1400ee8db63 /session-persistence-event-catalog/resolved-persistence-types/type-agent-preset-selected: - en: c8b6e46c8e161688 - zh: 3f45f3e70ce9f600 + en: 52112a8633ba49e4 + zh: 95318f8c8b2d19ee /session-persistence-event-catalog/resolved-persistence-types/type-agent-inbox-spliced: - en: d73cb120a4aa138e - zh: 1f5ce9f1bb430aec + en: 35219c983f8168c9 + zh: 5bdf2bc555cc5e1a /session-persistence-event-catalog/resolved-persistence-types/type-approval-asked: en: 9c77f18b0dad22f0 zh: 8894cf2878fe90db @@ -1935,11 +1959,11 @@ en: ba73fb10b7e2ffc9 zh: 8d5c97e63e5f16da /session-persistence-event-catalog/resolved-persistence-types/type-deliverables-presented: - en: ec7e9f13864db847 - zh: 694e948cd52387ea + en: d69ad6b38df667d3 + zh: 72fb317a507a0b3b /session-persistence-event-catalog/resolved-persistence-types/type-developer-message: - en: 73514ab7122858e8 - zh: 335ebf8ba5454d7e + en: 1d16681802c679ce + zh: 01c03831212dd260 /session-persistence-event-catalog/resolved-persistence-types/type-feedback-message-delete: en: 204561e2311db97e zh: 77264396bd5e25c4 @@ -2010,20 +2034,20 @@ en: acf7e21e4d21ebff zh: fb83a853634d204a /session-persistence-event-catalog/resolved-persistence-types/type-session-title: - en: 8dd08982ac65646b - zh: 3f22850e3160ed36 + en: 203f5661ca1850b1 + zh: b3d9ec1019188df4 /session-persistence-event-catalog/resolved-persistence-types/type-session-title-llm-request: - en: 48c9d78076942c91 - zh: 6cafb7732d3a1258 + en: 246920f06c3a2175 + zh: 56deee3b86499174 /session-persistence-event-catalog/resolved-persistence-types/type-step-end: en: 7fc233c4a569dae2 zh: bc2b3fed4a397c00 /session-persistence-event-catalog/resolved-persistence-types/type-step-start: - en: 55ec9a3f52280221 - zh: bf2afb135514eb45 + en: bb27281e17bb5e4f + zh: b53e3fc8d916118b /session-persistence-event-catalog/resolved-persistence-types/type-subagent-catalog: - en: 85475a5c4198d213 - zh: 16a71daeb92288db + en: 0f697ddcde1625dd + zh: 0dd87d2e108c5312 /session-persistence-event-catalog/resolved-persistence-types/type-subagent-descriptor: en: 2adab4361a5b558d zh: 7f2a8da669f9e117 @@ -2073,11 +2097,11 @@ en: 939c100f8a590003 zh: b372827fffc17b1c /session-persistence-event-catalog/resolved-persistence-types/type-tool-call: - en: 2dc168f9f2bd25d6 - zh: d75bd24319468403 + en: fa99a04f82d0f7d0 + zh: 0f900151f73276e7 /session-persistence-event-catalog/resolved-persistence-types/type-tool-ptc-dispatch: - en: 4c950143ed9318b0 - zh: 2a0cfcaf190a01a9 + en: e598ac0ecca97709 + zh: ab07e2bf9235d135 /session-persistence-event-catalog/resolved-persistence-types/type-tool-ptc-dispatch-start: en: 11e3a2031948dd55 zh: a3666d1a2f08bb8c @@ -2091,17 +2115,20 @@ en: 1b045de96172be4b zh: 8a15c6f35b576b37 /session-persistence-event-catalog/resolved-persistence-types/type-usage: - en: 5e70d314feb1b86b - zh: b98f69d4d8442c14 + en: fa542ccb91698e56 + zh: 7b725c813d69588d /session-persistence-event-catalog/resolved-persistence-types/type-user-message: - en: 4f793420e83a748e - zh: 70a4cb82e27965dd + en: fb49c375aec5952c + zh: 19b80920c1068b86 /session-persistence-event-catalog/resolved-persistence-types/type-web-deepseek-search-llm-request: en: f109997e295af37d zh: b2dcfd599f73b61c /session-persistence-event-catalog/resolved-persistence-types/type-web-search-20250305: - en: a5ffa4a988ead0df - zh: fb8aa9a4fb0a264b + en: e0071555851c7cee + zh: 1f368baf78a7d689 +/session-persistence-event-catalog/resolved-persistence-types/type-working-directory-change: + en: 2fcb09b83898ee18 + zh: 44c7f987f0bf3588 /session-persistence-event-catalog/resolved-persistence-types/type-workspace-changes: en: 411ecd58c4405c8a zh: e53709d457a1f118 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index bb5db150fa..7634129b63 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -3,7 +3,9 @@ # Session Persistence Event Catalog -Every repository-declared durable Session event appears here with its source declaration and resolved types. The catalog covers the logical and physical headers, event envelopes, and every plugin declaration merge. See [Session](subsystems/session.md) for replay and [persistence](subsystems/persistence.md) for storage. +Every repository-declared durable Session event appears here with its source declaration and resolved types. The catalog covers the logical and physical headers, event envelopes, and every SessionEventMap declaration merge. See [Session](subsystems/session.md) for replay and [persistence](subsystems/persistence.md) for storage. + +Experimental records appear in the separate [experimental plugin record catalog](experimental-persistence-catalog.md). Run `pnpm run gen-persistence-catalog` to regenerate both catalog languages, their pairing record, the known-event module, and the machine schema inventory. `pnpm run verify-persistence-catalog` checks all generated files. Declaration fences preserve source JSDoc and type references; resolved definitions expose their transitive structure. @@ -21,7 +23,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178) | +| `event:agent/inbox/spliced` | event | `9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -34,7 +36,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467` | [`{ type: "developer/message" }`](#persistence-type-sha256-186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467) | +| `event:developer/message` | event | `ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06` | [`{ type: "developer/message" }`](#persistence-type-sha256-ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -56,10 +58,10 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46) | +| `event:session/title-llm-request` | event | `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | -| `event:subagent/catalog` | event | `3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604) | +| `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | | `event:subagent/descriptor` | event | `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` | [`{ type: "subagent/descriptor" }`](#persistence-type-sha256-b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5) | | `event:subagent/model-selection-policy` | event | `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` | [`{ type: "subagent/model-selection-policy" }`](#persistence-type-sha256-a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc) | | `event:system/message` | event | `69081694be231d56fd9580ba14645fd5e35373202605d5c5c841a9435b5fa3b1` | [`{ type: "system/message" }`](#persistence-type-sha256-69081694be231d56fd9580ba14645fd5e35373202605d5c5c841a9435b5fa3b1) | @@ -73,13 +75,14 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`{ type: "tool-workflow/run-end" }`](#persistence-type-sha256-42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f) | | `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058) | | `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`{ type: "tool/call" }`](#persistence-type-sha256-3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48) | -| `event:tool/ptc-dispatch` | event | `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163) | +| `event:tool/ptc-dispatch` | event | `d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5) | | `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`{ type: "tool/ptc-dispatch-start" }`](#persistence-type-sha256-ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93) | | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411` | [`{ type: "user/message" }`](#persistence-type-sha256-b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411) | +| `event:user/message` | event | `2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade` | [`{ type: "user/message" }`](#persistence-type-sha256-2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade) | | `event:web/deepseek-search-llm-request` | event | `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331) | +| `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | ## Event envelope @@ -582,7 +585,7 @@ Source: [`packages/goal/goal/src/domain.ts:66`](../packages/goal/goal/src/domain } ``` -Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) +Source: [`packages/experimental/hook-protocol/src/types.ts:19`](../packages/experimental/hook-protocol/src/types.ts) @@ -605,7 +608,7 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook- } ``` -Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) +Source: [`packages/experimental/hook-protocol/src/types.ts:31`](../packages/experimental/hook-protocol/src/types.ts) ### `image/*` @@ -723,7 +726,7 @@ Source: [`packages/interaction/permission-presets/src/index.ts:59`](../packages/ 'plan/mode': { active: boolean } ``` -Source: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) +Source: [`packages/plan/plan-mode/src/index.ts:52`](../packages/plan/plan-mode/src/index.ts) ### `request/*` @@ -824,8 +827,8 @@ Source: [`packages/schedule/schedule/src/types.ts:365`](../packages/schedule/sch * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -866,7 +869,7 @@ Source: [`packages/session/session-title/src/index.ts:77`](../packages/session/s Types: [SessionTitleLlmRequestEventData](subsystems/session-title.md) -Source: [`packages/session/session-title-llm/src/index.ts:52`](../packages/session/session-title-llm/src/index.ts) +Source: [`packages/session/session-title-llm/src/index.ts:54`](../packages/session/session-title-llm/src/index.ts) ### `session-log-deepseek/*` @@ -926,7 +929,7 @@ Source: [`packages/core/session/src/types.ts:299`](../packages/core/session/src/ 'subagent/catalog': SubagentCatalogEvent ``` -Source: [`packages/subagent/subagent/src/catalog.ts:43`](../packages/subagent/subagent/src/catalog.ts) +Source: [`packages/subagent/subagent/src/catalog.ts:44`](../packages/subagent/subagent/src/catalog.ts) @@ -935,8 +938,8 @@ Source: [`packages/subagent/subagent/src/catalog.ts:43`](../packages/subagent/su ```ts persistence-catalog /** * Durable identity and lifecycle mode of a session-backed subagent child, - * appended once by the establishing provider inside the child's initial - * turn, before its first request. Continuable records also carry their + * appended once inside the child's initial turn, before its first + * request. Continuable records also carry their * resumable composition. Log-only: it carries no `surfaceOp`, never enters * model history, and survives compaction. */ @@ -1091,8 +1094,8 @@ Source: [`packages/core/session/src/types.ts:361`](../packages/core/session/src/ * `tool/ptc-dispatch-start` with the same `subCallId`), the tool `name` * with the same JSON-normalized `arguments`, and the sub-call's complete * durable outcome in `tool/result`'s own vocabulary (`content` + `isError` - * + optional structured `error`), so UIs and SDKs render a sub-call through - * the exact path used for a native call. Every started sub-call settles + * + optional presentation `meta` and structured `error`), so UIs and SDKs + * render a sub-call through the exact path used for a native call. Every started sub-call settles * with exactly one of these (abort included: the aborted pipeline result * is an `isError` outcome). * Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter @@ -1104,7 +1107,7 @@ Source: [`packages/core/session/src/types.ts:361`](../packages/core/session/src/ 'tool/ptc-dispatch': PtcDispatchEventData ``` -Source: [`packages/core/tools/src/types.ts:58`](../packages/core/tools/src/types.ts) +Source: [`packages/core/tools/src/types.ts:61`](../packages/core/tools/src/types.ts) @@ -1127,7 +1130,7 @@ Source: [`packages/core/tools/src/types.ts:58`](../packages/core/tools/src/types 'tool/ptc-dispatch-start': PtcDispatchStartEventData ``` -Source: [`packages/core/tools/src/types.ts:42`](../packages/core/tools/src/types.ts) +Source: [`packages/core/tools/src/types.ts:45`](../packages/core/tools/src/types.ts) @@ -1290,6 +1293,22 @@ Source: [`packages/core/session/src/types.ts:309`](../packages/core/session/src/ Source: [`packages/web/web-search-deepseek/src/provider.ts:82`](../packages/web/web-search-deepseek/src/provider.ts) +### `working-directory/*` + + + +#### `working-directory/change` — log-only + +```ts persistence-catalog +/** Effective working directory; the immutable Session header retains the original project. */ +'working-directory/change': { + /** Absolute directory used by subsequent directory-based operations. */ + cwd: string +} +``` + +Source: [`packages/session/working-directory/src/index.ts:36`](../packages/session/working-directory/src/index.ts) + ### `workspace/*` @@ -1759,6 +1778,14 @@ SHA-256: `e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87` `"every"` + + +### `"external"` + +SHA-256: `fc351843b8ecbd05d739c4ca4fcd4bc7cfad6c0beacbbd48389959bb1bf06cf3` + +`"external"` + ### `"failed"` @@ -2949,6 +2976,22 @@ SHA-256: `db6c5246911d47a7969f6b4cb47c7d0dbbde75c402710b22ace3672fa97bebe0` `"webhook"` + + +### `"working-directory"` + +SHA-256: `7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a` + +`"working-directory"` + + + +### `"working-directory/change"` + +SHA-256: `d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5` + +`"working-directory/change"` + ### `"workspace-write"` @@ -3007,7 +3050,7 @@ SHA-256: `af6e173f7be819bcd0a5c8d77918649c10ce761d8a184ff472f2e01ce64eb2e0` SHA-256: `83da72c6857601c75da11212ef847f57d457b9c99f0a82246dcdff3e26700239` -Sources: [`packages/context/agent-instructions/src/render.ts:47`](../packages/context/agent-instructions/src/render.ts) +Sources: [`packages/context/agent-instructions/src/render.ts:53`](../packages/context/agent-instructions/src/render.ts) | Property | Presence | Type | |---|---|---| @@ -3034,7 +3077,7 @@ Array of [`AgentInstructionChange`](#persistence-type-sha256-83da72c6857601c75da SHA-256: `6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590` -Sources: [`packages/context/agent-instructions/src/state.ts:37`](../packages/context/agent-instructions/src/state.ts) +Sources: [`packages/context/agent-instructions/src/state.ts:40`](../packages/context/agent-instructions/src/state.ts) | Property | Presence | Type | |---|---|---| @@ -3054,7 +3097,7 @@ Sources: [`packages/context/agent-instructions/src/state.ts:37`](../packages/con SHA-256: `4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86` -Sources: [`packages/subagent/subagent/src/continuation-messages.ts:15`](../packages/subagent/subagent/src/continuation-messages.ts) +Sources: [`packages/subagent/subagent/src/continuation-messages.ts:14`](../packages/subagent/subagent/src/continuation-messages.ts) | Property | Presence | Type | |---|---|---| @@ -3297,7 +3340,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web | `body` | required | [`{ max_tokens, messages, model, tools }`](#persistence-type-sha256-930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731) | | `endpoint` | required | `string` | - + @@ -3305,7 +3348,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:55`](../packages/web ### `DeveloperMessage` -SHA-256: `1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200` +SHA-256: `af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08` Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3314,11 +3357,11 @@ Sources: [`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"developer"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154) | Source compatibility: `source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. -Attribution-only additions: `tmux-context`, `user-question-reply`. +Attribution-only additions: `tmux-context`, `user-question-reply`, `working-directory`. @@ -3613,13 +3656,13 @@ Sources: [`packages/session/session-persistence-jsonl/src/format.ts:83`](../pack - + ### `HookDialect` SHA-256: `a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb` -Sources: [`packages/hooks/hook-protocol/src/types.ts:48`](../packages/hooks/hook-protocol/src/types.ts) +Sources: [`packages/experimental/hook-protocol/src/types.ts:48`](../packages/experimental/hook-protocol/src/types.ts) One of: @@ -3930,7 +3973,7 @@ Sources: [`packages/schedule/schedule/src/types.ts:185`](../packages/schedule/sc SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0` -Sources: [`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call-config.ts) +Sources: [`packages/llm/llm/src/call-config.ts:31`](../packages/llm/llm/src/call-config.ts) | Property | Presence | Type | |---|---|---| @@ -3951,7 +3994,7 @@ Sources: [`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693` -Sources: [`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts) +Sources: [`packages/llm/llm/src/call-config.ts:54`](../packages/llm/llm/src/call-config.ts) | Property | Presence | Type | |---|---|---| @@ -4015,7 +4058,7 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr | `step` | required | `number` | | `turn` | required | `number` | - + @@ -4023,15 +4066,15 @@ Sources: [`packages/llm/llm-retry/src/types.ts:43`](../packages/llm/llm-retry/sr ### `Message` -SHA-256: `cb7b863ddb7b3eab5f18f216602feba25f47b456e73e4e72b6bcc259875c6dab` +SHA-256: `d901c4f5e856591c3217af983caa15bba568d580700f0958bab56b88004eced9` Sources: [`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) One of: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200) -- [`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd) +- [`DeveloperMessage`](#persistence-type-sha256-af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08) +- [`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4108,7 +4151,7 @@ One of: - `"negative"` - `"positive"` - + @@ -4116,7 +4159,7 @@ One of: ### `MessageSource` -SHA-256: `26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116` +SHA-256: `1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154` Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4232,14 +4275,15 @@ Sources: [`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message | `user-approval` | not declared | none | [`{ kind: "user-approval" }`](#persistence-type-sha256-6292bcbedaa97feaec5b48a03bb8144b581b93309d9d6c905f26da7a6a7a0a4f) | | `user-question-reply` | not declared | `callId`: `string`; `outcome`: `"answered"` | [`{ kind: "user-question-reply" }`](#persistence-type-sha256-c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005) | | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | +| `working-directory` | not declared | none | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `3f42f45cbb8a06586d247e5f3763dc02c8d31a145a9a3b82fabcd101e7e224cc` +SHA-256: `4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c` -Array of [`Message`](#persistence-type-sha256-cb7b863ddb7b3eab5f18f216602feba25f47b456e73e4e72b6bcc259875c6dab). +Array of [`Message`](#persistence-type-sha256-d901c4f5e856591c3217af983caa15bba568d580700f0958bab56b88004eced9). @@ -4357,7 +4401,7 @@ SHA-256: `1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345` Array of [`PresentedFile`](#persistence-type-sha256-b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97). - + @@ -4365,9 +4409,9 @@ Array of [`PresentedFile`](#persistence-type-sha256-b8fc636a2121df9d8423c242e03c ### `PtcDispatchEventData` -SHA-256: `0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8` +SHA-256: `cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6` -Sources: [`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/types.ts) +Sources: [`packages/core/tools/src/types.ts:21`](../packages/core/tools/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4375,6 +4419,7 @@ Sources: [`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/type | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `error` | optional | [`{ code, name, reason? }`](#persistence-type-sha256-a81059ca06999da1e712414520c2c65e491d052fcf9640fae20aa38f0a5dc735) | | `isError` | required | `boolean` | +| `meta` | optional | [`JsonValue`](#persistence-type-sha256-c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6) | | `name` | required | `string` | | `parentCallId` | required | `string` | | `rootCallId` | required | `string` | @@ -4390,7 +4435,7 @@ Sources: [`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/type SHA-256: `2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f` -Sources: [`packages/core/tools/src/types.ts:11`](../packages/core/tools/src/types.ts) +Sources: [`packages/core/tools/src/types.ts:12`](../packages/core/tools/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4606,7 +4651,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ | `source` | required | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | required | `string` | - + @@ -4614,7 +4659,7 @@ Sources: [`packages/session/session-title/src/types.ts:41`](../packages/session/ ### `SessionTitleLlmRequestEventData` -SHA-256: `fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996` +SHA-256: `045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19` Sources: [`packages/session/session-title-llm/src/index.ts:34`](../packages/session/session-title-llm/src/index.ts) @@ -4622,7 +4667,8 @@ Sources: [`packages/session/session-title-llm/src/index.ts:34`](../packages/sess |---|---|---| | `maxTokens` | required | `number` | | `messageSeqs` | required | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | required | [`Message[]`](#persistence-type-sha256-3f42f45cbb8a06586d247e5f3763dc02c8d31a145a9a3b82fabcd101e7e224cc) | +| `messages` | required | [`Message[]`](#persistence-type-sha256-4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c) | +| `reasoningEffort` | optional | `string` | | `route` | required | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | required | `string` | | `titleProvider` | required | `string` | @@ -4684,7 +4730,7 @@ Sources: [`packages/session/session-title/src/types.ts:28`](../packages/session/ SHA-256: `fc2ae6962f9f8801c2b1e414668e1e17cbb7544c4f39381198977a38bd10c093` -Sources: [`packages/skill/tool-skill/src/index.ts:34`](../packages/skill/tool-skill/src/index.ts) +Sources: [`packages/skill/tool-skill/src/index.ts:35`](../packages/skill/tool-skill/src/index.ts) | Property | Presence | Type | |---|---|---| @@ -4733,7 +4779,7 @@ One of: - [`{ type: "finish" }`](#persistence-type-sha256-a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1) - [`{ type: "usage" }`](#persistence-type-sha256-f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515) - + @@ -4741,17 +4787,18 @@ One of: ### `SubagentCatalogEvent` -SHA-256: `1de64ea9a0849ce631b7e4e1255ae773fabd94a4a7c8d27e716f8012302e12c0` +SHA-256: `f731ffe80810b0289b3e694d8f40af6f01865c02a622ee457be90ca808b66e87` Sources: [`packages/subagent/subagent/src/catalog.ts:28`](../packages/subagent/subagent/src/catalog.ts) One of: -- [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd) - [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-e2ad8f5bcd813f268ffef1b10a12d7ddd2b8ced8b9b6d0275b3f6b7ed094a187) +- [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-b6896938660a1dfe4ddd100fb3164d9f8fa57feeabfe37441a80106c9942697a) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-a0f25b5aeeebf2bf1542ce67726b095e3b139a39c71861d57e827f12dea1f499) +- [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd) @@ -4780,7 +4827,7 @@ One of: SHA-256: `4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a` -Sources: [`packages/subagent/subagent/src/continuation-messages.ts:30`](../packages/subagent/subagent/src/continuation-messages.ts) +Sources: [`packages/subagent/subagent/src/continuation-messages.ts:29`](../packages/subagent/subagent/src/continuation-messages.ts) | Property | Presence | Type | |---|---|---| @@ -5107,7 +5154,7 @@ Sources: [`packages/llm/llm/src/types.ts:127`](../packages/llm/llm/src/types.ts) SHA-256: `3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f` -Sources: [`packages/core/tools/src/index.ts:700`](../packages/core/tools/src/index.ts) +Sources: [`packages/core/tools/src/index.ts:701`](../packages/core/tools/src/index.ts) | Property | Presence | Type | |---|---|---| @@ -5276,7 +5323,7 @@ Sources: [`packages/core/session/src/types.ts:232`](../packages/core/session/src | `interrupted` | not declared | none | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | not declared | none | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5284,7 +5331,7 @@ Sources: [`packages/core/session/src/types.ts:232`](../packages/core/session/src ### `UserMessage` -SHA-256: `cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd` +SHA-256: `89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97` Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5293,19 +5340,19 @@ Sources: [`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message | `content` | required | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | required | `string` | | `role` | required | `"user"` | -| `source` | required | [`MessageSource`](#persistence-type-sha256-26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116) | +| `source` | required | [`MessageSource`](#persistence-type-sha256-1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154) | Source compatibility: `source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. -Attribution-only additions: `tmux-context`, `user-question-reply`. +Attribution-only additions: `tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `5a92f12db2059c282cdea45b6845c65e6f4a95dd3d649aeddbde6f9ae03c561f` +SHA-256: `2fef90def36aa758ca096c5f6ad0e00dbb2101943e19ca54b05618801974894a` -Array of [`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd). +Array of [`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97). @@ -5431,6 +5478,12 @@ Array of `number`. + + + + + + @@ -5459,12 +5512,6 @@ Array of `number`. - - - - - - @@ -5501,7 +5548,7 @@ Array of `number`. SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/webhook/webhook/src/brand.ts:12`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:6`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:9`](../packages/webhook/webhook/src/brand.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -5687,7 +5734,7 @@ Array of `unknown` (opaque). SHA-256: `20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5` -Sources: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) +Sources: [`packages/plan/plan-mode/src/index.ts:52`](../packages/plan/plan-mode/src/index.ts) | Property | Presence | Type | |---|---|---| @@ -5877,13 +5924,27 @@ SHA-256: `b6896938660a1dfe4ddd100fb3164d9f8fa57feeabfe37441a80106c9942697a` | `mode` | required | `"one-shot"` | | `version` | required | `1` | + + +### `{ childCreatedAt, childId, label?, mode, … }` + +SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` + +| Property | Presence | Type | +|---|---|---| +| `childCreatedAt` | required | `number` | +| `childId` | required | `string` | +| `label` | optional | `string` | +| `mode` | required | `"external"` | +| `version` | required | `2` | + ### `{ code, name, reason? }` SHA-256: `a81059ca06999da1e712414520c2c65e491d052fcf9640fae20aa38f0a5dc735` -Sources: [`packages/core/session/src/types.ts:383`](../packages/core/session/src/types.ts) · [`packages/core/tools/src/types.ts:24`](../packages/core/tools/src/types.ts) +Sources: [`packages/core/session/src/types.ts:383`](../packages/core/session/src/types.ts) · [`packages/core/tools/src/types.ts:27`](../packages/core/tools/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -5976,13 +6037,25 @@ Sources: [`packages/compaction/compaction/src/types.ts:24`](../packages/compacti | `sourceCommandId` | optional | `string` | | `turn` | required | [`OptionalSessionSeq`](#persistence-type-sha256-3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f) | + + +### `{ cwd }` + +SHA-256: `da60ed58a8fa23d7aa4920ab5c1f4e3d28109cb0797ef9e5c8b125721b847582` + +Sources: [`packages/session/working-directory/src/index.ts:36`](../packages/session/working-directory/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `cwd` | required | `string` | + ### `{ decision, durationMs, exitCode?, handlerId, … }` SHA-256: `8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141` -Sources: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) +Sources: [`packages/experimental/hook-protocol/src/types.ts:31`](../packages/experimental/hook-protocol/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -6041,7 +6114,7 @@ Sources: [`packages/llm/llm-retry/src/types.ts:30`](../packages/llm/llm-retry/sr SHA-256: `83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693` -Sources: [`packages/skill/tool-skill/src/index.ts:40`](../packages/skill/tool-skill/src/index.ts) +Sources: [`packages/skill/tool-skill/src/index.ts:41`](../packages/skill/tool-skill/src/index.ts) | Property | Presence | Type | |---|---|---| @@ -6062,7 +6135,7 @@ Array of [`{ description, name }`](#persistence-type-sha256-83079c8a3f733ac3fa60 SHA-256: `119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25` -Sources: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) +Sources: [`packages/experimental/hook-protocol/src/types.ts:19`](../packages/experimental/hook-protocol/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -6168,18 +6241,18 @@ Sources: [`packages/core/session/src/types.ts:390`](../packages/core/session/src | `reason` | required | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | optional | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `a5d80b5a2f3a6bbaf8f0334126632b78f94d8346122c68e8122ea89906d63d26` +SHA-256: `5b55817a403569fbb2d379496df2e3f3f71107f6c577a218028ce187c60b61e6` Sources: [`packages/core/session/src/types.ts:311`](../packages/core/session/src/types.ts) | Property | Presence | Type | |---|---|---| | `headerSeq` | optional | `number` | -| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200) | +| `message` | required | [`DeveloperMessage`](#persistence-type-sha256-af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08) | | `step` | required | `number` | | `turn` | required | `number` | @@ -6263,17 +6336,17 @@ Sources: [`packages/core/session/src/types.ts:427`](../packages/core/session/src |---|---|---| | `inherited` | optional | `true` | - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `149c5a77529cb0dc06f5ceb6273069b7606174d1e7893b5f9b1071c15306b846` +SHA-256: `83a665476a9ee9af05cb1fdded12dd647d1b97b491200d7f12ef1af7cdac4428` Sources: [`packages/core/agent/src/types.ts:96`](../packages/core/agent/src/types.ts) | Property | Presence | Type | |---|---|---| -| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-5a92f12db2059c282cdea45b6845c65e6f4a95dd3d649aeddbde6f9ae03c561f) | +| `inserted` | required | [`UserMessage[]`](#persistence-type-sha256-2fef90def36aa758ca096c5f6ad0e00dbb2101943e19ca54b05618801974894a) | | `outcome` | optional | `"canceled"` | | `removedCount` | optional | `number` | | `start` | required | `number` | @@ -7434,7 +7507,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49` SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543` -Sources: [`packages/core/tools/src/index.ts:34`](../packages/core/tools/src/index.ts) +Sources: [`packages/core/tools/src/index.ts:35`](../packages/core/tools/src/index.ts) | Property | Presence | Type | |---|---|---| @@ -7552,7 +7625,7 @@ Sources: [`packages/interaction/user-questions/src/types.ts:117`](../packages/in SHA-256: `9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94` -Sources: [`packages/webhook/webhook/src/types.ts:74`](../packages/webhook/webhook/src/types.ts) +Sources: [`packages/experimental/webhook/src/types.ts:74`](../packages/experimental/webhook/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -7564,6 +7637,18 @@ Sources: [`packages/webhook/webhook/src/types.ts:74`](../packages/webhook/webhoo | `source` | required | `string` | | `summary` | required | `string` | + + +### `{ kind: "working-directory" }` + +SHA-256: `2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5` + +Sources: [`packages/session/working-directory/src/index.ts:29`](../packages/session/working-directory/src/index.ts) + +| Property | Presence | Type | +|---|---|---| +| `kind` | required | `"working-directory"` | + ### `{ max_tokens, messages, model, tools }` @@ -7821,17 +7906,17 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | required | `number` | | `type` | required | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178` +SHA-256: `9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-149c5a77529cb0dc06f5ceb6273069b7606174d1e7893b5f9b1071c15306b846) | +| `data` | required | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-83a665476a9ee9af05cb1fdded12dd647d1b97b491200d7f12ef1af7cdac4428) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -8072,17 +8157,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | required | `number` | | `type` | required | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467` +SHA-256: `ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06` | Property | Presence | Type | |---|---|---| -| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-a5d80b5a2f3a6bbaf8f0334126632b78f94d8346122c68e8122ea89906d63d26) | +| `data` | required | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-5b55817a403569fbb2d379496df2e3f3f71107f6c577a218028ce187c60b61e6) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8470,17 +8555,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | required | `number` | | `type` | required | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46` +SHA-256: `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | Property | Presence | Type | |---|---|---| -| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996) | +| `data` | required | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -8518,17 +8603,17 @@ SHA-256: `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | `time` | required | `number` | | `type` | required | `"step/start"` | - + ### `{ type: "subagent/catalog" }` -SHA-256: `3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604` +SHA-256: `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | Property | Presence | Type | |---|---|---| -| `data` | required | [`SubagentCatalogEvent`](#persistence-type-sha256-1de64ea9a0849ce631b7e4e1255ae773fabd94a4a7c8d27e716f8012302e12c0) | +| `data` | required | [`SubagentCatalogEvent`](#persistence-type-sha256-f731ffe80810b0289b3e694d8f40af6f01865c02a622ee457be90ca808b66e87) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -8808,17 +8893,17 @@ SHA-256: `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | `time` | required | `number` | | `type` | required | `"tool/call"` | - + ### `{ type: "tool/ptc-dispatch" }` -SHA-256: `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` +SHA-256: `d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5` | Property | Presence | Type | |---|---|---| -| `data` | required | [`PtcDispatchEventData`](#persistence-type-sha256-0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8) | +| `data` | required | [`PtcDispatchEventData`](#persistence-type-sha256-cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `time` | required | `number` | @@ -8903,17 +8988,17 @@ Sources: [`packages/llm/llm/src/types.ts:458`](../packages/llm/llm/src/types.ts) | `type` | required | `"usage"` | | `usage` | required | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411` +SHA-256: `2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade` | Property | Presence | Type | |---|---|---| -| `data` | required | [`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd) | +| `data` | required | [`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97) | | `ignorable` | optional | `true` | | `seq` | required | `number` | | `sourceEventSeqs` | optional | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8951,6 +9036,22 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:71`](../packages/web | `name` | required | `"web_search"` | | `type` | required | `"web_search_20250305"` | + + + + +### `{ type: "working-directory/change" }` + +SHA-256: `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`{ cwd }`](#persistence-type-sha256-da60ed58a8fa23d7aa4920ab5c1f4e3d28109cb0797ef9e5c8b125721b847582) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"working-directory/change"` | + diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index c431d9f5ec..9226767438 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -5,7 +5,9 @@ [English](persistence-catalog.md) | 中文 -本目录列出仓库声明的每个持久化 Session 事件及其源码声明和解析类型,覆盖逻辑与物理 header、事件信封以及各插件的声明合并。回放规则参见 [Session](subsystems/session.zh.md),存储规则参见[持久化](subsystems/persistence.zh.md)。 +本目录列出仓库声明的每个持久化 Session 事件及其源码声明和解析类型,覆盖逻辑与物理 header、事件信封及所有 SessionEventMap 声明合并。回放规则参见 [Session](subsystems/session.zh.md),存储规则参见[持久化](subsystems/persistence.zh.md)。 + +实验性记录单独列在[实验性插件记录目录](experimental-persistence-catalog.zh.md)中。 运行 `pnpm run gen-persistence-catalog` 可重新生成目录的两种语言、配对记录、已知事件模块和机器 schema 目录。`pnpm run verify-persistence-catalog` 检查所有生成文件。声明围栏保留源码 JSDoc 和类型引用;解析后的定义展开其传递引用结构。 @@ -23,7 +25,7 @@ | `JsonlHeaderLine` | header | `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` | [`HeaderLine`](#persistence-type-sha256-18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f) | | `SessionEventEnvelope` | envelope | `5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc` | [`union (3 variants)`](#persistence-type-sha256-5776e5553ff2dfe3f5bc202dbb1e7c9f93e35a531aebb7764c23b2b6153b2ccc) | | `event:agent-preset/selected` | event | `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | [`{ type: "agent-preset/selected" }`](#persistence-type-sha256-a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b) | -| `event:agent/inbox/spliced` | event | `6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178) | +| `event:agent/inbox/spliced` | event | `9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0` | [`{ type: "agent/inbox/spliced" }`](#persistence-type-sha256-9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0) | | `event:approval/asked` | event | `3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1` | [`{ type: "approval/asked" }`](#persistence-type-sha256-3bfeb47b58606f4661904bc723da612782214c463d01e6d61cd6d6193d7374e1) | | `event:approval/decided` | event | `bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3` | [`{ type: "approval/decided" }`](#persistence-type-sha256-bb1ab3d08f49a9f3b265f844cd78d5c49813062a7b34b54904b426f85d0ff6e3) | | `event:approval/policy` | event | `26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78` | [`{ type: "approval/policy" }`](#persistence-type-sha256-26718e15e7e395bce9642dba5bbe09b3b1a4ce2213d20d566cd9207d7fc5fb78) | @@ -36,7 +38,7 @@ | `event:compaction/start` | event | `db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965` | [`{ type: "compaction/start" }`](#persistence-type-sha256-db874d463b0fdec77e9da1c4568f37cb70bd6596781eb93800db44fb8a116965) | | `event:compaction/summary` | event | `e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602` | [`{ type: "compaction/summary" }`](#persistence-type-sha256-e2f9a41e0989f54ed8cee80f8db2bcf9d60a5c810dc9d45b83fa050b9dce7602) | | `event:deliverables/presented` | event | `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | [`{ type: "deliverables/presented" }`](#persistence-type-sha256-13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176) | -| `event:developer/message` | event | `186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467` | [`{ type: "developer/message" }`](#persistence-type-sha256-186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467) | +| `event:developer/message` | event | `ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06` | [`{ type: "developer/message" }`](#persistence-type-sha256-ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06) | | `event:feedback/message-delete` | event | `3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796` | [`{ type: "feedback/message-delete" }`](#persistence-type-sha256-3ee93b06f3a125850337602bcdf155d2538c43a5c944ec55b1b3c365152d6796) | | `event:feedback/message-put` | event | `b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf` | [`{ type: "feedback/message-put" }`](#persistence-type-sha256-b5086d249e8502e9ead1d39156bb8d559bde7951cac0f14ce150345b4e42a2bf) | | `event:feedback/record` | event | `b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8` | [`{ type: "feedback/record" }`](#persistence-type-sha256-b54940ff095c17e874c5be03815f4c2145a256cf3a1d34dae4ab2f7769dfffe8) | @@ -58,10 +60,10 @@ | `event:session-log-deepseek/delivery-accepted` | event | `d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9` | [`{ type: "session-log-deepseek/delivery-accepted" }`](#persistence-type-sha256-d63b8b8ffad9c02fd80c43a17df4f240c1fe8118ecca9de34f9d5871838ab5b9) | | `event:session/end-seed` | event | `5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e` | [`{ type: "session/end-seed" }`](#persistence-type-sha256-5e6db6e24948d4a853c71cb9fabd252ad051ce93d4672c1266cf837c1c17b84e) | | `event:session/title` | event | `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | [`{ type: "session/title" }`](#persistence-type-sha256-1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5) | -| `event:session/title-llm-request` | event | `ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46) | +| `event:session/title-llm-request` | event | `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | [`{ type: "session/title-llm-request" }`](#persistence-type-sha256-b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6) | | `event:step/end` | event | `e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05` | [`{ type: "step/end" }`](#persistence-type-sha256-e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05) | | `event:step/start` | event | `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | [`{ type: "step/start" }`](#persistence-type-sha256-4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986) | -| `event:subagent/catalog` | event | `3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604) | +| `event:subagent/catalog` | event | `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | [`{ type: "subagent/catalog" }`](#persistence-type-sha256-53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac) | | `event:subagent/descriptor` | event | `b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5` | [`{ type: "subagent/descriptor" }`](#persistence-type-sha256-b79ada42962cad0190a9d465805260567621fa3a4abd757eb31e6016b52d5ab5) | | `event:subagent/model-selection-policy` | event | `a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc` | [`{ type: "subagent/model-selection-policy" }`](#persistence-type-sha256-a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc) | | `event:system/message` | event | `69081694be231d56fd9580ba14645fd5e35373202605d5c5c841a9435b5fa3b1` | [`{ type: "system/message" }`](#persistence-type-sha256-69081694be231d56fd9580ba14645fd5e35373202605d5c5c841a9435b5fa3b1) | @@ -75,13 +77,14 @@ | `event:tool-workflow/run-end` | event | `42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f` | [`{ type: "tool-workflow/run-end" }`](#persistence-type-sha256-42e0916e0dda5f6d1e7bb05d8514717147c036a79c9f36085683469516c1fd3f) | | `event:tool-workflow/run-start` | event | `c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058` | [`{ type: "tool-workflow/run-start" }`](#persistence-type-sha256-c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058) | | `event:tool/call` | event | `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | [`{ type: "tool/call" }`](#persistence-type-sha256-3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48) | -| `event:tool/ptc-dispatch` | event | `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163) | +| `event:tool/ptc-dispatch` | event | `d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5` | [`{ type: "tool/ptc-dispatch" }`](#persistence-type-sha256-d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5) | | `event:tool/ptc-dispatch-start` | event | `ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93` | [`{ type: "tool/ptc-dispatch-start" }`](#persistence-type-sha256-ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93) | | `event:tool/result` | event | `7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397` | [`{ type: "tool/result" }`](#persistence-type-sha256-7c9f44e90a0058f4cc532ae20dad0c10afa6eba22e70a6c79fc79490bad64397) | | `event:turn/end` | event | `0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce` | [`{ type: "turn/end" }`](#persistence-type-sha256-0f8512903d94f57a4748fa1a2092e64342856796684e6b8343db685b192745ce) | | `event:turn/start` | event | `aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8` | [`{ type: "turn/start" }`](#persistence-type-sha256-aa0957eca50aeb28bcd2e6930b95809926edacb550c8c340ba526ba6b861b3d8) | -| `event:user/message` | event | `b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411` | [`{ type: "user/message" }`](#persistence-type-sha256-b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411) | +| `event:user/message` | event | `2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade` | [`{ type: "user/message" }`](#persistence-type-sha256-2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade) | | `event:web/deepseek-search-llm-request` | event | `cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331` | [`{ type: "web/deepseek-search-llm-request" }`](#persistence-type-sha256-cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331) | +| `event:working-directory/change` | event | `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` | [`{ type: "working-directory/change" }`](#persistence-type-sha256-124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3) | | `event:workspace/changes` | event | `e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72` | [`{ type: "workspace/changes" }`](#persistence-type-sha256-e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72) | ## 事件信封 @@ -584,7 +587,7 @@ export type SessionEvent = { } ``` -来源:[`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) +来源:[`packages/experimental/hook-protocol/src/types.ts:19`](../packages/experimental/hook-protocol/src/types.ts) @@ -607,7 +610,7 @@ export type SessionEvent = { } ``` -来源:[`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) +来源:[`packages/experimental/hook-protocol/src/types.ts:31`](../packages/experimental/hook-protocol/src/types.ts) ### `image/*` @@ -725,7 +728,7 @@ export type SessionEvent = { 'plan/mode': { active: boolean } ``` -来源:[`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) +来源:[`packages/plan/plan-mode/src/index.ts:52`](../packages/plan/plan-mode/src/index.ts) ### `request/*` @@ -826,8 +829,8 @@ export type SessionEvent = { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -868,7 +871,7 @@ export type SessionEvent = { 类型:[SessionTitleLlmRequestEventData](subsystems/session-title.zh.md) -来源:[`packages/session/session-title-llm/src/index.ts:52`](../packages/session/session-title-llm/src/index.ts) +来源:[`packages/session/session-title-llm/src/index.ts:54`](../packages/session/session-title-llm/src/index.ts) ### `session-log-deepseek/*` @@ -928,7 +931,7 @@ export type SessionEvent = { 'subagent/catalog': SubagentCatalogEvent ``` -来源:[`packages/subagent/subagent/src/catalog.ts:43`](../packages/subagent/subagent/src/catalog.ts) +来源:[`packages/subagent/subagent/src/catalog.ts:44`](../packages/subagent/subagent/src/catalog.ts) @@ -937,8 +940,8 @@ export type SessionEvent = { ```ts persistence-catalog /** * Durable identity and lifecycle mode of a session-backed subagent child, - * appended once by the establishing provider inside the child's initial - * turn, before its first request. Continuable records also carry their + * appended once inside the child's initial turn, before its first + * request. Continuable records also carry their * resumable composition. Log-only: it carries no `surfaceOp`, never enters * model history, and survives compaction. */ @@ -1093,8 +1096,8 @@ export type SessionEvent = { * `tool/ptc-dispatch-start` with the same `subCallId`), the tool `name` * with the same JSON-normalized `arguments`, and the sub-call's complete * durable outcome in `tool/result`'s own vocabulary (`content` + `isError` - * + optional structured `error`), so UIs and SDKs render a sub-call through - * the exact path used for a native call. Every started sub-call settles + * + optional presentation `meta` and structured `error`), so UIs and SDKs + * render a sub-call through the exact path used for a native call. Every started sub-call settles * with exactly one of these (abort included: the aborted pipeline result * is an `isError` outcome). * Log-only: `deriveMessages()` ignores it, so sub-calls never re-enter @@ -1106,7 +1109,7 @@ export type SessionEvent = { 'tool/ptc-dispatch': PtcDispatchEventData ``` -来源:[`packages/core/tools/src/types.ts:58`](../packages/core/tools/src/types.ts) +来源:[`packages/core/tools/src/types.ts:61`](../packages/core/tools/src/types.ts) @@ -1129,7 +1132,7 @@ export type SessionEvent = { 'tool/ptc-dispatch-start': PtcDispatchStartEventData ``` -来源:[`packages/core/tools/src/types.ts:42`](../packages/core/tools/src/types.ts) +来源:[`packages/core/tools/src/types.ts:45`](../packages/core/tools/src/types.ts) @@ -1292,6 +1295,22 @@ export type SessionEvent = { 来源:[`packages/web/web-search-deepseek/src/provider.ts:82`](../packages/web/web-search-deepseek/src/provider.ts) +### `working-directory/*` + + + +#### `working-directory/change` — log-only + +```ts persistence-catalog +/** Effective working directory; the immutable Session header retains the original project. */ +'working-directory/change': { + /** Absolute directory used by subsequent directory-based operations. */ + cwd: string +} +``` + +来源:[`packages/session/working-directory/src/index.ts:36`](../packages/session/working-directory/src/index.ts) + ### `workspace/*` @@ -1761,6 +1780,14 @@ SHA-256: `e42b0f40b5d0b628c6e1c44a8fb9eda0780037a81959e1b93d2dfb47b46b0e87` `"every"` + + +### `"external"` + +SHA-256: `fc351843b8ecbd05d739c4ca4fcd4bc7cfad6c0beacbbd48389959bb1bf06cf3` + +`"external"` + ### `"failed"` @@ -2951,6 +2978,22 @@ SHA-256: `db6c5246911d47a7969f6b4cb47c7d0dbbde75c402710b22ace3672fa97bebe0` `"webhook"` + + +### `"working-directory"` + +SHA-256: `7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a` + +`"working-directory"` + + + +### `"working-directory/change"` + +SHA-256: `d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5` + +`"working-directory/change"` + ### `"workspace-write"` @@ -3009,7 +3052,7 @@ SHA-256: `af6e173f7be819bcd0a5c8d77918649c10ce761d8a184ff472f2e01ce64eb2e0` SHA-256: `83da72c6857601c75da11212ef847f57d457b9c99f0a82246dcdff3e26700239` -来源:[`packages/context/agent-instructions/src/render.ts:47`](../packages/context/agent-instructions/src/render.ts) +来源:[`packages/context/agent-instructions/src/render.ts:53`](../packages/context/agent-instructions/src/render.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -3036,7 +3079,7 @@ SHA-256: `2ab12a0e345658f9342816d773fe610f049982bcad82631f43b9205abe43e5ee` SHA-256: `6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590` -来源:[`packages/context/agent-instructions/src/state.ts:37`](../packages/context/agent-instructions/src/state.ts) +来源:[`packages/context/agent-instructions/src/state.ts:40`](../packages/context/agent-instructions/src/state.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -3056,7 +3099,7 @@ SHA-256: `6f22a9d02b9a1f1157f3b0e7bdccd7e7b12b085c4efdb709dfc74a465cd4e590` SHA-256: `4a9bef9f27a942520cc2279ca7dd3082556e4bff543f66b5c0333e1bbed1cd86` -来源:[`packages/subagent/subagent/src/continuation-messages.ts:15`](../packages/subagent/subagent/src/continuation-messages.ts) +来源:[`packages/subagent/subagent/src/continuation-messages.ts:14`](../packages/subagent/subagent/src/continuation-messages.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -3299,7 +3342,7 @@ SHA-256: `2517ba143a271508d3ca35126d5aca8f7f1facf5aaddce4adf011832042fa2b9` | `body` | 必需 | [`{ max_tokens, messages, model, tools }`](#persistence-type-sha256-930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731) | | `endpoint` | 必需 | `string` | - + @@ -3307,7 +3350,7 @@ SHA-256: `2517ba143a271508d3ca35126d5aca8f7f1facf5aaddce4adf011832042fa2b9` ### `DeveloperMessage` -SHA-256: `1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200` +SHA-256: `af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08` 来源:[`packages/llm/llm/src/message.ts:157`](../packages/llm/llm/src/message.ts) @@ -3316,11 +3359,11 @@ SHA-256: `1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"developer"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.developer-message.source`; `kind`; `preserve`. -仅表示归属的新增 kind:`tmux-context`, `user-question-reply`. +仅表示归属的新增 kind:`tmux-context`, `user-question-reply`, `working-directory`. @@ -3615,13 +3658,13 @@ SHA-256: `18ee62b8900a4c3d046700f05d7a4d49d6cab2a660a020c87481d1603dd8bd4f` - + ### `HookDialect` SHA-256: `a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb` -来源:[`packages/hooks/hook-protocol/src/types.ts:48`](../packages/hooks/hook-protocol/src/types.ts) +来源:[`packages/experimental/hook-protocol/src/types.ts:48`](../packages/experimental/hook-protocol/src/types.ts) 以下类型之一: @@ -3932,7 +3975,7 @@ SHA-256: `4b1e632e05776982a2d21c25a50c7c04c199d311c5675218fa73ff9fe30d22cd` SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0` -来源:[`packages/llm/llm/src/call-config.ts:23`](../packages/llm/llm/src/call-config.ts) +来源:[`packages/llm/llm/src/call-config.ts:31`](../packages/llm/llm/src/call-config.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -3953,7 +3996,7 @@ SHA-256: `67d5f73ceae82f5265258142ad6cf172de2c1fef93ba551b7f0454e0a93bffe0` SHA-256: `326a9bfda27dda5882fb3cbe6e715df41dae2298fdae3d66437457268a665693` -来源:[`packages/llm/llm/src/call-config.ts:36`](../packages/llm/llm/src/call-config.ts) +来源:[`packages/llm/llm/src/call-config.ts:54`](../packages/llm/llm/src/call-config.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4017,7 +4060,7 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` | `step` | 必需 | `number` | | `turn` | 必需 | `number` | - + @@ -4025,15 +4068,15 @@ SHA-256: `6f72ece8f317cd080c4f20a93f1bf709965f5b81ac4669528f5a023191cfcf20` ### `Message` -SHA-256: `cb7b863ddb7b3eab5f18f216602feba25f47b456e73e4e72b6bcc259875c6dab` +SHA-256: `d901c4f5e856591c3217af983caa15bba568d580700f0958bab56b88004eced9` 来源:[`packages/llm/llm/src/message.ts:196`](../packages/llm/llm/src/message.ts) 以下类型之一: - [`ToolResultMessage`](#persistence-type-sha256-4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731) -- [`DeveloperMessage`](#persistence-type-sha256-1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200) -- [`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd) +- [`DeveloperMessage`](#persistence-type-sha256-af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08) +- [`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97) - [`SystemMessage`](#persistence-type-sha256-09f3f8dc417f278257202714132f98ba7b52957d7b558c4a04456603890eb363) - [`AssistantMessage`](#persistence-type-sha256-fa87537f4a9c1e25602990a8bf686b383e9e00352ec8946e16ae24ba7c1625be) @@ -4110,7 +4153,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` - `"negative"` - `"positive"` - + @@ -4118,7 +4161,7 @@ SHA-256: `390eed1028aba02774f14408647acec10ec5516e97d4f15ecde2b9989d5a69f2` ### `MessageSource` -SHA-256: `26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116` +SHA-256: `1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154` 来源:[`packages/llm/llm/src/message.ts:136`](../packages/llm/llm/src/message.ts) @@ -4234,14 +4277,15 @@ SHA-256: `26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116` | `user-approval` | 未声明 | 无 | [`{ kind: "user-approval" }`](#persistence-type-sha256-6292bcbedaa97feaec5b48a03bb8144b581b93309d9d6c905f26da7a6a7a0a4f) | | `user-question-reply` | 未声明 | `callId`: `string`; `outcome`: `"answered"` | [`{ kind: "user-question-reply" }`](#persistence-type-sha256-c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005) | | `webhook` | `"notice"` | `deliveryId`: `string`; `provider`: `string`; `ruleId`: `string`; `source`: `string`; `summary`: `string` | [`{ kind: "webhook", form: "notice" }`](#persistence-type-sha256-9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94) | +| `working-directory` | 未声明 | 无 | [`{ kind: "working-directory" }`](#persistence-type-sha256-2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5) | - + ### `Message[]` -SHA-256: `3f42f45cbb8a06586d247e5f3763dc02c8d31a145a9a3b82fabcd101e7e224cc` +SHA-256: `4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c` -[`Message`](#persistence-type-sha256-cb7b863ddb7b3eab5f18f216602feba25f47b456e73e4e72b6bcc259875c6dab) 的数组。 +[`Message`](#persistence-type-sha256-d901c4f5e856591c3217af983caa15bba568d580700f0958bab56b88004eced9) 的数组。 @@ -4359,7 +4403,7 @@ SHA-256: `1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345` [`PresentedFile`](#persistence-type-sha256-b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97) 的数组。 - + @@ -4367,9 +4411,9 @@ SHA-256: `1d9cb3caa96100b18b1911fa3ff8c751ef6992971a03c5f4d9aefc1a0004a345` ### `PtcDispatchEventData` -SHA-256: `0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8` +SHA-256: `cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6` -来源:[`packages/core/tools/src/types.ts:20`](../packages/core/tools/src/types.ts) +来源:[`packages/core/tools/src/types.ts:21`](../packages/core/tools/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4377,6 +4421,7 @@ SHA-256: `0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `error` | 可选 | [`{ code, name, reason? }`](#persistence-type-sha256-a81059ca06999da1e712414520c2c65e491d052fcf9640fae20aa38f0a5dc735) | | `isError` | 必需 | `boolean` | +| `meta` | 可选 | [`JsonValue`](#persistence-type-sha256-c592ce75aab73fcab19c1d7845684c72cf402b78d2e1f2833a58ecf9f3598ed6) | | `name` | 必需 | `string` | | `parentCallId` | 必需 | `string` | | `rootCallId` | 必需 | `string` | @@ -4392,7 +4437,7 @@ SHA-256: `0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8` SHA-256: `2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f` -来源:[`packages/core/tools/src/types.ts:11`](../packages/core/tools/src/types.ts) +来源:[`packages/core/tools/src/types.ts:12`](../packages/core/tools/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4608,7 +4653,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` | `source` | 必需 | [`SessionTitleSource`](#persistence-type-sha256-30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f) | | `title` | 必需 | `string` | - + @@ -4616,7 +4661,7 @@ SHA-256: `b56f6a885da3dc394c69a3e3cc5c0cfc4da15601b63fda4989eee8f40efc5be4` ### `SessionTitleLlmRequestEventData` -SHA-256: `fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996` +SHA-256: `045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19` 来源:[`packages/session/session-title-llm/src/index.ts:34`](../packages/session/session-title-llm/src/index.ts) @@ -4624,7 +4669,8 @@ SHA-256: `fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996` |---|---|---| | `maxTokens` | 必需 | `number` | | `messageSeqs` | 必需 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | -| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-3f42f45cbb8a06586d247e5f3763dc02c8d31a145a9a3b82fabcd101e7e224cc) | +| `messages` | 必需 | [`Message[]`](#persistence-type-sha256-4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c) | +| `reasoningEffort` | 可选 | `string` | | `route` | 必需 | [`SessionTitleModelIdentity`](#persistence-type-sha256-07e1a58c58b593f507b8e7f25723bb0aa42baae6e00f5ecfddf28cb1e64d3235) | | `system` | 必需 | `string` | | `titleProvider` | 必需 | `string` | @@ -4686,7 +4732,7 @@ SHA-256: `30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f` SHA-256: `fc2ae6962f9f8801c2b1e414668e1e17cbb7544c4f39381198977a38bd10c093` -来源:[`packages/skill/tool-skill/src/index.ts:34`](../packages/skill/tool-skill/src/index.ts) +来源:[`packages/skill/tool-skill/src/index.ts:35`](../packages/skill/tool-skill/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4735,7 +4781,7 @@ SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180` - [`{ type: "finish" }`](#persistence-type-sha256-a2b49331716ba0a7078b026d145306a70eae64b7313c3e6204bd931e75ec3bb1) - [`{ type: "usage" }`](#persistence-type-sha256-f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515) - + @@ -4743,17 +4789,18 @@ SHA-256: `47e9095591ae36e7c39ee62553097772f2bfa16304536a72dbbc3f691f605180` ### `SubagentCatalogEvent` -SHA-256: `1de64ea9a0849ce631b7e4e1255ae773fabd94a4a7c8d27e716f8012302e12c0` +SHA-256: `f731ffe80810b0289b3e694d8f40af6f01865c02a622ee457be90ca808b66e87` 来源:[`packages/subagent/subagent/src/catalog.ts:28`](../packages/subagent/subagent/src/catalog.ts) 以下类型之一: -- [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd) - [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-e2ad8f5bcd813f268ffef1b10a12d7ddd2b8ced8b9b6d0275b3f6b7ed094a187) +- [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-5d542ac7bdf13310c46e1a884dec9fb18ad8c6e153cff207e639cd7bf9d7e694) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-b6896938660a1dfe4ddd100fb3164d9f8fa57feeabfe37441a80106c9942697a) - [`{ childCreatedAt, childId, label?, mode, … }`](#persistence-type-sha256-a0f25b5aeeebf2bf1542ce67726b095e3b139a39c71861d57e827f12dea1f499) +- [`{ childCreatedAt, childId, label, mode, … }`](#persistence-type-sha256-0e10d7c096b47e61defe372a5fc8bd37d835199410eac44367f95c45082498bd) @@ -4782,7 +4829,7 @@ SHA-256: `86d1d0e9cfebb240421187e3dc37fb71cc38a1501885cf32f8f9adef7b0a2bde` SHA-256: `4e97c3d85c0fc817ee58873c38c25a2474abaaa0af4839311b50b68db3b8cf1a` -来源:[`packages/subagent/subagent/src/continuation-messages.ts:30`](../packages/subagent/subagent/src/continuation-messages.ts) +来源:[`packages/subagent/subagent/src/continuation-messages.ts:29`](../packages/subagent/subagent/src/continuation-messages.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5109,7 +5156,7 @@ SHA-256: `43e311235aa537df1bacdd92d144df8a952337a76e9ac892cdf670f1a797bad8` SHA-256: `3cfc3a56502da1f8c6153c2c56657bab4b749056968634dd0991d325ec1c919f` -来源:[`packages/core/tools/src/index.ts:700`](../packages/core/tools/src/index.ts) +来源:[`packages/core/tools/src/index.ts:701`](../packages/core/tools/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5278,7 +5325,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` | `interrupted` | 未声明 | 无 | [`{ kind: "interrupted" }`](#persistence-type-sha256-56b7c047694875a843ba020dfea3c8380d54ae4ff88ab349e72004dc21e07c23) | | `max-tokens` | 未声明 | 无 | [`{ kind: "max-tokens" }`](#persistence-type-sha256-ab7ec5f9eb2c6dfacca5da4c37cf7ba85d04172053ee6b1ebf1709ffcd99c1a7) | - + @@ -5286,7 +5333,7 @@ SHA-256: `f1687f982fa3d41ef29e159feed154ecd13af4dd209acaee16038f223f2e9402` ### `UserMessage` -SHA-256: `cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd` +SHA-256: `89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97` 来源:[`packages/llm/llm/src/message.ts:162`](../packages/llm/llm/src/message.ts) @@ -5295,19 +5342,19 @@ SHA-256: `cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd` | `content` | 必需 | [`ContentBlock[]`](#persistence-type-sha256-b52f215a6cd691058a3413c12f161fa3b563a2f4895adb3df6f744ec4b0a6ef1) | | `id` | 必需 | `string` | | `role` | 必需 | `"user"` | -| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116) | +| `source` | 必需 | [`MessageSource`](#persistence-type-sha256-1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154) | 消息源兼容策略:`source` — `session-source-attribution` v1; `session.user-message.source`; `kind`; `preserve`. -仅表示归属的新增 kind:`tmux-context`, `user-question-reply`. +仅表示归属的新增 kind:`tmux-context`, `user-question-reply`, `working-directory`. - + ### `UserMessage[]` -SHA-256: `5a92f12db2059c282cdea45b6845c65e6f4a95dd3d649aeddbde6f9ae03c561f` +SHA-256: `2fef90def36aa758ca096c5f6ad0e00dbb2101943e19ca54b05618801974894a` -[`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd) 的数组。 +[`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97) 的数组。 @@ -5433,6 +5480,12 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` + + + + + + @@ -5461,12 +5514,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` - - - - - - @@ -5503,7 +5550,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/webhook/webhook/src/brand.ts:12`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:6`](../packages/webhook/webhook/src/brand.ts) · [`packages/webhook/webhook/src/brand.ts:9`](../packages/webhook/webhook/src/brand.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -5689,7 +5736,7 @@ SHA-256: `b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7` SHA-256: `20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5` -来源:[`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts) +来源:[`packages/plan/plan-mode/src/index.ts:52`](../packages/plan/plan-mode/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5879,13 +5926,27 @@ SHA-256: `b6896938660a1dfe4ddd100fb3164d9f8fa57feeabfe37441a80106c9942697a` | `mode` | 必需 | `"one-shot"` | | `version` | 必需 | `1` | + + +### `{ childCreatedAt, childId, label?, mode, … }` + +SHA-256: `cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `childCreatedAt` | 必需 | `number` | +| `childId` | 必需 | `string` | +| `label` | 可选 | `string` | +| `mode` | 必需 | `"external"` | +| `version` | 必需 | `2` | + ### `{ code, name, reason? }` SHA-256: `a81059ca06999da1e712414520c2c65e491d052fcf9640fae20aa38f0a5dc735` -来源:[`packages/core/session/src/types.ts:383`](../packages/core/session/src/types.ts) · [`packages/core/tools/src/types.ts:24`](../packages/core/tools/src/types.ts) +来源:[`packages/core/session/src/types.ts:383`](../packages/core/session/src/types.ts) · [`packages/core/tools/src/types.ts:27`](../packages/core/tools/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5978,13 +6039,25 @@ SHA-256: `3e4e7683b0192c9f0e6a75c59076108f017892cdb302a98032c2632fd79c356f` | `sourceCommandId` | 可选 | `string` | | `turn` | 必需 | [`OptionalSessionSeq`](#persistence-type-sha256-3bd652ebfa8726b3ce3937a4f1bd2759e02f86b3a08a20f9e41a8513656fbc5f) | + + +### `{ cwd }` + +SHA-256: `da60ed58a8fa23d7aa4920ab5c1f4e3d28109cb0797ef9e5c8b125721b847582` + +来源:[`packages/session/working-directory/src/index.ts:36`](../packages/session/working-directory/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `cwd` | 必需 | `string` | + ### `{ decision, durationMs, exitCode?, handlerId, … }` SHA-256: `8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141` -来源:[`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-protocol/src/types.ts) +来源:[`packages/experimental/hook-protocol/src/types.ts:31`](../packages/experimental/hook-protocol/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -6043,7 +6116,7 @@ SHA-256: `374fdd68aee5ff844e378c0cbdd961d247a0f22e4ade0bb03d800cd2a46a2b8b` SHA-256: `83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693` -来源:[`packages/skill/tool-skill/src/index.ts:40`](../packages/skill/tool-skill/src/index.ts) +来源:[`packages/skill/tool-skill/src/index.ts:41`](../packages/skill/tool-skill/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -6064,7 +6137,7 @@ SHA-256: `aba175fb20ffc71112c55efe70458ff5d7dbfb83501892b4ba8d077f0ea5d7ba` SHA-256: `119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25` -来源:[`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) +来源:[`packages/experimental/hook-protocol/src/types.ts:19`](../packages/experimental/hook-protocol/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -6170,18 +6243,18 @@ SHA-256: `ad0c38a0adb82a94c925456df41586d9b5f9202fca18593eae2213d4445b4585` | `reason` | 必需 | [`RequestHeaderReason`](#persistence-type-sha256-530da6c0267813bb4511af72883cc518834e624e3b3220a7062955fd57a788a6) | | `startsSeries` | 可选 | `true` | - + ### `{ headerSeq?, message, step, turn }` -SHA-256: `a5d80b5a2f3a6bbaf8f0334126632b78f94d8346122c68e8122ea89906d63d26` +SHA-256: `5b55817a403569fbb2d379496df2e3f3f71107f6c577a218028ce187c60b61e6` 来源:[`packages/core/session/src/types.ts:311`](../packages/core/session/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| | `headerSeq` | 可选 | `number` | -| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200) | +| `message` | 必需 | [`DeveloperMessage`](#persistence-type-sha256-af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08) | | `step` | 必需 | `number` | | `turn` | 必需 | `number` | @@ -6265,17 +6338,17 @@ SHA-256: `17d1afb770d9941936130996da00dc86782cfef731d8d6526162c301256a4ac3` |---|---|---| | `inherited` | 可选 | `true` | - + ### `{ inserted, outcome?, removedCount?, start, … }` -SHA-256: `149c5a77529cb0dc06f5ceb6273069b7606174d1e7893b5f9b1071c15306b846` +SHA-256: `83a665476a9ee9af05cb1fdded12dd647d1b97b491200d7f12ef1af7cdac4428` 来源:[`packages/core/agent/src/types.ts:96`](../packages/core/agent/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| -| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-5a92f12db2059c282cdea45b6845c65e6f4a95dd3d649aeddbde6f9ae03c561f) | +| `inserted` | 必需 | [`UserMessage[]`](#persistence-type-sha256-2fef90def36aa758ca096c5f6ad0e00dbb2101943e19ca54b05618801974894a) | | `outcome` | 可选 | `"canceled"` | | `removedCount` | 可选 | `number` | | `start` | 必需 | `number` | @@ -7436,7 +7509,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49` SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543` -来源:[`packages/core/tools/src/index.ts:34`](../packages/core/tools/src/index.ts) +来源:[`packages/core/tools/src/index.ts:35`](../packages/core/tools/src/index.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7554,7 +7627,7 @@ SHA-256: `c55827fba7018198772c50e37cb7d3f60a3d8bb6e94b07de72dee0d9f8968005` SHA-256: `9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94` -来源:[`packages/webhook/webhook/src/types.ts:74`](../packages/webhook/webhook/src/types.ts) +来源:[`packages/experimental/webhook/src/types.ts:74`](../packages/experimental/webhook/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7566,6 +7639,18 @@ SHA-256: `9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94` | `source` | 必需 | `string` | | `summary` | 必需 | `string` | + + +### `{ kind: "working-directory" }` + +SHA-256: `2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5` + +来源:[`packages/session/working-directory/src/index.ts:29`](../packages/session/working-directory/src/index.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `kind` | 必需 | `"working-directory"` | + ### `{ max_tokens, messages, model, tools }` @@ -7823,17 +7908,17 @@ SHA-256: `a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b` | `time` | 必需 | `number` | | `type` | 必需 | `"agent-preset/selected"` | - + ### `{ type: "agent/inbox/spliced" }` -SHA-256: `6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178` +SHA-256: `9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-149c5a77529cb0dc06f5ceb6273069b7606174d1e7893b5f9b1071c15306b846) | +| `data` | 必需 | [`{ inserted, outcome?, removedCount?, start, … }`](#persistence-type-sha256-83a665476a9ee9af05cb1fdded12dd647d1b97b491200d7f12ef1af7cdac4428) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -8074,17 +8159,17 @@ SHA-256: `13d3d180f977bf78081d487ffa0ecb75857349bcab29a5a3fb48189fca2a6176` | `time` | 必需 | `number` | | `type` | 必需 | `"deliverables/presented"` | - + ### `{ type: "developer/message" }` -SHA-256: `186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467` +SHA-256: `ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-a5d80b5a2f3a6bbaf8f0334126632b78f94d8346122c68e8122ea89906d63d26) | +| `data` | 必需 | [`{ headerSeq?, message, step, turn }`](#persistence-type-sha256-5b55817a403569fbb2d379496df2e3f3f71107f6c577a218028ce187c60b61e6) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8472,17 +8557,17 @@ SHA-256: `1b912703e2d64f91c99c675b8f805b01076c8325b905c1218ad81ef0b24909d5` | `time` | 必需 | `number` | | `type` | 必需 | `"session/title"` | - + ### `{ type: "session/title-llm-request" }` -SHA-256: `ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46` +SHA-256: `b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996) | +| `data` | 必需 | [`SessionTitleLlmRequestEventData`](#persistence-type-sha256-045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -8520,17 +8605,17 @@ SHA-256: `4513e088d43e6c68425be30451b9f961cc264fe7318ca62681c41d4d78615986` | `time` | 必需 | `number` | | `type` | 必需 | `"step/start"` | - + ### `{ type: "subagent/catalog" }` -SHA-256: `3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604` +SHA-256: `53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`SubagentCatalogEvent`](#persistence-type-sha256-1de64ea9a0849ce631b7e4e1255ae773fabd94a4a7c8d27e716f8012302e12c0) | +| `data` | 必需 | [`SubagentCatalogEvent`](#persistence-type-sha256-f731ffe80810b0289b3e694d8f40af6f01865c02a622ee457be90ca808b66e87) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -8810,17 +8895,17 @@ SHA-256: `3b1be838223869fe0a08210db85bf773796ed3f2373ac16555dff227cade0c48` | `time` | 必需 | `number` | | `type` | 必需 | `"tool/call"` | - + ### `{ type: "tool/ptc-dispatch" }` -SHA-256: `100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163` +SHA-256: `d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`PtcDispatchEventData`](#persistence-type-sha256-0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8) | +| `data` | 必需 | [`PtcDispatchEventData`](#persistence-type-sha256-cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `time` | 必需 | `number` | @@ -8905,17 +8990,17 @@ SHA-256: `f1a96afe7503ac1817de769cd0ba13d9b80111c7034c50106b3344ecc14cc515` | `type` | 必需 | `"usage"` | | `usage` | 必需 | [`TokenUsage`](#persistence-type-sha256-fec5442b9ee4122e10514d81d5a43648003fe8bfb7ee2ba50c0cdbdd383ed394) | - + ### `{ type: "user/message" }` -SHA-256: `b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411` +SHA-256: `2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade` | 属性 | 存在性 | 类型 | |---|---|---| -| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd) | +| `data` | 必需 | [`UserMessage`](#persistence-type-sha256-89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97) | | `ignorable` | 可选 | `true` | | `seq` | 必需 | `number` | | `sourceEventSeqs` | 可选 | [`number[]`](#persistence-type-sha256-5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78) | @@ -8953,6 +9038,22 @@ SHA-256: `2d11ca7b0d4493e244b74eba093227866b33249841e59a1e9bf56afed38604c3` | `name` | 必需 | `"web_search"` | | `type` | 必需 | `"web_search_20250305"` | + + + + +### `{ type: "working-directory/change" }` + +SHA-256: `124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`{ cwd }`](#persistence-type-sha256-da60ed58a8fa23d7aa4920ab5c1f4e3d28109cb0797ef9e5c8b125721b847582) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"working-directory/change"` | + diff --git a/docs/persistence-changes/2026-09-13-working-directory.i18n.yaml b/docs/persistence-changes/2026-09-13-working-directory.i18n.yaml new file mode 100644 index 0000000000..9f630bfbcb --- /dev/null +++ b/docs/persistence-changes/2026-09-13-working-directory.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-09-13-working-directory.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-09-13-working-directory.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-09-13-working-directory: + en: 2ae6bda09b2fb22b + zh: 45204fff7e931e07 +/2026-09-13-working-directory/summary: + en: ac7c5d84de3040fa + zh: 85af1f5ea31e8956 +/2026-09-13-working-directory/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-09-13-working-directory/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-09-13-working-directory/compatibility: + en: 3dae5b1bf48b0435 + zh: aea832a92da37dc4 +/2026-09-13-working-directory/verification: + en: ff3444e53a105e07 + zh: 7a77d20c75e59a59 +/2026-09-13-working-directory/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-09-13-working-directory.md b/docs/persistence-changes/2026-09-13-working-directory.md new file mode 100644 index 0000000000..0c3de3fd7e --- /dev/null +++ b/docs/persistence-changes/2026-09-13-working-directory.md @@ -0,0 +1,48 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-09-13-working-directory + +English | [中文](2026-09-13-working-directory.zh.md) + +## Summary + +Adds durable per-Session working-directory changes. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-13-working-directory +baseline: false +changes: + - root: "event:working-directory/change" + previous: null + after: "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3" + decision: same-version +``` + + +## Compatibility + +Existing Session headers and events remain valid. The new event is required on read: older builds that do not know working-directory/change refuse logs containing it. The event vocabulary grows without changing the structural Session format. + + +## Verification + +Focused working-directory and system-prompt suites pass 113 tests with per-file 100% coverage. Keyless headless replay verifies changes, recovery, user-context notices, and a stable system prompt. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-09-13-working-directory.schema.json b/docs/persistence-changes/2026-09-13-working-directory.schema.json new file mode 100644 index 0000000000..cf728b6d31 --- /dev/null +++ b/docs/persistence-changes/2026-09-13-working-directory.schema.json @@ -0,0 +1,76 @@ +{ + "formatVersion": 1, + "roots": [ + { + "key": "event:working-directory/change", + "kind": "event", + "event": "working-directory/change", + "surface": false, + "digest": "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "cwd", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "working-directory/change" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-09-13-working-directory.zh.md b/docs/persistence-changes/2026-09-13-working-directory.zh.md new file mode 100644 index 0000000000..81985385a3 --- /dev/null +++ b/docs/persistence-changes/2026-09-13-working-directory.zh.md @@ -0,0 +1,48 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-09-13-working-directory + +[English](2026-09-13-working-directory.md) | 中文 + +## 概述 + +增加每个 Session 的持久化工作目录变更。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-13-working-directory +baseline: false +changes: + - root: "event:working-directory/change" + previous: null + after: "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3" + decision: same-version +``` + + +## 兼容性 + +已有 Session header 和事件继续有效。新事件在读取时为必需:不认识 working-directory/change 的旧版本拒绝包含它的日志。事件词汇扩展不改变 Session 的结构格式。 + + +## 验证 + +工作目录和系统提示词定向套件通过 113 个测试,各源码文件覆盖率达到 100%。无密钥 headless 回放验证变更、恢复、用户上下文通知和稳定的系统提示词。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/2026-09-19-external-subagent-catalog.i18n.yaml b/docs/persistence-changes/2026-09-19-external-subagent-catalog.i18n.yaml new file mode 100644 index 0000000000..d2a7867bef --- /dev/null +++ b/docs/persistence-changes/2026-09-19-external-subagent-catalog.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-09-19-external-subagent-catalog.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-09-19-external-subagent-catalog.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-09-19-external-subagent-catalog: + en: 81860767c8b7e373 + zh: 4fcb6866cd8d8696 +/2026-09-19-external-subagent-catalog/summary: + en: 02848a04a27423e5 + zh: 8fa393f6130f3212 +/2026-09-19-external-subagent-catalog/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-09-19-external-subagent-catalog/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-09-19-external-subagent-catalog/compatibility: + en: ae460855a021bf2b + zh: 3789da36fc4202fd +/2026-09-19-external-subagent-catalog/verification: + en: f629646a19627773 + zh: 7538959cb86407a1 +/2026-09-19-external-subagent-catalog/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-09-19-external-subagent-catalog.md b/docs/persistence-changes/2026-09-19-external-subagent-catalog.md new file mode 100644 index 0000000000..1522d0ffa5 --- /dev/null +++ b/docs/persistence-changes/2026-09-19-external-subagent-catalog.md @@ -0,0 +1,48 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-09-19-external-subagent-catalog + +English | [中文](2026-09-19-external-subagent-catalog.zh.md) + +## Summary + +Add external mode to parent catalog entries for children without a local Session. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-19-external-subagent-catalog +baseline: false +changes: + - root: "event:subagent/catalog" + previous: "2026-09-20-unknown-child-catalog" + after: "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac" + decision: same-version +``` + + +## Compatibility + +Existing version-0 and version-1 one-shot and continuable facts remain valid; version 1 also retains unknown mode. New external executions write payload version 2 with mode external, without a separate marker. Creation writes one membership fact; execution and cleanup do not update it. Duplicate child ids remain invalid. This does not change released generations or the Session format version. + + +## Verification + +Focused catalog, external activation, native V4 restoration, client navigation, and control-tool tests passed. They cover external membership, historical local modes, invalid marker rejection, non-navigable external leaves, and duplicate-child rejection. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-09-19-external-subagent-catalog.schema.json b/docs/persistence-changes/2026-09-19-external-subagent-catalog.schema.json new file mode 100644 index 0000000000..337379c605 --- /dev/null +++ b/docs/persistence-changes/2026-09-19-external-subagent-catalog.schema.json @@ -0,0 +1,290 @@ +{ + "formatVersion": 2, + "roots": [ + { + "key": "event:subagent/catalog", + "kind": "event", + "event": "subagent/catalog", + "surface": false, + "digest": "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 17, + "optional": true + }, + { + "name": "seq", + "type": 3, + "optional": false + }, + { + "name": "time", + "type": 3, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 7, + 9, + 12, + 14, + 15 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "continuable" + }, + { + "kind": "literal", + "value": 0 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": false + }, + { + "name": "mode", + "type": 5, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 10, + "optional": false + }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "external" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 13, + "optional": false + }, + { + "name": "version", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "one-shot" + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 13, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 16, + "optional": false + }, + { + "name": "version", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "unknown" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "subagent/catalog" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-09-19-external-subagent-catalog.zh.md b/docs/persistence-changes/2026-09-19-external-subagent-catalog.zh.md new file mode 100644 index 0000000000..e1d563ed48 --- /dev/null +++ b/docs/persistence-changes/2026-09-19-external-subagent-catalog.zh.md @@ -0,0 +1,48 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-09-19-external-subagent-catalog + +[English](2026-09-19-external-subagent-catalog.md) | 中文 + +## 概述 + +为父级目录中没有本地 Session 的子级添加 external 模式。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-09-19-external-subagent-catalog +baseline: false +changes: + - root: "event:subagent/catalog" + previous: "2026-09-20-unknown-child-catalog" + after: "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac" + decision: same-version +``` + + +## 兼容性 + +已有 version-0 和 version-1 的 one-shot 与 continuable 事实仍然有效;version 1 还保留 unknown 模式。新外部执行写入 payload version 2 和 external 模式,不使用单独的标记。创建时写入一次成员事实,执行和清理不更新它。重复的子 id 仍然无效。这不改变已发布的代际或 Session 格式版本。 + + +## 验证 + +定向 catalog、外部 activation、原生 V4 恢复、客户端导航和控制工具测试通过,覆盖外部成员关系、历史本地模式、非法标记拒绝、不可导航的外部叶节点,以及重复子级拒绝。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/2026-10-05-ptc-operation-metadata.i18n.yaml b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.i18n.yaml new file mode 100644 index 0000000000..1d225bc26f --- /dev/null +++ b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-05-ptc-operation-metadata.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-05-ptc-operation-metadata.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-05-ptc-operation-metadata: + en: 1dead83b6553cd1a + zh: 9b6177c2b132640f +/2026-10-05-ptc-operation-metadata/summary: + en: 515119ce4f93c34d + zh: 36f42e64c69945cf +/2026-10-05-ptc-operation-metadata/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-05-ptc-operation-metadata/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-05-ptc-operation-metadata/compatibility: + en: e9737c6c28ee0ab0 + zh: 468a046ccd51d95b +/2026-10-05-ptc-operation-metadata/verification: + en: a6d2fb4553a4f895 + zh: bb2367ad270ee0bc +/2026-10-05-ptc-operation-metadata/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-05-ptc-operation-metadata.md b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.md new file mode 100644 index 0000000000..aef4870208 --- /dev/null +++ b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.md @@ -0,0 +1,48 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-05-ptc-operation-metadata + +English | [中文](2026-10-05-ptc-operation-metadata.zh.md) + +## Summary + +Adds optional pure presentation metadata to persisted nested PTC tool results so history retains operation-time directories and file targets. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-05-ptc-operation-metadata +baseline: false +changes: + - root: "event:tool/ptc-dispatch" + previous: "2026-09-16-session-format-v4" + after: "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5" + decision: same-version +``` + + +## Compatibility + +Existing tool/ptc-dispatch records without meta remain valid. The optional JsonValue field does not change canonical program values, model-visible content, tool identities or message ordering. Historical calls without a recorded relative target keep safe unavailable navigation. The writer and accepted baseline stay at V4. + + +## Verification + +963 focused tests passed with per-file 100% coverage of the six affected runtime/UI sources; actual nested shell and relative filesystem regressions, frozen metadata controls, TypeScript SDK 45 tests, Python SDK 29 tests and three owning keyless refresh/replays passed. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-05-ptc-operation-metadata.schema.json b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.schema.json new file mode 100644 index 0000000000..6c5e59e6da --- /dev/null +++ b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.schema.json @@ -0,0 +1,457 @@ +{ + "formatVersion": 2, + "roots": [ + { + "key": "event:tool/ptc-dispatch", + "kind": "event", + "event": "tool/ptc-dispatch", + "surface": false, + "digest": "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 17, + "optional": false + }, + { + "name": "time", + "type": 17, + "optional": false + }, + { + "name": "type", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 2, + "optional": false + }, + { + "name": "content", + "type": 3, + "optional": false + }, + { + "name": "error", + "type": 31, + "optional": true + }, + { + "name": "isError", + "type": 32, + "optional": false + }, + { + "name": "meta", + "type": 33, + "optional": true + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "parentCallId", + "type": 6, + "optional": false + }, + { + "name": "rootCallId", + "type": 6, + "optional": false + }, + { + "name": "subCallId", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "array", + "element": 4 + }, + { + "kind": "union", + "types": [ + 5, + 8, + 10, + 13, + 15, + 19, + 21 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 9, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 11, + "optional": true + }, + { + "name": "toolName", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 12, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 14, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 6, + "optional": false + }, + { + "name": "id", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 22, + "optional": false + }, + { + "name": "offloaded", + "type": 29, + "optional": true + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "mediaType", + "type": 23, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": true + }, + { + "name": "originalDimensions", + "type": 28, + "optional": true + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 24, + 25, + 26, + 27 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 17, + "optional": false + }, + { + "name": "width", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "code", + "type": 6, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + }, + { + "name": "reason", + "type": 6, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "union", + "types": [ + 6, + 17, + 32, + 34, + 35, + 36 + ] + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 6, + "value": 33 + } + ] + }, + { + "kind": "literal", + "value": "tool/ptc-dispatch" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-10-05-ptc-operation-metadata.zh.md b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.zh.md new file mode 100644 index 0000000000..3e531b72f9 --- /dev/null +++ b/docs/persistence-changes/2026-10-05-ptc-operation-metadata.zh.md @@ -0,0 +1,48 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-05-ptc-operation-metadata + +[English](2026-10-05-ptc-operation-metadata.md) | 中文 + +## 概述 + +为持久化的嵌套 PTC 工具结果增加可选的纯展示元数据,使历史保留操作时的目录和文件目标。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-05-ptc-operation-metadata +baseline: false +changes: + - root: "event:tool/ptc-dispatch" + previous: "2026-09-16-session-format-v4" + after: "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5" + decision: same-version +``` + + +## 兼容性 + +没有 meta 的已有 tool/ptc-dispatch 记录仍然有效。可选 JsonValue 字段不改变程序读取的规范值、模型可见内容、工具标识或消息顺序。没有记录相对文件目标的历史调用仍安全地禁用导航。写入版本和已接受基线保持 V4。 + + +## 验证 + +963 个定向测试通过,六个受影响 runtime/UI 源文件逐文件覆盖率 100%;实际嵌套 shell 与相对文件操作回归、冻结元数据控制、TypeScript SDK 45 个测试、Python SDK 29 个测试及三个 owning keyless refresh/replay 通过。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml b/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml new file mode 100644 index 0000000000..1ae5d09e4f --- /dev/null +++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-05-working-directory-attribution.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-05-working-directory-attribution.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-05-working-directory-attribution: + en: 0dfc1c93be2577cb + zh: 3448816e53ae4bda +/2026-10-05-working-directory-attribution/summary: + en: 5e013c7f8b9e268c + zh: 134bc44b2081addc +/2026-10-05-working-directory-attribution/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-05-working-directory-attribution/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-05-working-directory-attribution/compatibility: + en: 39494aa3af994e6f + zh: 07c2880503668176 +/2026-10-05-working-directory-attribution/verification: + en: 5963942c0d87e518 + zh: 5dbb6ac0fca1f279 +/2026-10-05-working-directory-attribution/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.md b/docs/persistence-changes/2026-10-05-working-directory-attribution.md new file mode 100644 index 0000000000..80996eee5c --- /dev/null +++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.md @@ -0,0 +1,60 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-05-working-directory-attribution + +English | [中文](2026-10-05-working-directory-attribution.zh.md) + +## Summary + +Adds producer-owned working-directory notice attribution. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-05-working-directory-attribution +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-09-21-user-question-reply" + after: "9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0" + decision: same-version + - root: "event:developer/message" + previous: "2026-09-21-user-question-reply" + after: "ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-09-21-user-question-reply" + after: "19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd" + decision: same-version + - root: "event:user/message" + previous: "2026-09-21-user-question-reply" + after: "2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade" + decision: same-version +``` + + +## Compatibility + +Existing records remain valid. The additive working-directory source kind is attribution only; the declared source policy preserves unknown attribution without interpreting it. The Session format remains unchanged. + + +## Verification + +pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts: 4 files and 203 tests passed. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.schema.json b/docs/persistence-changes/2026-10-05-working-directory-attribution.schema.json new file mode 100644 index 0000000000..8b93756bfd --- /dev/null +++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.schema.json @@ -0,0 +1,11474 @@ +{ + "formatVersion": 2, + "roots": [ + { + "key": "event:agent/inbox/spliced", + "kind": "event", + "event": "agent/inbox/spliced", + "surface": false, + "digest": "9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 27, + "optional": true + }, + { + "name": "seq", + "type": 20, + "optional": false + }, + { + "name": "time", + "type": 20, + "optional": false + }, + { + "name": "type", + "type": 204, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "inserted", + "type": 2, + "optional": false + }, + { + "name": "outcome", + "type": 200, + "optional": true + }, + { + "name": "removedCount", + "type": 20, + "optional": true + }, + { + "name": "start", + "type": 20, + "optional": false + }, + { + "name": "target", + "type": 201, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 32, + "optional": false + }, + { + "name": "source", + "type": 33, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.user-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 14, + 16, + 18, + 29 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 12, + "optional": true + }, + { + "name": "toolName", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "offloaded", + "type": 27, + "optional": true + }, + { + "name": "type", + "type": 28, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 20, + "optional": false + }, + { + "name": "height", + "type": 20, + "optional": false + }, + { + "name": "mediaType", + "type": 21, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 26, + "optional": true + }, + { + "name": "width", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 22, + 23, + 24, + 25 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 20, + "optional": false + }, + { + "name": "width", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 30, + "optional": false + }, + { + "name": "type", + "type": 31, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 20, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 34, + 39, + 42, + 45, + 47, + 49, + 51, + 53, + 55, + 57, + 59, + 61, + 63, + 65, + 67, + 69, + 70, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 127, + 129, + 132, + 133, + 134, + 135, + 136, + 138, + 139, + 140, + 141, + 143, + 144, + 145, + 146, + 147, + 149, + 150, + 151, + 153, + 155, + 156, + 164, + 167, + 169, + 170, + 172, + 174, + 178, + 180, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "array", + "element": 38 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 41, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plan-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "runtime-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "schedule" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "time-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tmux-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 126, + "optional": false + }, + { + "name": "revision", + "type": 20, + "optional": false + }, + { + "name": "round", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 128, + "optional": false + }, + { + "name": "messageId", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 130, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 131, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 137, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 142, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 148, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 32, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 152, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 154, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "cordis-host-runner" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 27, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 7, + "optional": true + }, + { + "name": "changes", + "type": 157, + "optional": false + }, + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 163, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 158 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 159, + "optional": false + }, + { + "name": "digest", + "type": 7, + "optional": true + }, + { + "name": "path", + "type": 7, + "optional": false + }, + { + "name": "scope", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 160, + 161, + 162 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 165, + "optional": false + }, + { + "name": "outcome", + "type": 166, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-question-reply" + }, + { + "kind": "literal", + "value": "answered" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 168, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 7, + "optional": true + }, + { + "name": "kind", + "type": 32, + "optional": false + }, + { + "name": "rpcId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 171, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 7, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compact-checkpoint" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 7, + "optional": false + }, + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 173, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "ruleId", + "type": 7, + "optional": false + }, + { + "name": "source", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 175, + "optional": false + }, + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 177, + "optional": false + }, + { + "name": "update", + "type": 27, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 176 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 179, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 181, + "optional": false + }, + { + "name": "references", + "type": 182, + "optional": false + }, + { + "name": "version", + "type": 187, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 183 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 20, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 184, + "optional": false + }, + { + "name": "compacted", + "type": 186, + "optional": false + }, + { + "name": "inputIndex", + "type": 20, + "optional": false + }, + { + "name": "label", + "type": 7, + "optional": false + }, + { + "name": "omittedBytes", + "type": 20, + "optional": false + }, + { + "name": "omittedMessages", + "type": 20, + "optional": false + }, + { + "name": "originalMessages", + "type": 20, + "optional": false + }, + { + "name": "retainedMessages", + "type": 20, + "optional": false + }, + { + "name": "sessionId", + "type": 7, + "optional": false + }, + { + "name": "truncated", + "type": 186, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 20, + 185 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "canceled" + }, + { + "kind": "union", + "types": [ + 202, + 203 + ] + }, + { + "kind": "literal", + "value": "next-step" + }, + { + "kind": "literal", + "value": "next-turn" + }, + { + "kind": "literal", + "value": "agent/inbox/spliced" + } + ] + } + }, + { + "key": "event:developer/message", + "kind": "event", + "event": "developer/message", + "surface": true, + "digest": "ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 26, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 200, + "optional": true + }, + { + "name": "surfaceOp", + "type": 201, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 204, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "headerSeq", + "type": 2, + "optional": true + }, + { + "name": "message", + "type": 3, + "optional": false + }, + { + "name": "step", + "type": 2, + "optional": false + }, + { + "name": "turn", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 4, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "role", + "type": 31, + "optional": false + }, + { + "name": "source", + "type": 32, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.developer-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 9, + 11, + 14, + 16, + 18, + 28 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 12, + "optional": true + }, + { + "name": "toolName", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "type", + "type": 17, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 19, + "optional": false + }, + { + "name": "offloaded", + "type": 26, + "optional": true + }, + { + "name": "type", + "type": 27, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 20, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": true + }, + { + "name": "originalDimensions", + "type": 25, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 21, + 22, + 23, + 24 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 29, + "optional": false + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 7, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "developer" + }, + { + "kind": "union", + "types": [ + 33, + 38, + 41, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 69, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 126, + 128, + 131, + 132, + 133, + 134, + 135, + 136, + 138, + 139, + 140, + 141, + 143, + 144, + 145, + 146, + 147, + 149, + 151, + 152, + 154, + 156, + 164, + 167, + 169, + 170, + 172, + 174, + 178, + 180, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "array", + "element": 37 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plan-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "runtime-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "schedule" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "time-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tmux-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 125, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 127, + "optional": false + }, + { + "name": "messageId", + "type": 7, + "optional": false + }, + { + "name": "senderId", + "type": 7, + "optional": false + }, + { + "name": "senderName", + "type": 7, + "optional": false + }, + { + "name": "teamId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 129, + "optional": false + }, + { + "name": "model", + "type": 7, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "replayState", + "type": 130, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 45, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 47, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 51, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 137, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 142, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 148, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 150, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 153, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 155, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "cordis-host-runner" + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 26, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 7, + "optional": true + }, + { + "name": "changes", + "type": 157, + "optional": false + }, + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 163, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 158 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 159, + "optional": false + }, + { + "name": "digest", + "type": 7, + "optional": true + }, + { + "name": "path", + "type": 7, + "optional": false + }, + { + "name": "scope", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 160, + 161, + 162 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 165, + "optional": false + }, + { + "name": "outcome", + "type": 166, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-question-reply" + }, + { + "kind": "literal", + "value": "answered" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 168, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 7, + "optional": true + }, + { + "name": "kind", + "type": 150, + "optional": false + }, + { + "name": "rpcId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 7, + "optional": false + }, + { + "name": "kind", + "type": 171, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 7, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compact-checkpoint" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 7, + "optional": false + }, + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 173, + "optional": false + }, + { + "name": "provider", + "type": 7, + "optional": false + }, + { + "name": "ruleId", + "type": 7, + "optional": false + }, + { + "name": "source", + "type": 7, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 175, + "optional": false + }, + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 177, + "optional": false + }, + { + "name": "update", + "type": 26, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 176 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 7, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 179, + "optional": false + }, + { + "name": "name", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 181, + "optional": false + }, + { + "name": "references", + "type": 182, + "optional": false + }, + { + "name": "version", + "type": 187, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 183 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 184, + "optional": false + }, + { + "name": "compacted", + "type": 186, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 7, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 7, + "optional": false + }, + { + "name": "truncated", + "type": 186, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 185 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 53, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "union", + "types": [ + 202, + 203 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 2, + "optional": false + }, + { + "name": "op", + "type": 161, + "optional": false + }, + { + "name": "startSeq", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "developer/message" + } + ] + } + }, + { + "key": "event:session/title-llm-request", + "kind": "event", + "event": "session/title-llm-request", + "surface": false, + "digest": "19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 210, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": false + }, + { + "name": "messageSeqs", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "route", + "type": 209, + "optional": false + }, + { + "name": "system", + "type": 10, + "optional": false + }, + { + "name": "titleProvider", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 37, + 204, + 205, + 207 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "isError", + "type": 34, + "optional": true + }, + { + "name": "role", + "type": 35, + "optional": false + }, + { + "name": "source", + "type": 36, + "optional": false + }, + { + "name": "toolCallId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "union", + "types": [ + 9, + 12, + 14, + 17, + 19, + 21, + 31 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 15, + "optional": true + }, + { + "name": "toolName", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 10, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 22, + "optional": false + }, + { + "name": "offloaded", + "type": 29, + "optional": true + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 10, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 23, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": true + }, + { + "name": "originalDimensions", + "type": 28, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 24, + 25, + 26, + 27 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 32, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 10, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 38, + "optional": false + }, + { + "name": "source", + "type": 39, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.developer-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "developer" + }, + { + "kind": "union", + "types": [ + 36, + 40, + 45, + 47, + 49, + 51, + 53, + 55, + 56, + 58, + 60, + 62, + 63, + 65, + 67, + 69, + 71, + 72, + 73, + 76, + 79, + 81, + 83, + 92, + 95, + 97, + 98, + 100, + 102, + 104, + 109, + 111, + 119, + 120, + 121, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 191, + 192, + 194, + 197, + 198, + 199, + 200, + 201, + 203 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "tool-goal" + }, + { + "kind": "array", + "element": 44 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 10, + "optional": false + }, + { + "name": "text", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "runtime-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "schedule" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "time-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tmux-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "cordis-host-runner" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 75, + "optional": false + }, + { + "name": "senderSessionId", + "type": 10, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "senderSessionId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 29, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 10, + "optional": true + }, + { + "name": "changes", + "type": 84, + "optional": false + }, + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 91, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 85 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 86, + "optional": false + }, + { + "name": "digest", + "type": 10, + "optional": true + }, + { + "name": "path", + "type": 10, + "optional": false + }, + { + "name": "scope", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 87, + 88, + 89 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 93, + "optional": false + }, + { + "name": "outcome", + "type": 94, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-question-reply" + }, + { + "kind": "literal", + "value": "answered" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 10, + "optional": true + }, + { + "name": "kind", + "type": 61, + "optional": false + }, + { + "name": "rpcId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 99, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compact-checkpoint" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plan-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 10, + "optional": false + }, + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 103, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + }, + { + "name": "ruleId", + "type": 10, + "optional": false + }, + { + "name": "source", + "type": 10, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 105, + "optional": false + }, + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 108, + "optional": false + }, + { + "name": "update", + "type": 29, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 106 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 110, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 113, + "optional": false + }, + { + "name": "references", + "type": 114, + "optional": false + }, + { + "name": "version", + "type": 118, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 115 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 116, + "optional": false + }, + { + "name": "compacted", + "type": 34, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 10, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 10, + "optional": false + }, + { + "name": "truncated", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 117 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 190, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 193, + "optional": false + }, + { + "name": "messageId", + "type": 10, + "optional": false + }, + { + "name": "senderId", + "type": 10, + "optional": false + }, + { + "name": "senderName", + "type": 10, + "optional": false + }, + { + "name": "teamId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 195, + "optional": false + }, + { + "name": "model", + "type": 10, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + }, + { + "name": "replayState", + "type": 196, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 202, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 61, + "optional": false + }, + { + "name": "source", + "type": 39, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.user-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 206, + "optional": false + }, + { + "name": "source", + "type": 194, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 208, + "optional": false + }, + { + "name": "source", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 10, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + } + }, + { + "key": "event:user/message", + "kind": "event", + "event": "user/message", + "surface": true, + "digest": "2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 25, + "optional": true + }, + { + "name": "seq", + "type": 18, + "optional": false + }, + { + "name": "sourceEventSeqs", + "type": 198, + "optional": true + }, + { + "name": "surfaceOp", + "type": 199, + "optional": false + }, + { + "name": "time", + "type": 18, + "optional": false + }, + { + "name": "type", + "type": 202, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 2, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "role", + "type": 30, + "optional": false + }, + { + "name": "source", + "type": 31, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.user-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 3 + }, + { + "kind": "union", + "types": [ + 4, + 7, + 9, + 12, + 14, + 16, + 27 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 8, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 10, + "optional": true + }, + { + "name": "toolName", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 5, + "optional": false + }, + { + "name": "id", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "type", + "type": 15, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 17, + "optional": false + }, + { + "name": "offloaded", + "type": 25, + "optional": true + }, + { + "name": "type", + "type": 26, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 18, + "optional": false + }, + { + "name": "height", + "type": 18, + "optional": false + }, + { + "name": "mediaType", + "type": 19, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": true + }, + { + "name": "originalDimensions", + "type": 24, + "optional": true + }, + { + "name": "width", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "union", + "types": [ + 20, + 21, + 22, + 23 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 18, + "optional": false + }, + { + "name": "width", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 28, + "optional": false + }, + { + "name": "type", + "type": 29, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 5, + "optional": false + }, + { + "name": "bytes", + "type": 18, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "union", + "types": [ + 32, + 35, + 37, + 39, + 41, + 43, + 45, + 47, + 49, + 51, + 53, + 56, + 58, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 114, + 116, + 119, + 120, + 121, + 122, + 123, + 125, + 126, + 127, + 128, + 130, + 131, + 132, + 133, + 134, + 136, + 137, + 138, + 140, + 142, + 143, + 151, + 154, + 156, + 157, + 159, + 161, + 165, + 167, + 175, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 193, + 195, + 196, + 197 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "plan-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "runtime-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "schedule" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "time-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tmux-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 113, + "optional": false + }, + { + "name": "revision", + "type": 18, + "optional": false + }, + { + "name": "round", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 115, + "optional": false + }, + { + "name": "messageId", + "type": 5, + "optional": false + }, + { + "name": "senderId", + "type": 5, + "optional": false + }, + { + "name": "senderName", + "type": 5, + "optional": false + }, + { + "name": "teamId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 117, + "optional": false + }, + { + "name": "model", + "type": 5, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "replayState", + "type": 118, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 124, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 38, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 129, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 44, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 135, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 139, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 141, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "cordis-host-runner" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 25, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 5, + "optional": true + }, + { + "name": "changes", + "type": 144, + "optional": false + }, + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 150, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 145 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 146, + "optional": false + }, + { + "name": "digest", + "type": 5, + "optional": true + }, + { + "name": "path", + "type": 5, + "optional": false + }, + { + "name": "scope", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 147, + 148, + 149 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 152, + "optional": false + }, + { + "name": "outcome", + "type": 153, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-question-reply" + }, + { + "kind": "literal", + "value": "answered" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 155, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 5, + "optional": true + }, + { + "name": "kind", + "type": 30, + "optional": false + }, + { + "name": "rpcId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 5, + "optional": false + }, + { + "name": "kind", + "type": 158, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 5, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compact-checkpoint" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 5, + "optional": false + }, + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 160, + "optional": false + }, + { + "name": "provider", + "type": 5, + "optional": false + }, + { + "name": "ruleId", + "type": 5, + "optional": false + }, + { + "name": "source", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 162, + "optional": false + }, + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 164, + "optional": false + }, + { + "name": "update", + "type": 25, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 163 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 5, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 54, + "optional": false + }, + { + "name": "kind", + "type": 166, + "optional": false + }, + { + "name": "name", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 168, + "optional": false + }, + { + "name": "references", + "type": 169, + "optional": false + }, + { + "name": "version", + "type": 174, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 170 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 18, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 171, + "optional": false + }, + { + "name": "compacted", + "type": 173, + "optional": false + }, + { + "name": "inputIndex", + "type": 18, + "optional": false + }, + { + "name": "label", + "type": 5, + "optional": false + }, + { + "name": "omittedBytes", + "type": 18, + "optional": false + }, + { + "name": "omittedMessages", + "type": 18, + "optional": false + }, + { + "name": "originalMessages", + "type": 18, + "optional": false + }, + { + "name": "retainedMessages", + "type": 18, + "optional": false + }, + { + "name": "sessionId", + "type": 5, + "optional": false + }, + { + "name": "truncated", + "type": 173, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 18, + 172 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "array", + "element": 178 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 5, + "optional": false + }, + { + "name": "text", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 34, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "sections", + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 192, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 194, + "optional": false + }, + { + "name": "senderSessionId", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 18 + }, + { + "kind": "union", + "types": [ + 200, + 201 + ] + }, + { + "kind": "literal", + "value": "append" + }, + { + "kind": "object", + "properties": [ + { + "name": "endSeq", + "type": 18, + "optional": false + }, + { + "name": "op", + "type": 148, + "optional": false + }, + { + "name": "startSeq", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user/message" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md b/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md new file mode 100644 index 0000000000..e7ed00e08f --- /dev/null +++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md @@ -0,0 +1,60 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-05-working-directory-attribution + +[English](2026-10-05-working-directory-attribution.md) | 中文 + +## 概述 + +新增由工作目录组件拥有的通知来源标记。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-05-working-directory-attribution +baseline: false +changes: + - root: "event:agent/inbox/spliced" + previous: "2026-09-21-user-question-reply" + after: "9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0" + decision: same-version + - root: "event:developer/message" + previous: "2026-09-21-user-question-reply" + after: "ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06" + decision: same-version + - root: "event:session/title-llm-request" + previous: "2026-09-21-user-question-reply" + after: "19fab83e64cf84ee8a6caf62c592e85c9b195bfd19e84a2667c6693a395681fd" + decision: same-version + - root: "event:user/message" + previous: "2026-09-21-user-question-reply" + after: "2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade" + decision: same-version +``` + + +## 兼容性 + +已有记录仍然有效。新增的 working-directory 来源种类只提供来源标记;已声明的来源策略保留未知标记而不解释其含义。Session 格式保持不变。 + + +## 验证 + +pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts:4 个文件、203 个测试通过。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/2026-10-07-title-reasoning-effort.i18n.yaml b/docs/persistence-changes/2026-10-07-title-reasoning-effort.i18n.yaml new file mode 100644 index 0000000000..3ac60a790d --- /dev/null +++ b/docs/persistence-changes/2026-10-07-title-reasoning-effort.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-07-title-reasoning-effort.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/persistence-changes/2026-10-07-title-reasoning-effort.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-07-title-reasoning-effort: + en: 62238f237d762372 + zh: 67c868935122faba +/2026-10-07-title-reasoning-effort/summary: + en: a23a870e04888548 + zh: a794ea9022adf492 +/2026-10-07-title-reasoning-effort/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-07-title-reasoning-effort/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-07-title-reasoning-effort/compatibility: + en: 2aef689aa3af8d1c + zh: dc468f9d2c98d70c +/2026-10-07-title-reasoning-effort/verification: + en: 48ef9b977eaf074b + zh: b1183bda3a6a38be +/2026-10-07-title-reasoning-effort/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-07-title-reasoning-effort.md b/docs/persistence-changes/2026-10-07-title-reasoning-effort.md new file mode 100644 index 0000000000..658dac171d --- /dev/null +++ b/docs/persistence-changes/2026-10-07-title-reasoning-effort.md @@ -0,0 +1,48 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-07-title-reasoning-effort + +English | [中文](2026-10-07-title-reasoning-effort.zh.md) + +## Summary + +Records the concrete reasoning effort selected for auxiliary session-title requests. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-07-title-reasoning-effort +baseline: false +changes: + - root: "event:session/title-llm-request" + previous: "2026-10-05-working-directory-attribution" + after: "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6" + decision: same-version +``` + + +## Compatibility + +The new reasoningEffort field is optional. Existing V4 records remain valid and retain their original absence of a recorded effort. Prepared title requests write the resolved effort when the route exposes selectable reasoning; no Session format bump is required. + + +## Verification + +Focused LLM, adapter, title-provider, and replay tests passed: 50 files and 1960 tests. The session-title-after-turn SDK snapshot passed with a recorded low effort selected from a route whose default is high. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-07-title-reasoning-effort.schema.json b/docs/persistence-changes/2026-10-07-title-reasoning-effort.schema.json new file mode 100644 index 0000000000..06cad34dff --- /dev/null +++ b/docs/persistence-changes/2026-10-07-title-reasoning-effort.schema.json @@ -0,0 +1,2990 @@ +{ + "formatVersion": 2, + "roots": [ + { + "key": "event:session/title-llm-request", + "kind": "event", + "event": "session/title-llm-request", + "surface": false, + "digest": "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 29, + "optional": true + }, + { + "name": "seq", + "type": 2, + "optional": false + }, + { + "name": "time", + "type": 2, + "optional": false + }, + { + "name": "type", + "type": 210, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "maxTokens", + "type": 2, + "optional": false + }, + { + "name": "messageSeqs", + "type": 3, + "optional": false + }, + { + "name": "messages", + "type": 4, + "optional": false + }, + { + "name": "reasoningEffort", + "type": 10, + "optional": true + }, + { + "name": "route", + "type": 209, + "optional": false + }, + { + "name": "system", + "type": 10, + "optional": false + }, + { + "name": "titleProvider", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "array", + "element": 2 + }, + { + "kind": "array", + "element": 5 + }, + { + "kind": "union", + "types": [ + 6, + 37, + 204, + 205, + 207 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "isError", + "type": 34, + "optional": true + }, + { + "name": "role", + "type": 35, + "optional": false + }, + { + "name": "source", + "type": 36, + "optional": false + }, + { + "name": "toolCallId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 8 + }, + { + "kind": "union", + "types": [ + 9, + 12, + 14, + 17, + 19, + 21, + 31 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": "reasoning" + }, + { + "kind": "object", + "properties": [ + { + "name": "text", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 13, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "text" + }, + { + "kind": "object", + "properties": [ + { + "name": "tool", + "type": 15, + "optional": true + }, + { + "name": "toolName", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 16, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "never" + }, + { + "kind": "literal", + "value": "tool-addition" + }, + { + "kind": "object", + "properties": [ + { + "name": "toolName", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-removal" + }, + { + "kind": "object", + "properties": [ + { + "name": "arguments", + "type": 10, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + }, + { + "name": "type", + "type": 20, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-call" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 22, + "optional": false + }, + { + "name": "offloaded", + "type": 29, + "optional": true + }, + { + "name": "type", + "type": 30, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 10, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "mediaType", + "type": 23, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": true + }, + { + "name": "originalDimensions", + "type": 28, + "optional": true + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 24, + 25, + 26, + 27 + ] + }, + { + "kind": "literal", + "value": "image/gif" + }, + { + "kind": "literal", + "value": "image/jpeg" + }, + { + "kind": "literal", + "value": "image/png" + }, + { + "kind": "literal", + "value": "image/webp" + }, + { + "kind": "object", + "properties": [ + { + "name": "height", + "type": 2, + "optional": false + }, + { + "name": "width", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "literal", + "value": "image" + }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 32, + "optional": false + }, + { + "name": "type", + "type": 33, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 10, + "optional": false + }, + { + "name": "bytes", + "type": 2, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "file" + }, + { + "kind": "primitive", + "type": "boolean" + }, + { + "kind": "literal", + "value": "tool" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 38, + "optional": false + }, + { + "name": "source", + "type": 39, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.developer-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "developer" + }, + { + "kind": "union", + "types": [ + 36, + 40, + 45, + 47, + 49, + 51, + 53, + 55, + 56, + 58, + 60, + 62, + 63, + 65, + 67, + 69, + 71, + 72, + 73, + 76, + 79, + 81, + 83, + 92, + 95, + 97, + 98, + 100, + 102, + 104, + 109, + 111, + 119, + 120, + 121, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 191, + 192, + 194, + 197, + 198, + 199, + 200, + 201, + 203 + ] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "tool-goal" + }, + { + "kind": "array", + "element": 44 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 10, + "optional": false + }, + { + "name": "text", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "runtime-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "schedule" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 50, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "time-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tmux-context" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "cordis-host-runner" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 75, + "optional": false + }, + { + "name": "senderSessionId", + "type": 10, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "notice" + }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 78, + "optional": false + }, + { + "name": "senderSessionId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "baseline", + "type": 29, + "optional": true + }, + { + "name": "baselineIdentity", + "type": 10, + "optional": true + }, + { + "name": "changes", + "type": 84, + "optional": false + }, + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 91, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 85 + }, + { + "kind": "object", + "properties": [ + { + "name": "action", + "type": 86, + "optional": false + }, + { + "name": "digest", + "type": 10, + "optional": true + }, + { + "name": "path", + "type": 10, + "optional": false + }, + { + "name": "scope", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 87, + 88, + 89 + ] + }, + { + "kind": "literal", + "value": "remove" + }, + { + "kind": "literal", + "value": "replace" + }, + { + "kind": "literal", + "value": "set" + }, + { + "kind": "literal", + "value": "instructions" + }, + { + "kind": "literal", + "value": "agent-instructions" + }, + { + "kind": "object", + "properties": [ + { + "name": "callId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 93, + "optional": false + }, + { + "name": "outcome", + "type": 94, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-question-reply" + }, + { + "kind": "literal", + "value": "answered" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "clientTimeZone", + "type": 10, + "optional": true + }, + { + "name": "kind", + "type": 61, + "optional": false + }, + { + "name": "rpcId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "compactionId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 99, + "optional": false + }, + { + "name": "sourceCommandId", + "type": 10, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "compact-checkpoint" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plan-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "deliveryId", + "type": 10, + "optional": false + }, + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 103, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + }, + { + "name": "ruleId", + "type": 10, + "optional": false + }, + { + "name": "source", + "type": 10, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "webhook" + }, + { + "kind": "object", + "properties": [ + { + "name": "entries", + "type": 105, + "optional": false + }, + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 108, + "optional": false + }, + { + "name": "update", + "type": 29, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "array", + "element": 106 + }, + { + "kind": "object", + "properties": [ + { + "name": "description", + "type": 10, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "catalog" + }, + { + "kind": "literal", + "value": "skill-catalog" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 110, + "optional": false + }, + { + "name": "name", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "skill-invocation" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 113, + "optional": false + }, + { + "name": "references", + "type": 114, + "optional": false + }, + { + "name": "version", + "type": 118, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "recall" + }, + { + "kind": "literal", + "value": "session-reference" + }, + { + "kind": "array", + "element": 115 + }, + { + "kind": "object", + "properties": [ + { + "name": "capturedFormatVersion", + "type": 2, + "optional": true + }, + { + "name": "capturedThroughSeq", + "type": 116, + "optional": false + }, + { + "name": "compacted", + "type": 34, + "optional": false + }, + { + "name": "inputIndex", + "type": 2, + "optional": false + }, + { + "name": "label", + "type": 10, + "optional": false + }, + { + "name": "omittedBytes", + "type": 2, + "optional": false + }, + { + "name": "omittedMessages", + "type": 2, + "optional": false + }, + { + "name": "originalMessages", + "type": 2, + "optional": false + }, + { + "name": "retainedMessages", + "type": 2, + "optional": false + }, + { + "name": "sessionId", + "type": 10, + "optional": false + }, + { + "name": "truncated", + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "union", + "types": [ + 2, + 117 + ] + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "literal", + "value": 1 + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "goalId", + "type": 10, + "optional": false + }, + { + "name": "kind", + "type": 190, + "optional": false + }, + { + "name": "revision", + "type": 2, + "optional": false + }, + { + "name": "round", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "goal" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 193, + "optional": false + }, + { + "name": "messageId", + "type": 10, + "optional": false + }, + { + "name": "senderId", + "type": 10, + "optional": false + }, + { + "name": "senderName", + "type": 10, + "optional": false + }, + { + "name": "teamId", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "team-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 195, + "optional": false + }, + { + "name": "model", + "type": 10, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + }, + { + "name": "replayState", + "type": 196, + "optional": true + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model" + }, + { + "kind": "opaque", + "reason": "unknown" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 202, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 61, + "optional": false + }, + { + "name": "source", + "type": 39, + "optional": false, + "compatibility": { + "version": 1, + "policy": "session-source-attribution", + "binding": "session.user-message.source", + "discriminator": "kind", + "unknownKinds": "preserve", + "attributionKinds": [ + "tmux-context", + "user-question-reply", + "working-directory" + ] + } + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 206, + "optional": false + }, + { + "name": "source", + "type": 194, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "assistant" + }, + { + "kind": "object", + "properties": [ + { + "name": "content", + "type": 7, + "optional": false + }, + { + "name": "id", + "type": 10, + "optional": false + }, + { + "name": "role", + "type": 208, + "optional": false + }, + { + "name": "source", + "type": 49, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system" + }, + { + "kind": "object", + "properties": [ + { + "name": "model", + "type": 10, + "optional": false + }, + { + "name": "provider", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "session/title-llm-request" + } + ] + } + } + ], + "types": [] +} diff --git a/docs/persistence-changes/2026-10-07-title-reasoning-effort.zh.md b/docs/persistence-changes/2026-10-07-title-reasoning-effort.zh.md new file mode 100644 index 0000000000..39c05b5b52 --- /dev/null +++ b/docs/persistence-changes/2026-10-07-title-reasoning-effort.zh.md @@ -0,0 +1,48 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-07-title-reasoning-effort + +[English](2026-10-07-title-reasoning-effort.md) | 中文 + +## 概述 + +记录辅助会话标题请求选择的具体推理强度。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-07-title-reasoning-effort +baseline: false +changes: + - root: "event:session/title-llm-request" + previous: "2026-10-05-working-directory-attribution" + after: "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6" + decision: same-version +``` + + +## 兼容性 + +新增 reasoningEffort 字段为可选。现有 V4 记录仍有效,继续保留其未记录强度的事实。路由提供可选推理强度时,已准备的标题请求写入解析后的强度;无需提升 Session 格式版本。 + + +## 验证 + +LLM、适配器、标题提供方与回放的定向测试通过:50 个文件、1960 个测试。session-title-after-turn SDK 快照通过,记录从默认 high 的路由中选出的 low 强度。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-changes/README.i18n.yaml b/docs/persistence-changes/README.i18n.yaml index 92e983bcc1..30537db444 100644 --- a/docs/persistence-changes/README.i18n.yaml +++ b/docs/persistence-changes/README.i18n.yaml @@ -18,8 +18,8 @@ en: 6cd0f79a9455bdf2 zh: a5978b7834c0802b /persistence-type-change-records/compatibility-rules: - en: 7d5ce8c14e387922 - zh: 9106c78283ad932c + en: 62ce78d5618572b6 + zh: 023021d431f8a8e6 /persistence-type-change-records/history-and-limitations: en: 10a426d36c41548b zh: ef03b693d9d9be26 diff --git a/docs/persistence-changes/README.md b/docs/persistence-changes/README.md index 2f3d2dd50a..4f6cf773a9 100644 --- a/docs/persistence-changes/README.md +++ b/docs/persistence-changes/README.md @@ -53,14 +53,14 @@ Every detected structural change requires an acknowledgement. Record creation an | Add an optional event-body property, including its complete subtree | `same-version` | | Make a required event-body property optional | `same-version` | | Add an ordinary event type | `same-version` | -| Add a higher numeric `data.version` to an ordinary event while retaining every old payload alternative unchanged | `same-version` | +| Add a higher numeric `data.version` to an ordinary event while retaining every old payload alternative under the existing compatibility rules | `same-version` | | Add an explicitly qualified attribution kind to a user/developer source slot whose before and after schemas carry the same supported policy | `same-version` | | Make an optional property required, add a required property, change an existing type, or remove/rename a property or event | `version-bump` | | Change the Session header or event envelope | `version-bump` | A finalized checkpoint protects the accepted baseline without replacing these compatibility rules. At V4, optional additions, ordinary events, and qualified attribution additions can receive new same-version records. Breaking differences require a higher writer version and an acknowledgement containing its own header increase. The accepted V4 record cannot be updated to reuse its original 3→4 transition. -An ordinary event may add payload alternatives with a required, nonnegative integer `data.version` greater than every existing payload version, provided all existing alternatives remain structurally unchanged. The reader must retain support for the old payloads; adding variants at an existing version, dropping old versions, and changing the Session header or envelope remain breaking. Older readers may reject the new payload version. The [catalog acknowledgement](2026-09-20-unknown-child-catalog.md) records one such transition. +An ordinary event may add payload alternatives with a required, nonnegative integer `data.version` greater than every existing payload version, provided every existing alternative remains readable under the compatibility rules above. Optional property additions and required-to-optional changes may accompany the new version; each retained alternative is compared separately. The reader must retain support for the old payloads; adding variants at an existing version, dropping old versions, and changing the Session header or envelope remain breaking. Older readers may reject the new payload version. The [catalog acknowledgement](2026-09-20-unknown-child-catalog.md) records one such transition. The extractor accepts an explicit `@persistenceSource` binding for a core-owned source property on a literal user or developer role; it does not infer a binding from an unannotated type. A producer qualifies its `MessageSourceMap` entry with `@persistenceAttribution`. Qualification promises that an unknown kind and its JSON metadata survive reading without the producer, and that the kind imposes no validation, replay, or authority requirement. A producer may inspect its own kind to resume duplicate suppression; other readers must preserve and derive the recorded messages without that projection. The recorded schema retains the binding, policy version, literal `kind` discriminator, preservation promise, and qualified kind set. Inventory format 2 stores those promises; extraction without a bound policy retains format 1. Session format versions are independent. Both compared snapshots must carry compatible policy state. Existing kind groups still receive ordinary structural comparison; removals, unmarked additions, policy changes, and unrelated breaking changes remain strict. Multiple context-form alternatives with the same wire kind form one group. diff --git a/docs/persistence-changes/README.zh.md b/docs/persistence-changes/README.zh.md index d2cec933b2..5754ff07c1 100644 --- a/docs/persistence-changes/README.zh.md +++ b/docs/persistence-changes/README.zh.md @@ -53,14 +53,14 @@ description: "审阅和维护已记录的会话持久化类型变更、对应 sc | 添加可选事件体属性,包括其完整子树 | `same-version` | | 将必选事件体属性改为可选 | `same-version` | | 添加普通事件类型 | `same-version` | -| 为普通事件增加更高的数字 `data.version`,并原样保留所有旧载荷分支 | `same-version` | +| 为普通事件增加更高的数字 `data.version`,并按现有兼容性规则保留所有旧载荷分支 | `same-version` | | 在前后 schema 均带有相同受支持策略的 user/developer 消息源字段中,添加显式声明为归属信息的 kind | `same-version` | | 将可选属性改为必选、添加必选属性、更改已有类型,或删除/重命名属性或事件 | `version-bump` | | 更改会话头或事件封装 | `version-bump` | 定稿检查点保护已接受基线,不替换这些兼容性规则。在 V4 中,可选新增、普通事件及符合条件的归属 kind 新增可以使用新的同版本记录。破坏性差异要求更高的写入器版本,以及包含自身头版本递增的确认记录。不能更新已接受 V4 记录以复用其原有 3→4 转换。 -普通事件可增加具有必选、非负整数 `data.version` 的载荷分支,新版本必须高于所有已有载荷版本,且所有已有分支结构保持不变。读取器必须保留旧载荷支持;在已有版本中增加分支、移除旧版本,以及修改 Session header 或事件封装仍属于破坏性变更。旧读取器可能拒绝新载荷版本。[Catalog 确认记录](2026-09-20-unknown-child-catalog.zh.md) 记录了此类演进。 +普通事件可增加具有必选、非负整数 `data.version` 的载荷分支,新版本必须高于所有已有载荷版本,且所有已有分支按上述兼容性规则保持可读。可选属性新增和必选改可选可以与新版本一同引入;每个保留分支均独立比较。读取器必须保留旧载荷支持;在已有版本中增加分支、移除旧版本,以及修改 Session header 或事件封装仍属于破坏性变更。旧读取器可能拒绝新载荷版本。[Catalog 确认记录](2026-09-20-unknown-child-catalog.zh.md) 记录了此类演进。 提取器接受核心拥有的 source 属性上针对 user 或 developer 字面量角色的显式 `@persistenceSource` 绑定,不会从未标记类型中推断绑定。生产者用 `@persistenceAttribution` 标记其 `MessageSourceMap` 成员。该标记承诺:读取器无需生产者即可保留未知 kind 及其 JSON 元数据,且该 kind 不引入校验、回放或权限要求。生产者可以检查自身 kind 来恢复去重状态;其他读取器必须无需该投影也能保留并派生已记录的消息。记录的 schema 保存绑定、策略版本、字面量 `kind` 判别字段、保留承诺及符合条件的 kind 集合。Inventory format 2 保存这些承诺;没有绑定策略的提取仍使用 format 1。Session 格式版本独立于此。比较双方的快照必须带有兼容的策略状态。已有 kind 分组仍进行常规结构比较;删除、未标记的添加、策略更改及无关破坏性变更继续采用严格规则。同一 wire kind 的多个上下文形式分支归为一组。 diff --git a/docs/persistence-changes/historical-formats/README.i18n.yaml b/docs/persistence-changes/historical-formats/README.i18n.yaml index ec4d7e606a..1835c6fe96 100644 --- a/docs/persistence-changes/historical-formats/README.i18n.yaml +++ b/docs/persistence-changes/historical-formats/README.i18n.yaml @@ -15,8 +15,8 @@ en: 14c57f30e676f6e3 zh: b1a0ccf26fab3727 /session-persistence-formats/format-references: - en: 83e6e5c81eec05c3 - zh: 7f82a8b410361d4a + en: 73b4caa341293dda + zh: 305a83174f20c790 /session-persistence-formats/scope-and-evidence: en: b220062139ddac1e zh: 7c4c0efe05dafa9c diff --git a/docs/persistence-changes/historical-formats/README.md b/docs/persistence-changes/historical-formats/README.md index 8c09f40f98..1f0274c047 100644 --- a/docs/persistence-changes/historical-formats/README.md +++ b/docs/persistence-changes/historical-formats/README.md @@ -32,7 +32,7 @@ The index is generated from validated snapshots and the current writer constant. | 1 | PR #3349 | [V1](v1.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 64 / 603 | +| 4 | Current checkout | [Current catalog](../../persistence-catalog.md) | [JSON](../../persistence-schema.json) | 65 / 610 | diff --git a/docs/persistence-changes/historical-formats/README.zh.md b/docs/persistence-changes/historical-formats/README.zh.md index 0c9a15103c..1dd81855c5 100644 --- a/docs/persistence-changes/historical-formats/README.zh.md +++ b/docs/persistence-changes/historical-formats/README.zh.md @@ -32,7 +32,7 @@ description: "查找从 V0 到工作区写入器版本的每个 Session 格式 | 1 | PR #3349 | [V1](v1.zh.md) | [JSON](v1.schema.json) | 54 / 415 | | 2 | `dsh-v0.1.3-alpha.2` | [V2](v2.zh.md) | [JSON](v2.schema.json) | 56 / 435 | | 3 | PR #4320 | [V3](v3.zh.md) | [JSON](v3.schema.json) | 60 / 467 | -| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 64 / 603 | +| 4 | 当前工作树 | [当前目录](../../persistence-catalog.zh.md) | [JSON](../../persistence-schema.json) | 65 / 610 | diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index 3894584342..a24e399098 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -406,7 +406,7 @@ "kind": "event", "event": "agent/inbox/spliced", "surface": false, - "digest": "6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178", + "digest": "9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0", "schema": { "root": 0, "nodes": [ @@ -435,7 +435,7 @@ }, { "name": "type", - "type": 202, + "type": 204, "optional": false } ], @@ -451,7 +451,7 @@ }, { "name": "outcome", - "type": 198, + "type": 200, "optional": true }, { @@ -466,7 +466,7 @@ }, { "name": "target", - "type": 199, + "type": 201, "optional": false } ], @@ -506,7 +506,8 @@ "unknownKinds": "preserve", "attributionKinds": [ "tmux-context", - "user-question-reply" + "user-question-reply", + "working-directory" ] } } @@ -816,29 +817,24 @@ "kind": "union", "types": [ 34, - 37, 39, - 41, - 43, + 42, 45, 47, 49, 51, 53, 55, - 58, - 60, - 62, + 57, + 59, + 61, 63, - 64, 65, - 66, 67, - 68, 69, 70, - 71, 72, + 73, 74, 75, 76, @@ -850,8 +846,8 @@ 82, 83, 84, - 85, 86, + 87, 88, 89, 90, @@ -865,6 +861,7 @@ 98, 99, 100, + 101, 102, 103, 104, @@ -875,20 +872,21 @@ 109, 110, 111, - 112, 113, 114, + 115, 116, + 117, 118, + 119, + 120, 121, 122, 123, 124, 125, 127, - 128, 129, - 130, 132, 133, 134, @@ -897,34 +895,38 @@ 138, 139, 140, - 142, + 141, 143, + 144, + 145, + 146, + 147, + 149, + 150, 151, - 154, + 153, + 155, 156, - 157, - 159, - 161, - 165, + 164, 167, - 175, - 179, + 169, + 170, + 172, + 174, + 178, 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, 188, 189, 190, 191, + 192, 193, + 194, 195, 196, - 197 + 197, + 198, + 199 ] }, { @@ -940,6 +942,60 @@ "type": 36, "optional": false }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "array", + "element": 38 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 41, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, { "name": "summary", "type": 7, @@ -952,6 +1008,135 @@ "kind": "literal", "value": "notice" }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 44, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 50, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 40, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "plan-mode" @@ -961,12 +1146,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 38, + "type": 54, "optional": false }, { @@ -986,12 +1171,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 40, + "type": 56, "optional": false }, { @@ -1011,12 +1196,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 42, + "type": 58, "optional": false }, { @@ -1036,12 +1221,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 44, + "type": 60, "optional": false }, { @@ -1061,12 +1246,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 46, + "type": 62, "optional": false }, { @@ -1086,12 +1271,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 48, + "type": 64, "optional": false }, { @@ -1111,12 +1296,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 50, + "type": 66, "optional": false }, { @@ -1136,12 +1321,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 52, + "type": 68, "optional": false }, { @@ -1161,12 +1346,12 @@ "properties": [ { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 54, + "type": 36, "optional": false }, { @@ -1177,160 +1362,12 @@ ], "indices": [] }, - { - "kind": "literal", - "value": "dsh-session-title-llm" - }, { "kind": "object", "properties": [ { "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 57, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "instructions" - }, - { - "kind": "literal", - "value": "hooks-claude-code" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 59, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "hooks-codex" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 61, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "model-selection" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 36, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 38, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, - "optional": false - }, - { - "name": "kind", - "type": 44, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 56, + "type": 71, "optional": false }, { @@ -1341,12 +1378,16 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "instructions" + }, { "kind": "object", "properties": [ { "name": "form", - "type": 56, + "type": 71, "optional": false }, { @@ -1362,7 +1403,7 @@ "properties": [ { "name": "form", - "type": 56, + "type": 71, "optional": false }, { @@ -1378,7 +1419,7 @@ "properties": [ { "name": "form", - "type": 56, + "type": 71, "optional": false }, { @@ -1394,7 +1435,7 @@ "properties": [ { "name": "form", - "type": 56, + "type": 71, "optional": false }, { @@ -1410,12 +1451,140 @@ "properties": [ { "name": "form", - "type": 73, + "type": 71, "optional": false }, { "name": "kind", - "type": 57, + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 46, "optional": false } ], @@ -1430,135 +1599,7 @@ "properties": [ { "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 59, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 61, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 36, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 38, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 44, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, - "optional": false - }, - { - "name": "kind", - "type": 46, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 73, + "type": 85, "optional": false }, { @@ -1574,7 +1615,7 @@ "properties": [ { "name": "form", - "type": 73, + "type": 85, "optional": false }, { @@ -1590,7 +1631,7 @@ "properties": [ { "name": "form", - "type": 73, + "type": 85, "optional": false }, { @@ -1606,7 +1647,7 @@ "properties": [ { "name": "form", - "type": 73, + "type": 85, "optional": false }, { @@ -1622,32 +1663,12 @@ "properties": [ { "name": "form", - "type": 87, + "type": 85, "optional": false }, { "name": "kind", - "type": 57, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "relay" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 59, + "type": 56, "optional": false } ], @@ -1658,12 +1679,12 @@ "properties": [ { "name": "form", - "type": 87, + "type": 85, "optional": false }, { "name": "kind", - "type": 61, + "type": 58, "optional": false } ], @@ -1674,7 +1695,87 @@ "properties": [ { "name": "form", - "type": 87, + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, "optional": false }, { @@ -1690,71 +1791,7 @@ "properties": [ { "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 38, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 44, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, + "type": 43, "optional": false }, { @@ -1770,7 +1807,7 @@ "properties": [ { "name": "form", - "type": 87, + "type": 43, "optional": false }, { @@ -1786,7 +1823,7 @@ "properties": [ { "name": "form", - "type": 87, + "type": 43, "optional": false }, { @@ -1802,7 +1839,7 @@ "properties": [ { "name": "form", - "type": 87, + "type": 43, "optional": false }, { @@ -1818,7 +1855,7 @@ "properties": [ { "name": "form", - "type": 87, + "type": 43, "optional": false }, { @@ -1834,12 +1871,140 @@ "properties": [ { "name": "form", - "type": 101, + "type": 43, "optional": false }, { "name": "kind", - "type": 57, + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 43, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 46, "optional": false } ], @@ -1854,135 +2019,7 @@ "properties": [ { "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 59, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 61, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 36, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 38, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 44, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, - "optional": false - }, - { - "name": "kind", - "type": 46, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 101, + "type": 112, "optional": false }, { @@ -1998,7 +2035,7 @@ "properties": [ { "name": "form", - "type": 101, + "type": 112, "optional": false }, { @@ -2014,7 +2051,7 @@ "properties": [ { "name": "form", - "type": 101, + "type": 112, "optional": false }, { @@ -2030,7 +2067,7 @@ "properties": [ { "name": "form", - "type": 101, + "type": 112, "optional": false }, { @@ -2041,6 +2078,134 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 36, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -2051,7 +2216,7 @@ }, { "name": "kind", - "type": 115, + "type": 126, "optional": false }, { @@ -2076,7 +2241,7 @@ "properties": [ { "name": "kind", - "type": 117, + "type": 128, "optional": false }, { @@ -2111,7 +2276,7 @@ "properties": [ { "name": "kind", - "type": 119, + "type": 130, "optional": false }, { @@ -2126,7 +2291,7 @@ }, { "name": "replayState", - "type": 120, + "type": 131, "optional": true } ], @@ -2140,124 +2305,6 @@ "kind": "opaque", "reason": "unknown" }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 57, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 59, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 61, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 36, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 126, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "ptc-mode" - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 38, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 131, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "system-prompt" - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 44, - "optional": false - } - ], - "indices": [] - }, { "kind": "object", "properties": [ @@ -2291,6 +2338,17 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -2302,6 +2360,113 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 56, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 58, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 142, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 60, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 62, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 148, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "tool-registry" @@ -2322,7 +2487,7 @@ "properties": [ { "name": "kind", - "type": 52, + "type": 68, "optional": false } ], @@ -2333,7 +2498,22 @@ "properties": [ { "name": "kind", - "type": 141, + "type": 152, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 154, "optional": false } ], @@ -2348,7 +2528,7 @@ "properties": [ { "name": "kind", - "type": 54, + "type": 36, "optional": false } ], @@ -2369,17 +2549,17 @@ }, { "name": "changes", - "type": 144, + "type": 157, "optional": false }, { "name": "form", - "type": 56, + "type": 71, "optional": false }, { "name": "kind", - "type": 150, + "type": 163, "optional": false } ], @@ -2387,14 +2567,14 @@ }, { "kind": "array", - "element": 145 + "element": 158 }, { "kind": "object", "properties": [ { "name": "action", - "type": 146, + "type": 159, "optional": false }, { @@ -2418,9 +2598,9 @@ { "kind": "union", "types": [ - 147, - 148, - 149 + 160, + 161, + 162 ] }, { @@ -2449,12 +2629,12 @@ }, { "name": "kind", - "type": 152, + "type": 165, "optional": false }, { "name": "outcome", - "type": 153, + "type": 166, "optional": false } ], @@ -2478,7 +2658,7 @@ }, { "name": "kind", - "type": 155, + "type": 168, "optional": false } ], @@ -2519,7 +2699,7 @@ }, { "name": "kind", - "type": 158, + "type": 171, "optional": false }, { @@ -2544,12 +2724,12 @@ }, { "name": "form", - "type": 35, + "type": 40, "optional": false }, { "name": "kind", - "type": 160, + "type": 173, "optional": false }, { @@ -2584,17 +2764,17 @@ "properties": [ { "name": "entries", - "type": 162, + "type": 175, "optional": false }, { "name": "form", - "type": 101, + "type": 112, "optional": false }, { "name": "kind", - "type": 164, + "type": 177, "optional": false }, { @@ -2607,7 +2787,7 @@ }, { "kind": "array", - "element": 163 + "element": 176 }, { "kind": "object", @@ -2634,12 +2814,12 @@ "properties": [ { "name": "form", - "type": 56, + "type": 71, "optional": false }, { "name": "kind", - "type": 166, + "type": 179, "optional": false }, { @@ -2659,22 +2839,22 @@ "properties": [ { "name": "form", - "type": 73, + "type": 85, "optional": false }, { "name": "kind", - "type": 168, + "type": 181, "optional": false }, { "name": "references", - "type": 169, + "type": 182, "optional": false }, { "name": "version", - "type": 174, + "type": 187, "optional": false } ], @@ -2686,7 +2866,7 @@ }, { "kind": "array", - "element": 170 + "element": 183 }, { "kind": "object", @@ -2698,12 +2878,12 @@ }, { "name": "capturedThroughSeq", - "type": 171, + "type": 184, "optional": false }, { "name": "compacted", - "type": 173, + "type": 186, "optional": false }, { @@ -2743,7 +2923,7 @@ }, { "name": "truncated", - "type": 173, + "type": 186, "optional": false } ], @@ -2753,7 +2933,7 @@ "kind": "union", "types": [ 20, - 172 + 185 ] }, { @@ -2773,199 +2953,7 @@ "properties": [ { "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 57, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "snapshot" - }, - { - "kind": "array", - "element": 178 - }, - { - "kind": "object", - "properties": [ - { - "name": "name", - "type": 7, - "optional": false - }, - { - "name": "text", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 59, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 61, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 36, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 38, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 40, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 44, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, + "type": 35, "optional": false }, { @@ -2975,7 +2963,7 @@ }, { "name": "sections", - "type": 177, + "type": 37, "optional": false } ], @@ -2986,7 +2974,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 35, "optional": false }, { @@ -2996,7 +2984,7 @@ }, { "name": "sections", - "type": 177, + "type": 37, "optional": false } ], @@ -3007,7 +2995,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 35, "optional": false }, { @@ -3017,7 +3005,7 @@ }, { "name": "sections", - "type": 177, + "type": 37, "optional": false } ], @@ -3028,7 +3016,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 35, "optional": false }, { @@ -3038,7 +3026,7 @@ }, { "name": "sections", - "type": 177, + "type": 37, "optional": false } ], @@ -3049,7 +3037,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 35, "optional": false }, { @@ -3059,7 +3047,7 @@ }, { "name": "sections", - "type": 177, + "type": 37, "optional": false } ], @@ -3075,67 +3063,12 @@ }, { "name": "kind", - "type": 192, + "type": 56, "optional": false }, { - "name": "senderSessionId", - "type": 7, - "optional": false - }, - { - "name": "summary", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "subagent-settled" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 87, - "optional": false - }, - { - "name": "kind", - "type": 194, - "optional": false - }, - { - "name": "senderSessionId", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "agent-message" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 35, - "optional": false - }, - { - "name": "kind", - "type": 57, - "optional": false - }, - { - "name": "summary", - "type": 7, + "name": "sections", + "type": 37, "optional": false } ], @@ -3151,12 +3084,12 @@ }, { "name": "kind", - "type": 59, + "type": 58, "optional": false }, { - "name": "summary", - "type": 7, + "name": "sections", + "type": 37, "optional": false } ], @@ -3172,12 +3105,96 @@ }, { "name": "kind", - "type": 61, + "type": 60, "optional": false }, { - "name": "summary", - "type": 7, + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 62, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 66, + "optional": false + }, + { + "name": "sections", + "type": 37, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 35, + "optional": false + }, + { + "name": "kind", + "type": 68, + "optional": false + }, + { + "name": "sections", + "type": 37, "optional": false } ], @@ -3190,8 +3207,8 @@ { "kind": "union", "types": [ - 200, - 201 + 202, + 203 ] }, { @@ -6554,7 +6571,7 @@ "kind": "event", "event": "developer/message", "surface": true, - "digest": "186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467", + "digest": "ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06", "schema": { "root": 0, "nodes": [ @@ -6578,12 +6595,12 @@ }, { "name": "sourceEventSeqs", - "type": 198, + "type": 200, "optional": true }, { "name": "surfaceOp", - "type": 199, + "type": 201, "optional": false }, { @@ -6593,7 +6610,7 @@ }, { "name": "type", - "type": 202, + "type": 204, "optional": false } ], @@ -6659,7 +6676,8 @@ "unknownKinds": "preserve", "attributionKinds": [ "tmux-context", - "user-question-reply" + "user-question-reply", + "working-directory" ] } } @@ -6965,29 +6983,24 @@ "kind": "union", "types": [ 33, - 36, 38, - 40, - 42, + 41, 44, 46, 48, 50, 52, 54, - 57, - 59, - 61, + 56, + 58, + 60, 62, - 63, 64, - 65, 66, - 67, 68, 69, - 70, 71, + 72, 73, 74, 75, @@ -6999,8 +7012,8 @@ 81, 82, 83, - 84, 85, + 86, 87, 88, 89, @@ -7014,6 +7027,7 @@ 97, 98, 99, + 100, 101, 102, 103, @@ -7024,56 +7038,61 @@ 108, 109, 110, - 111, 112, 113, + 114, 115, + 116, 117, + 118, + 119, 120, 121, 122, 123, 124, - 125, - 127, + 126, 128, - 129, - 130, + 131, 132, 133, 134, 135, 136, 138, + 139, 140, 141, 143, + 144, + 145, + 146, + 147, + 149, 151, + 152, 154, 156, - 157, - 159, - 161, - 165, + 164, 167, - 175, - 179, + 169, + 170, + 172, + 174, + 178, 180, - 181, - 182, - 183, - 184, - 185, - 186, - 187, 188, 189, 190, 191, + 192, 193, + 194, 195, 196, - 197 + 197, + 198, + 199 ] }, { @@ -7089,6 +7108,60 @@ "type": 35, "optional": false }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "snapshot" + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "array", + "element": 37 + }, + { + "kind": "object", + "properties": [ + { + "name": "name", + "type": 7, + "optional": false + }, + { + "name": "text", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 40, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + }, { "name": "summary", "type": 7, @@ -7101,6 +7174,135 @@ "kind": "literal", "value": "notice" }, + { + "kind": "literal", + "value": "subagent-settled" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 43, + "optional": false + }, + { + "name": "senderSessionId", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "relay" + }, + { + "kind": "literal", + "value": "agent-message" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 45, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "dsh-session-title-llm" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 47, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-claude-code" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 49, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "hooks-codex" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 39, + "optional": false + }, + { + "name": "kind", + "type": 51, + "optional": false + }, + { + "name": "summary", + "type": 7, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "model-selection" @@ -7110,12 +7312,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 37, + "type": 53, "optional": false }, { @@ -7135,12 +7337,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 39, + "type": 55, "optional": false }, { @@ -7160,12 +7362,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 41, + "type": 57, "optional": false }, { @@ -7185,12 +7387,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 43, + "type": 59, "optional": false }, { @@ -7210,12 +7412,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 45, + "type": 61, "optional": false }, { @@ -7235,12 +7437,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 47, + "type": 63, "optional": false }, { @@ -7260,12 +7462,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 49, + "type": 65, "optional": false }, { @@ -7285,12 +7487,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 51, + "type": 67, "optional": false }, { @@ -7310,12 +7512,12 @@ "properties": [ { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 53, + "type": 35, "optional": false }, { @@ -7326,160 +7528,12 @@ ], "indices": [] }, - { - "kind": "literal", - "value": "user-approval" - }, { "kind": "object", "properties": [ { "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 56, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "instructions" - }, - { - "kind": "literal", - "value": "dsh-session-title-llm" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 58, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "hooks-claude-code" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 60, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "hooks-codex" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 35, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 37, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 39, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 41, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, - "optional": false - }, - { - "name": "kind", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 55, + "type": 70, "optional": false }, { @@ -7490,12 +7544,16 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "instructions" + }, { "kind": "object", "properties": [ { "name": "form", - "type": 55, + "type": 70, "optional": false }, { @@ -7511,7 +7569,7 @@ "properties": [ { "name": "form", - "type": 55, + "type": 70, "optional": false }, { @@ -7527,7 +7585,7 @@ "properties": [ { "name": "form", - "type": 55, + "type": 70, "optional": false }, { @@ -7543,7 +7601,7 @@ "properties": [ { "name": "form", - "type": 55, + "type": 70, "optional": false }, { @@ -7559,12 +7617,140 @@ "properties": [ { "name": "form", - "type": 72, + "type": 70, "optional": false }, { "name": "kind", - "type": 56, + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 70, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 45, "optional": false } ], @@ -7579,135 +7765,7 @@ "properties": [ { "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 58, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 60, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 35, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 37, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 39, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 41, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, - "optional": false - }, - { - "name": "kind", - "type": 45, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 72, + "type": 84, "optional": false }, { @@ -7723,7 +7781,7 @@ "properties": [ { "name": "form", - "type": 72, + "type": 84, "optional": false }, { @@ -7739,7 +7797,7 @@ "properties": [ { "name": "form", - "type": 72, + "type": 84, "optional": false }, { @@ -7755,7 +7813,7 @@ "properties": [ { "name": "form", - "type": 72, + "type": 84, "optional": false }, { @@ -7771,32 +7829,12 @@ "properties": [ { "name": "form", - "type": 86, + "type": 84, "optional": false }, { "name": "kind", - "type": 56, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "relay" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 58, + "type": 55, "optional": false } ], @@ -7807,12 +7845,12 @@ "properties": [ { "name": "form", - "type": 86, + "type": 84, "optional": false }, { "name": "kind", - "type": 60, + "type": 57, "optional": false } ], @@ -7823,7 +7861,87 @@ "properties": [ { "name": "form", - "type": 86, + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 84, "optional": false }, { @@ -7839,71 +7957,7 @@ "properties": [ { "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 37, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 39, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 41, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, + "type": 42, "optional": false }, { @@ -7919,7 +7973,7 @@ "properties": [ { "name": "form", - "type": 86, + "type": 42, "optional": false }, { @@ -7935,7 +7989,7 @@ "properties": [ { "name": "form", - "type": 86, + "type": 42, "optional": false }, { @@ -7951,7 +8005,7 @@ "properties": [ { "name": "form", - "type": 86, + "type": 42, "optional": false }, { @@ -7967,7 +8021,7 @@ "properties": [ { "name": "form", - "type": 86, + "type": 42, "optional": false }, { @@ -7983,12 +8037,140 @@ "properties": [ { "name": "form", - "type": 100, + "type": 42, "optional": false }, { "name": "kind", - "type": 56, + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 42, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 45, "optional": false } ], @@ -8003,135 +8185,7 @@ "properties": [ { "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 58, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 60, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 35, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 37, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 39, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 41, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, - "optional": false - }, - { - "name": "kind", - "type": 45, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 100, + "type": 111, "optional": false }, { @@ -8147,7 +8201,7 @@ "properties": [ { "name": "form", - "type": 100, + "type": 111, "optional": false }, { @@ -8163,7 +8217,7 @@ "properties": [ { "name": "form", - "type": 100, + "type": 111, "optional": false }, { @@ -8179,7 +8233,7 @@ "properties": [ { "name": "form", - "type": 100, + "type": 111, "optional": false }, { @@ -8190,6 +8244,134 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 111, + "optional": false + }, + { + "name": "kind", + "type": 35, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -8200,7 +8382,7 @@ }, { "name": "kind", - "type": 114, + "type": 125, "optional": false }, { @@ -8225,7 +8407,7 @@ "properties": [ { "name": "kind", - "type": 116, + "type": 127, "optional": false }, { @@ -8260,7 +8442,7 @@ "properties": [ { "name": "kind", - "type": 118, + "type": 129, "optional": false }, { @@ -8275,7 +8457,7 @@ }, { "name": "replayState", - "type": 119, + "type": 130, "optional": true } ], @@ -8289,124 +8471,6 @@ "kind": "opaque", "reason": "unknown" }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 56, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 58, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 60, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 35, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 37, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 126, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "ptc-mode" - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 39, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 41, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 131, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "system-prompt" - }, { "kind": "object", "properties": [ @@ -8451,6 +8515,17 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 53, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -8462,6 +8537,113 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "ptc-mode" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 55, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 142, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "system-prompt" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 61, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 63, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 65, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 67, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 148, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "tool-registry" @@ -8471,7 +8653,7 @@ "properties": [ { "name": "kind", - "type": 139, + "type": 150, "optional": false } ], @@ -8486,7 +8668,7 @@ "properties": [ { "name": "kind", - "type": 53, + "type": 35, "optional": false } ], @@ -8497,7 +8679,22 @@ "properties": [ { "name": "kind", - "type": 142, + "type": 153, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 155, "optional": false } ], @@ -8522,17 +8719,17 @@ }, { "name": "changes", - "type": 144, + "type": 157, "optional": false }, { "name": "form", - "type": 55, + "type": 70, "optional": false }, { "name": "kind", - "type": 150, + "type": 163, "optional": false } ], @@ -8540,14 +8737,14 @@ }, { "kind": "array", - "element": 145 + "element": 158 }, { "kind": "object", "properties": [ { "name": "action", - "type": 146, + "type": 159, "optional": false }, { @@ -8571,9 +8768,9 @@ { "kind": "union", "types": [ - 147, - 148, - 149 + 160, + 161, + 162 ] }, { @@ -8602,12 +8799,12 @@ }, { "name": "kind", - "type": 152, + "type": 165, "optional": false }, { "name": "outcome", - "type": 153, + "type": 166, "optional": false } ], @@ -8631,7 +8828,7 @@ }, { "name": "kind", - "type": 155, + "type": 168, "optional": false } ], @@ -8651,7 +8848,7 @@ }, { "name": "kind", - "type": 139, + "type": 150, "optional": false }, { @@ -8672,7 +8869,7 @@ }, { "name": "kind", - "type": 158, + "type": 171, "optional": false }, { @@ -8697,12 +8894,12 @@ }, { "name": "form", - "type": 34, + "type": 39, "optional": false }, { "name": "kind", - "type": 160, + "type": 173, "optional": false }, { @@ -8737,17 +8934,17 @@ "properties": [ { "name": "entries", - "type": 162, + "type": 175, "optional": false }, { "name": "form", - "type": 100, + "type": 111, "optional": false }, { "name": "kind", - "type": 164, + "type": 177, "optional": false }, { @@ -8760,7 +8957,7 @@ }, { "kind": "array", - "element": 163 + "element": 176 }, { "kind": "object", @@ -8787,12 +8984,12 @@ "properties": [ { "name": "form", - "type": 55, + "type": 70, "optional": false }, { "name": "kind", - "type": 166, + "type": 179, "optional": false }, { @@ -8812,22 +9009,22 @@ "properties": [ { "name": "form", - "type": 72, + "type": 84, "optional": false }, { "name": "kind", - "type": 168, + "type": 181, "optional": false }, { "name": "references", - "type": 169, + "type": 182, "optional": false }, { "name": "version", - "type": 174, + "type": 187, "optional": false } ], @@ -8839,7 +9036,7 @@ }, { "kind": "array", - "element": 170 + "element": 183 }, { "kind": "object", @@ -8851,12 +9048,12 @@ }, { "name": "capturedThroughSeq", - "type": 171, + "type": 184, "optional": false }, { "name": "compacted", - "type": 173, + "type": 186, "optional": false }, { @@ -8896,7 +9093,7 @@ }, { "name": "truncated", - "type": 173, + "type": 186, "optional": false } ], @@ -8906,7 +9103,7 @@ "kind": "union", "types": [ 2, - 172 + 185 ] }, { @@ -8926,199 +9123,7 @@ "properties": [ { "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 56, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "snapshot" - }, - { - "kind": "array", - "element": 178 - }, - { - "kind": "object", - "properties": [ - { - "name": "name", - "type": 7, - "optional": false - }, - { - "name": "text", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 58, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 60, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 35, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 37, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 39, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 41, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, - "optional": false - }, - { - "name": "kind", - "type": 43, - "optional": false - }, - { - "name": "sections", - "type": 177, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 176, + "type": 34, "optional": false }, { @@ -9128,7 +9133,7 @@ }, { "name": "sections", - "type": 177, + "type": 36, "optional": false } ], @@ -9139,7 +9144,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 34, "optional": false }, { @@ -9149,7 +9154,7 @@ }, { "name": "sections", - "type": 177, + "type": 36, "optional": false } ], @@ -9160,7 +9165,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 34, "optional": false }, { @@ -9170,7 +9175,7 @@ }, { "name": "sections", - "type": 177, + "type": 36, "optional": false } ], @@ -9181,7 +9186,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 34, "optional": false }, { @@ -9191,7 +9196,7 @@ }, { "name": "sections", - "type": 177, + "type": 36, "optional": false } ], @@ -9202,7 +9207,7 @@ "properties": [ { "name": "form", - "type": 176, + "type": 34, "optional": false }, { @@ -9212,7 +9217,7 @@ }, { "name": "sections", - "type": 177, + "type": 36, "optional": false } ], @@ -9228,67 +9233,12 @@ }, { "name": "kind", - "type": 192, + "type": 55, "optional": false }, { - "name": "senderSessionId", - "type": 7, - "optional": false - }, - { - "name": "summary", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "subagent-settled" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 194, - "optional": false - }, - { - "name": "senderSessionId", - "type": 7, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "agent-message" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 34, - "optional": false - }, - { - "name": "kind", - "type": 56, - "optional": false - }, - { - "name": "summary", - "type": 7, + "name": "sections", + "type": 36, "optional": false } ], @@ -9304,12 +9254,12 @@ }, { "name": "kind", - "type": 58, + "type": 57, "optional": false }, { - "name": "summary", - "type": 7, + "name": "sections", + "type": 36, "optional": false } ], @@ -9325,12 +9275,96 @@ }, { "name": "kind", - "type": 60, + "type": 59, "optional": false }, { - "name": "summary", - "type": 7, + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 61, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 63, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 65, + "optional": false + }, + { + "name": "sections", + "type": 36, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 34, + "optional": false + }, + { + "name": "kind", + "type": 67, + "optional": false + }, + { + "name": "sections", + "type": 36, "optional": false } ], @@ -9343,8 +9377,8 @@ { "kind": "union", "types": [ - 200, - 201 + 202, + 203 ] }, { @@ -9361,7 +9395,7 @@ }, { "name": "op", - "type": 148, + "type": 161, "optional": false }, { @@ -12157,7 +12191,7 @@ "kind": "event", "event": "session/title-llm-request", "surface": false, - "digest": "ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46", + "digest": "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6", "schema": { "root": 0, "nodes": [ @@ -12186,7 +12220,7 @@ }, { "name": "type", - "type": 208, + "type": 210, "optional": false } ], @@ -12210,9 +12244,14 @@ "type": 4, "optional": false }, + { + "name": "reasoningEffort", + "type": 10, + "optional": true + }, { "name": "route", - "type": 207, + "type": 209, "optional": false }, { @@ -12245,9 +12284,9 @@ "types": [ 6, 37, - 202, - 203, - 205 + 204, + 205, + 207 ] }, { @@ -12631,7 +12670,8 @@ "unknownKinds": "preserve", "attributionKinds": [ "tmux-context", - "user-question-reply" + "user-question-reply", + "working-directory" ] } } @@ -12653,34 +12693,33 @@ 51, 53, 55, - 57, - 59, + 56, + 58, 60, 62, - 64, + 63, 65, - 68, + 67, + 69, 71, + 72, 73, - 75, - 77, + 76, 79, - 88, - 91, + 81, + 83, 92, - 94, - 96, + 95, + 97, 98, 100, - 105, - 107, - 115, - 116, - 117, - 118, + 102, + 104, + 109, + 111, 119, 120, - 122, + 121, 123, 124, 125, @@ -12744,17 +12783,19 @@ 183, 184, 185, + 186, 187, + 188, 189, + 191, 192, - 193, 194, - 195, - 196, 197, + 198, 199, 200, - 201 + 201, + 203 ] }, { @@ -12784,7 +12825,7 @@ }, { "kind": "literal", - "value": "user-approval" + "value": "tool-goal" }, { "kind": "array", @@ -12819,7 +12860,7 @@ }, { "kind": "literal", - "value": "system-prompt" + "value": "runtime-context" }, { "kind": "object", @@ -12834,7 +12875,7 @@ }, { "kind": "literal", - "value": "time-context" + "value": "schedule" }, { "kind": "object", @@ -12849,7 +12890,7 @@ }, { "kind": "literal", - "value": "tmux-context" + "value": "system-prompt" }, { "kind": "object", @@ -12864,7 +12905,7 @@ }, { "kind": "literal", - "value": "tool-goal" + "value": "time-context" }, { "kind": "object", @@ -12879,37 +12920,7 @@ }, { "kind": "literal", - "value": "tool-jobs" - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 56, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "tool-registry" - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 58, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "user" + "value": "tmux-context" }, { "kind": "object", @@ -12922,6 +12933,36 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-jobs" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 59, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "tool-registry" + }, { "kind": "object", "properties": [ @@ -12933,6 +12974,72 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "user" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "user-approval" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 66, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 68, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "cordis-host-runner" @@ -12940,19 +13047,9 @@ { "kind": "object", "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, { "name": "kind", - "type": 63, - "optional": false - }, - { - "name": "sections", - "type": 43, + "type": 70, "optional": false } ], @@ -12965,9 +13062,19 @@ { "kind": "object", "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, { "name": "kind", - "type": 63, + "type": 64, + "optional": false + }, + { + "name": "sections", + "type": 43, "optional": false } ], @@ -12978,12 +13085,33 @@ "properties": [ { "name": "form", - "type": 66, + "type": 41, "optional": false }, { "name": "kind", - "type": 67, + "type": 70, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 75, "optional": false }, { @@ -13012,12 +13140,12 @@ "properties": [ { "name": "form", - "type": 69, + "type": 77, "optional": false }, { "name": "kind", - "type": 70, + "type": 78, "optional": false }, { @@ -13041,12 +13169,12 @@ "properties": [ { "name": "form", - "type": 66, + "type": 74, "optional": false }, { "name": "kind", - "type": 72, + "type": 80, "optional": false }, { @@ -13066,12 +13194,12 @@ "properties": [ { "name": "form", - "type": 66, + "type": 74, "optional": false }, { "name": "kind", - "type": 74, + "type": 82, "optional": false }, { @@ -13086,56 +13214,6 @@ "kind": "literal", "value": "hooks-codex" }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 76, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "model-selection" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 78, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "plan-mode" - }, { "kind": "object", "properties": [ @@ -13151,17 +13229,17 @@ }, { "name": "changes", - "type": 80, + "type": 84, "optional": false }, { "name": "form", - "type": 86, + "type": 90, "optional": false }, { "name": "kind", - "type": 87, + "type": 91, "optional": false } ], @@ -13169,14 +13247,14 @@ }, { "kind": "array", - "element": 81 + "element": 85 }, { "kind": "object", "properties": [ { "name": "action", - "type": 82, + "type": 86, "optional": false }, { @@ -13200,9 +13278,9 @@ { "kind": "union", "types": [ - 83, - 84, - 85 + 87, + 88, + 89 ] }, { @@ -13235,12 +13313,12 @@ }, { "name": "kind", - "type": 89, + "type": 93, "optional": false }, { "name": "outcome", - "type": 90, + "type": 94, "optional": false } ], @@ -13254,6 +13332,31 @@ "kind": "literal", "value": "answered" }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "model-selection" + }, { "kind": "object", "properties": [ @@ -13264,7 +13367,7 @@ }, { "name": "kind", - "type": 58, + "type": 61, "optional": false }, { @@ -13275,31 +13378,6 @@ ], "indices": [] }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 93, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "repeat-tool-reminder" - }, { "kind": "object", "properties": [ @@ -13310,7 +13388,7 @@ }, { "name": "kind", - "type": 95, + "type": 99, "optional": false }, { @@ -13325,6 +13403,31 @@ "kind": "literal", "value": "compact-checkpoint" }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 74, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "plan-mode" + }, { "kind": "object", "properties": [ @@ -13335,12 +13438,12 @@ }, { "name": "form", - "type": 66, + "type": 74, "optional": false }, { "name": "kind", - "type": 97, + "type": 103, "optional": false }, { @@ -13370,47 +13473,22 @@ "kind": "literal", "value": "webhook" }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 99, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "runtime-context" - }, { "kind": "object", "properties": [ { "name": "entries", - "type": 101, + "type": 105, "optional": false }, { "name": "form", - "type": 103, + "type": 107, "optional": false }, { "name": "kind", - "type": 104, + "type": 108, "optional": false }, { @@ -13423,7 +13501,7 @@ }, { "kind": "array", - "element": 102 + "element": 106 }, { "kind": "object", @@ -13454,12 +13532,12 @@ "properties": [ { "name": "form", - "type": 86, + "type": 90, "optional": false }, { "name": "kind", - "type": 106, + "type": 110, "optional": false }, { @@ -13479,22 +13557,22 @@ "properties": [ { "name": "form", - "type": 108, + "type": 112, "optional": false }, { "name": "kind", - "type": 109, + "type": 113, "optional": false }, { "name": "references", - "type": 110, + "type": 114, "optional": false }, { "name": "version", - "type": 114, + "type": 118, "optional": false } ], @@ -13510,7 +13588,7 @@ }, { "kind": "array", - "element": 111 + "element": 115 }, { "kind": "object", @@ -13522,7 +13600,7 @@ }, { "name": "capturedThroughSeq", - "type": 112, + "type": 116, "optional": false }, { @@ -13577,7 +13655,7 @@ "kind": "union", "types": [ 2, - 113 + 117 ] }, { @@ -13598,7 +13676,7 @@ }, { "name": "kind", - "type": 72, + "type": 80, "optional": false }, { @@ -13619,91 +13697,28 @@ }, { "name": "kind", + "type": 82, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", "type": 74, "optional": false }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, { "name": "kind", - "type": 76, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 78, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 93, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 121, + "type": 122, "optional": false }, { @@ -13716,24 +13731,24 @@ }, { "kind": "literal", - "value": "schedule" + "value": "repeat-tool-reminder" }, { "kind": "object", "properties": [ { "name": "form", - "type": 66, + "type": 41, "optional": false }, { "name": "kind", - "type": 48, + "type": 96, "optional": false }, { - "name": "summary", - "type": 10, + "name": "sections", + "type": 43, "optional": false } ], @@ -13744,149 +13759,17 @@ "properties": [ { "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 50, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 52, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 54, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 66, - "optional": false - }, - { - "name": "kind", - "type": 63, - "optional": false - }, - { - "name": "summary", - "type": 10, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 72, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", "type": 74, "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false }, { "name": "kind", - "type": 76, + "type": 46, + "optional": false + }, + { + "name": "summary", + "type": 10, "optional": false } ], @@ -13897,342 +13780,17 @@ "properties": [ { "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 78, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 93, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 99, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 121, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 48, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 50, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 52, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 99, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 54, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 86, - "optional": false - }, - { - "name": "kind", - "type": 63, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 72, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", "type": 74, "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 76, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 78, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 93, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 99, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 121, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 121, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false }, { "name": "kind", "type": 48, "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false }, { - "name": "kind", - "type": 50, + "name": "summary", + "type": 10, "optional": false } ], @@ -14243,257 +13801,17 @@ "properties": [ { "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 52, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 54, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 108, - "optional": false - }, - { - "name": "kind", - "type": 63, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 72, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", "type": 74, "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 48, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 76, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 78, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 93, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 99, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 121, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 48, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 50, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false }, { "name": "kind", "type": 52, "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false }, { - "name": "kind", - "type": 54, + "name": "summary", + "type": 10, "optional": false } ], @@ -14504,82 +13822,18 @@ "properties": [ { "name": "form", - "type": 41, - "optional": false - }, - { - "name": "kind", - "type": 50, - "optional": false - }, - { - "name": "sections", - "type": 43, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 42, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 69, - "optional": false - }, - { - "name": "kind", - "type": 63, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 103, - "optional": false - }, - { - "name": "kind", - "type": 72, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 103, - "optional": false - }, - { - "name": "kind", "type": 74, "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + }, + { + "name": "summary", + "type": 10, + "optional": false } ], "indices": [] @@ -14589,12 +13843,17 @@ "properties": [ { "name": "form", - "type": 103, + "type": 74, "optional": false }, { "name": "kind", - "type": 76, + "type": 42, + "optional": false + }, + { + "name": "summary", + "type": 10, "optional": false } ], @@ -14605,12 +13864,17 @@ "properties": [ { "name": "form", - "type": 103, + "type": 74, "optional": false }, { "name": "kind", - "type": 78, + "type": 57, + "optional": false + }, + { + "name": "summary", + "type": 10, "optional": false } ], @@ -14621,12 +13885,17 @@ "properties": [ { "name": "form", - "type": 103, + "type": 74, "optional": false }, { "name": "kind", - "type": 93, + "type": 64, + "optional": false + }, + { + "name": "summary", + "type": 10, "optional": false } ], @@ -14637,12 +13906,17 @@ "properties": [ { "name": "form", - "type": 103, + "type": 74, "optional": false }, { "name": "kind", - "type": 99, + "type": 70, + "optional": false + }, + { + "name": "summary", + "type": 10, "optional": false } ], @@ -14653,12 +13927,12 @@ "properties": [ { "name": "form", - "type": 103, + "type": 90, "optional": false }, { "name": "kind", - "type": 121, + "type": 80, "optional": false } ], @@ -14669,7 +13943,87 @@ "properties": [ { "name": "form", - "type": 103, + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, "optional": false }, { @@ -14680,6 +14034,698 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 90, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 112, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 54, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, + { + "name": "kind", + "type": 48, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 42, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 77, + "optional": false + }, + { + "name": "kind", + "type": 70, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 80, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 82, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 96, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 101, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 122, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 46, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -14706,12 +14752,12 @@ "properties": [ { "name": "form", - "type": 103, + "type": 107, "optional": false }, { "name": "kind", - "type": 50, + "type": 48, "optional": false } ], @@ -14722,7 +14768,7 @@ "properties": [ { "name": "form", - "type": 103, + "type": 107, "optional": false }, { @@ -14738,7 +14784,7 @@ "properties": [ { "name": "form", - "type": 103, + "type": 107, "optional": false }, { @@ -14754,7 +14800,7 @@ "properties": [ { "name": "form", - "type": 103, + "type": 107, "optional": false }, { @@ -14770,12 +14816,44 @@ "properties": [ { "name": "form", - "type": 103, + "type": 107, "optional": false }, { "name": "kind", - "type": 63, + "type": 57, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 64, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 107, + "optional": false + }, + { + "name": "kind", + "type": 70, "optional": false } ], @@ -14791,7 +14869,7 @@ }, { "name": "kind", - "type": 186, + "type": 190, "optional": false }, { @@ -14814,9 +14892,30 @@ { "kind": "object", "properties": [ + { + "name": "form", + "type": 41, + "optional": false + }, { "name": "kind", - "type": 188, + "type": 54, + "optional": false + }, + { + "name": "sections", + "type": 43, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 193, "optional": false }, { @@ -14851,7 +14950,7 @@ "properties": [ { "name": "kind", - "type": 190, + "type": 195, "optional": false }, { @@ -14866,7 +14965,7 @@ }, { "name": "replayState", - "type": 191, + "type": 196, "optional": true } ], @@ -14883,19 +14982,9 @@ { "kind": "object", "properties": [ - { - "name": "form", - "type": 41, - "optional": false - }, { "name": "kind", - "type": 54, - "optional": false - }, - { - "name": "sections", - "type": 43, + "type": 80, "optional": false } ], @@ -14906,7 +14995,7 @@ "properties": [ { "name": "kind", - "type": 72, + "type": 82, "optional": false } ], @@ -14917,7 +15006,7 @@ "properties": [ { "name": "kind", - "type": 74, + "type": 96, "optional": false } ], @@ -14928,7 +15017,7 @@ "properties": [ { "name": "kind", - "type": 76, + "type": 101, "optional": false } ], @@ -14939,18 +15028,7 @@ "properties": [ { "name": "kind", - "type": 78, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 198, + "type": 202, "optional": false } ], @@ -14965,29 +15043,7 @@ "properties": [ { "name": "kind", - "type": 93, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 99, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 121, + "type": 122, "optional": false } ], @@ -15008,7 +15064,7 @@ }, { "name": "role", - "type": 58, + "type": 61, "optional": false }, { @@ -15023,7 +15079,8 @@ "unknownKinds": "preserve", "attributionKinds": [ "tmux-context", - "user-question-reply" + "user-question-reply", + "working-directory" ] } } @@ -15045,12 +15102,12 @@ }, { "name": "role", - "type": 204, + "type": 206, "optional": false }, { "name": "source", - "type": 189, + "type": 194, "optional": false } ], @@ -15075,12 +15132,12 @@ }, { "name": "role", - "type": 206, + "type": 208, "optional": false }, { "name": "source", - "type": 45, + "type": 49, "optional": false } ], @@ -15260,7 +15317,7 @@ "kind": "event", "event": "subagent/catalog", "surface": false, - "digest": "3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604", + "digest": "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac", "schema": { "root": 0, "nodes": [ @@ -15274,7 +15331,7 @@ }, { "name": "ignorable", - "type": 14, + "type": 17, "optional": true }, { @@ -15289,7 +15346,7 @@ }, { "name": "type", - "type": 15, + "type": 18, "optional": false } ], @@ -15301,8 +15358,9 @@ 2, 7, 9, - 11, - 12 + 12, + 14, + 15 ] }, { @@ -15410,6 +15468,45 @@ "type": 10, "optional": false }, + { + "name": "version", + "type": 11, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "external" + }, + { + "kind": "literal", + "value": 2 + }, + { + "kind": "object", + "properties": [ + { + "name": "childCreatedAt", + "type": 3, + "optional": false + }, + { + "name": "childId", + "type": 4, + "optional": false + }, + { + "name": "label", + "type": 4, + "optional": true + }, + { + "name": "mode", + "type": 13, + "optional": false + }, { "name": "version", "type": 6, @@ -15442,7 +15539,7 @@ }, { "name": "mode", - "type": 10, + "type": 13, "optional": false }, { @@ -15473,7 +15570,7 @@ }, { "name": "mode", - "type": 13, + "type": 16, "optional": false }, { @@ -17589,7 +17686,7 @@ "kind": "event", "event": "tool/ptc-dispatch", "surface": false, - "digest": "100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163", + "digest": "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5", "schema": { "root": 0, "nodes": [ @@ -17603,22 +17700,22 @@ }, { "name": "ignorable", - "type": 26, + "type": 29, "optional": true }, { "name": "seq", - "type": 19, + "type": 17, "optional": false }, { "name": "time", - "type": 19, + "type": 17, "optional": false }, { "name": "type", - "type": 33, + "type": 37, "optional": false } ], @@ -17647,6 +17744,11 @@ "type": 32, "optional": false }, + { + "name": "meta", + "type": 33, + "optional": true + }, { "name": "name", "type": 6, @@ -17686,8 +17788,8 @@ 10, 13, 15, - 17, - 28 + 19, + 21 ] }, { @@ -17783,6 +17885,51 @@ "kind": "literal", "value": "tool-removal" }, + { + "kind": "object", + "properties": [ + { + "name": "attachment", + "type": 16, + "optional": false + }, + { + "name": "type", + "type": 18, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "attachmentId", + "type": 6, + "optional": false + }, + { + "name": "bytes", + "type": 17, + "optional": false + }, + { + "name": "name", + "type": 6, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "file" + }, { "kind": "object", "properties": [ @@ -17803,7 +17950,7 @@ }, { "name": "type", - "type": 16, + "type": 20, "optional": false } ], @@ -17818,17 +17965,17 @@ "properties": [ { "name": "attachment", - "type": 18, + "type": 22, "optional": false }, { "name": "offloaded", - "type": 26, + "type": 29, "optional": true }, { "name": "type", - "type": 27, + "type": 30, "optional": false } ], @@ -17844,17 +17991,17 @@ }, { "name": "bytes", - "type": 19, + "type": 17, "optional": false }, { "name": "height", - "type": 19, + "type": 17, "optional": false }, { "name": "mediaType", - "type": 20, + "type": 23, "optional": false }, { @@ -17864,28 +18011,24 @@ }, { "name": "originalDimensions", - "type": 25, + "type": 28, "optional": true }, { "name": "width", - "type": 19, + "type": 17, "optional": false } ], "indices": [] }, - { - "kind": "primitive", - "type": "number" - }, { "kind": "union", "types": [ - 21, - 22, - 23, - 24 + 24, + 25, + 26, + 27 ] }, { @@ -17909,12 +18052,12 @@ "properties": [ { "name": "height", - "type": 19, + "type": 17, "optional": false }, { "name": "width", - "type": 19, + "type": 17, "optional": false } ], @@ -17928,47 +18071,6 @@ "kind": "literal", "value": "image" }, - { - "kind": "object", - "properties": [ - { - "name": "attachment", - "type": 29, - "optional": false - }, - { - "name": "type", - "type": 30, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "attachmentId", - "type": 6, - "optional": false - }, - { - "name": "bytes", - "type": 19, - "optional": false - }, - { - "name": "name", - "type": 6, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "file" - }, { "kind": "object", "properties": [ @@ -17994,6 +18096,35 @@ "kind": "primitive", "type": "boolean" }, + { + "kind": "union", + "types": [ + 6, + 17, + 32, + 34, + 35, + 36 + ] + }, + { + "kind": "array", + "element": 33 + }, + { + "kind": "primitive", + "type": "null" + }, + { + "kind": "object", + "properties": [], + "indices": [ + { + "key": 6, + "value": 33 + } + ] + }, { "kind": "literal", "value": "tool/ptc-dispatch" @@ -19027,7 +19158,7 @@ "kind": "event", "event": "user/message", "surface": true, - "digest": "b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411", + "digest": "2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade", "schema": { "root": 0, "nodes": [ @@ -19051,12 +19182,12 @@ }, { "name": "sourceEventSeqs", - "type": 196, + "type": 198, "optional": true }, { "name": "surfaceOp", - "type": 197, + "type": 199, "optional": false }, { @@ -19066,7 +19197,7 @@ }, { "name": "type", - "type": 200, + "type": 202, "optional": false } ], @@ -19102,7 +19233,8 @@ "unknownKinds": "preserve", "attributionKinds": [ "tmux-context", - "user-question-reply" + "user-question-reply", + "working-directory" ] } } @@ -19421,7 +19553,7 @@ 47, 49, 51, - 54, + 53, 56, 58, 60, @@ -19434,7 +19566,7 @@ 67, 68, 69, - 71, + 70, 72, 73, 74, @@ -19447,7 +19579,7 @@ 81, 82, 83, - 85, + 84, 86, 87, 88, @@ -19460,7 +19592,7 @@ 95, 96, 97, - 99, + 98, 100, 101, 102, @@ -19473,39 +19605,38 @@ 109, 110, 111, - 113, - 115, - 118, + 112, + 114, + 116, 119, 120, 121, 122, - 124, + 123, 125, 126, 127, - 129, + 128, 130, 131, 132, 133, - 135, + 134, 136, 137, - 139, + 138, 140, - 148, + 142, + 143, 151, - 153, 154, 156, - 158, - 162, - 164, - 172, - 176, - 177, - 178, + 157, + 159, + 161, + 165, + 167, + 175, 179, 180, 181, @@ -19516,11 +19647,13 @@ 186, 187, 188, + 189, 190, - 192, + 191, 193, - 194, - 195 + 195, + 196, + 197 ] }, { @@ -19550,7 +19683,7 @@ }, { "kind": "literal", - "value": "repeat-tool-reminder" + "value": "plan-mode" }, { "kind": "object", @@ -19573,6 +19706,31 @@ ], "indices": [] }, + { + "kind": "literal", + "value": "repeat-tool-reminder" + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 33, + "optional": false + }, + { + "name": "kind", + "type": 38, + "optional": false + }, + { + "name": "summary", + "type": 5, + "optional": false + } + ], + "indices": [] + }, { "kind": "literal", "value": "runtime-context" @@ -19587,7 +19745,7 @@ }, { "name": "kind", - "type": 38, + "type": 40, "optional": false }, { @@ -19612,7 +19770,7 @@ }, { "name": "kind", - "type": 40, + "type": 42, "optional": false }, { @@ -19637,7 +19795,7 @@ }, { "name": "kind", - "type": 42, + "type": 44, "optional": false }, { @@ -19662,7 +19820,7 @@ }, { "name": "kind", - "type": 44, + "type": 46, "optional": false }, { @@ -19687,7 +19845,7 @@ }, { "name": "kind", - "type": 46, + "type": 48, "optional": false }, { @@ -19712,7 +19870,7 @@ }, { "name": "kind", - "type": 48, + "type": 50, "optional": false }, { @@ -19737,7 +19895,7 @@ }, { "name": "kind", - "type": 50, + "type": 52, "optional": false }, { @@ -19757,12 +19915,12 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { "name": "kind", - "type": 53, + "type": 55, "optional": false } ], @@ -19781,12 +19939,12 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { "name": "kind", - "type": 55, + "type": 57, "optional": false } ], @@ -19801,12 +19959,12 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { "name": "kind", - "type": 57, + "type": 59, "optional": false } ], @@ -19821,27 +19979,7 @@ "properties": [ { "name": "form", - "type": 52, - "optional": false - }, - { - "name": "kind", - "type": 59, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "literal", - "value": "plan-mode" - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19857,7 +19995,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19873,7 +20011,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19889,7 +20027,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19905,7 +20043,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19921,7 +20059,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19937,7 +20075,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19953,7 +20091,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19969,7 +20107,7 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { @@ -19985,12 +20123,28 @@ "properties": [ { "name": "form", - "type": 70, + "type": 54, "optional": false }, { "name": "kind", - "type": 53, + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 71, + "optional": false + }, + { + "name": "kind", + "type": 55, "optional": false } ], @@ -20005,23 +20159,7 @@ "properties": [ { "name": "form", - "type": 70, - "optional": false - }, - { - "name": "kind", - "type": 55, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20037,7 +20175,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20053,7 +20191,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20069,7 +20207,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20085,7 +20223,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20101,7 +20239,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20117,7 +20255,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20133,7 +20271,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20149,7 +20287,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20165,7 +20303,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20181,7 +20319,7 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { @@ -20197,12 +20335,28 @@ "properties": [ { "name": "form", - "type": 84, + "type": 71, "optional": false }, { "name": "kind", - "type": 53, + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 85, + "optional": false + }, + { + "name": "kind", + "type": 55, "optional": false } ], @@ -20217,23 +20371,7 @@ "properties": [ { "name": "form", - "type": 84, - "optional": false - }, - { - "name": "kind", - "type": 55, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20249,7 +20387,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20265,7 +20403,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20281,7 +20419,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20297,7 +20435,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20313,7 +20451,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20329,7 +20467,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20345,7 +20483,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20361,7 +20499,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20377,7 +20515,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20393,7 +20531,7 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { @@ -20409,12 +20547,28 @@ "properties": [ { "name": "form", - "type": 98, + "type": 85, "optional": false }, { "name": "kind", - "type": 53, + "type": 52, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 55, "optional": false } ], @@ -20429,23 +20583,7 @@ "properties": [ { "name": "form", - "type": 98, - "optional": false - }, - { - "name": "kind", - "type": 55, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20461,7 +20599,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20477,7 +20615,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20493,7 +20631,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20509,7 +20647,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20525,7 +20663,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20541,7 +20679,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20557,7 +20695,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20573,7 +20711,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20589,7 +20727,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20605,7 +20743,7 @@ "properties": [ { "name": "form", - "type": 98, + "type": 99, "optional": false }, { @@ -20616,6 +20754,22 @@ ], "indices": [] }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 99, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + } + ], + "indices": [] + }, { "kind": "object", "properties": [ @@ -20626,7 +20780,7 @@ }, { "name": "kind", - "type": 112, + "type": 113, "optional": false }, { @@ -20651,7 +20805,7 @@ "properties": [ { "name": "kind", - "type": 114, + "type": 115, "optional": false }, { @@ -20686,7 +20840,7 @@ "properties": [ { "name": "kind", - "type": 116, + "type": 117, "optional": false }, { @@ -20701,7 +20855,7 @@ }, { "name": "replayState", - "type": 117, + "type": 118, "optional": true } ], @@ -20715,17 +20869,6 @@ "kind": "opaque", "reason": "unknown" }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 53, - "optional": false - } - ], - "indices": [] - }, { "kind": "object", "properties": [ @@ -20764,7 +20907,18 @@ "properties": [ { "name": "kind", - "type": 123, + "type": 34, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 124, "optional": false } ], @@ -20774,17 +20928,6 @@ "kind": "literal", "value": "ptc-mode" }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 34, - "optional": false - } - ], - "indices": [] - }, { "kind": "object", "properties": [ @@ -20812,7 +20955,18 @@ "properties": [ { "name": "kind", - "type": 128, + "type": 40, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 129, "optional": false } ], @@ -20822,17 +20976,6 @@ "kind": "literal", "value": "system-prompt" }, - { - "kind": "object", - "properties": [ - { - "name": "kind", - "type": 40, - "optional": false - } - ], - "indices": [] - }, { "kind": "object", "properties": [ @@ -20871,7 +21014,18 @@ "properties": [ { "name": "kind", - "type": 134, + "type": 48, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 135, "optional": false } ], @@ -20897,7 +21051,7 @@ "properties": [ { "name": "kind", - "type": 48, + "type": 50, "optional": false } ], @@ -20908,7 +21062,22 @@ "properties": [ { "name": "kind", - "type": 138, + "type": 139, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "literal", + "value": "working-directory" + }, + { + "kind": "object", + "properties": [ + { + "name": "kind", + "type": 141, "optional": false } ], @@ -20923,7 +21092,7 @@ "properties": [ { "name": "kind", - "type": 50, + "type": 52, "optional": false } ], @@ -20944,17 +21113,17 @@ }, { "name": "changes", - "type": 141, + "type": 144, "optional": false }, { "name": "form", - "type": 52, + "type": 54, "optional": false }, { "name": "kind", - "type": 147, + "type": 150, "optional": false } ], @@ -20962,14 +21131,14 @@ }, { "kind": "array", - "element": 142 + "element": 145 }, { "kind": "object", "properties": [ { "name": "action", - "type": 143, + "type": 146, "optional": false }, { @@ -20993,9 +21162,9 @@ { "kind": "union", "types": [ - 144, - 145, - 146 + 147, + 148, + 149 ] }, { @@ -21024,12 +21193,12 @@ }, { "name": "kind", - "type": 149, + "type": 152, "optional": false }, { "name": "outcome", - "type": 150, + "type": 153, "optional": false } ], @@ -21053,7 +21222,7 @@ }, { "name": "kind", - "type": 152, + "type": 155, "optional": false } ], @@ -21094,7 +21263,7 @@ }, { "name": "kind", - "type": 155, + "type": 158, "optional": false }, { @@ -21124,7 +21293,7 @@ }, { "name": "kind", - "type": 157, + "type": 160, "optional": false }, { @@ -21159,17 +21328,17 @@ "properties": [ { "name": "entries", - "type": 159, + "type": 162, "optional": false }, { "name": "form", - "type": 98, + "type": 99, "optional": false }, { "name": "kind", - "type": 161, + "type": 164, "optional": false }, { @@ -21182,7 +21351,7 @@ }, { "kind": "array", - "element": 160 + "element": 163 }, { "kind": "object", @@ -21209,12 +21378,12 @@ "properties": [ { "name": "form", - "type": 52, + "type": 54, "optional": false }, { "name": "kind", - "type": 163, + "type": 166, "optional": false }, { @@ -21234,22 +21403,22 @@ "properties": [ { "name": "form", - "type": 70, + "type": 71, "optional": false }, { "name": "kind", - "type": 165, + "type": 168, "optional": false }, { "name": "references", - "type": 166, + "type": 169, "optional": false }, { "name": "version", - "type": 171, + "type": 174, "optional": false } ], @@ -21261,7 +21430,7 @@ }, { "kind": "array", - "element": 167 + "element": 170 }, { "kind": "object", @@ -21273,12 +21442,12 @@ }, { "name": "capturedThroughSeq", - "type": 168, + "type": 171, "optional": false }, { "name": "compacted", - "type": 170, + "type": 173, "optional": false }, { @@ -21318,7 +21487,7 @@ }, { "name": "truncated", - "type": 170, + "type": 173, "optional": false } ], @@ -21328,7 +21497,7 @@ "kind": "union", "types": [ 18, - 169 + 172 ] }, { @@ -21348,17 +21517,17 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { "name": "kind", - "type": 53, + "type": 55, "optional": false }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21370,7 +21539,7 @@ }, { "kind": "array", - "element": 175 + "element": 178 }, { "kind": "object", @@ -21393,28 +21562,7 @@ "properties": [ { "name": "form", - "type": 173, - "optional": false - }, - { - "name": "kind", - "type": 55, - "optional": false - }, - { - "name": "sections", - "type": 174, - "optional": false - } - ], - "indices": [] - }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21424,7 +21572,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21435,7 +21583,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21445,7 +21593,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21456,7 +21604,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21466,7 +21614,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21477,7 +21625,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21487,7 +21635,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21498,7 +21646,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21508,7 +21656,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21519,7 +21667,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21529,7 +21677,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21540,7 +21688,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21550,7 +21698,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21561,7 +21709,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21571,7 +21719,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21582,7 +21730,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21592,7 +21740,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21603,7 +21751,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21613,7 +21761,7 @@ }, { "name": "sections", - "type": 174, + "type": 177, "optional": false } ], @@ -21624,7 +21772,7 @@ "properties": [ { "name": "form", - "type": 173, + "type": 176, "optional": false }, { @@ -21634,7 +21782,28 @@ }, { "name": "sections", - "type": 174, + "type": 177, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "form", + "type": 176, + "optional": false + }, + { + "name": "kind", + "type": 52, + "optional": false + }, + { + "name": "sections", + "type": 177, "optional": false } ], @@ -21650,7 +21819,7 @@ }, { "name": "kind", - "type": 189, + "type": 192, "optional": false }, { @@ -21675,12 +21844,12 @@ "properties": [ { "name": "form", - "type": 84, + "type": 85, "optional": false }, { "name": "kind", - "type": 191, + "type": 194, "optional": false }, { @@ -21695,27 +21864,6 @@ "kind": "literal", "value": "agent-message" }, - { - "kind": "object", - "properties": [ - { - "name": "form", - "type": 33, - "optional": false - }, - { - "name": "kind", - "type": 53, - "optional": false - }, - { - "name": "summary", - "type": 5, - "optional": false - } - ], - "indices": [] - }, { "kind": "object", "properties": [ @@ -21786,8 +21934,8 @@ { "kind": "union", "types": [ - 198, - 199 + 200, + 201 ] }, { @@ -21804,7 +21952,7 @@ }, { "name": "op", - "type": 145, + "type": 148, "optional": false }, { @@ -22027,6 +22175,76 @@ ] } }, + { + "key": "event:working-directory/change", + "kind": "event", + "event": "working-directory/change", + "surface": false, + "digest": "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3", + "schema": { + "root": 0, + "nodes": [ + { + "kind": "object", + "properties": [ + { + "name": "data", + "type": 1, + "optional": false + }, + { + "name": "ignorable", + "type": 3, + "optional": true + }, + { + "name": "seq", + "type": 4, + "optional": false + }, + { + "name": "time", + "type": 4, + "optional": false + }, + { + "name": "type", + "type": 5, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "object", + "properties": [ + { + "name": "cwd", + "type": 2, + "optional": false + } + ], + "indices": [] + }, + { + "kind": "primitive", + "type": "string" + }, + { + "kind": "literal", + "value": true + }, + { + "kind": "primitive", + "type": "number" + }, + { + "kind": "literal", + "value": "working-directory/change" + } + ] + } + }, { "key": "event:workspace/changes", "kind": "event", @@ -22129,6 +22347,15 @@ "names": [], "sources": [] }, + { + "digest": "045926475a46f625f84ae2be292b384817059f16927c69a9adab93717923ac19", + "names": [ + "packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData" + ], + "sources": [ + "packages/session/session-title-llm/src/index.ts:34" + ] + }, { "digest": "05a2796621b1ee6c03ef7abe560b25116bb4da4d592312117242a78415a32b04", "names": [], @@ -22270,15 +22497,6 @@ "names": [], "sources": [] }, - { - "digest": "0ee52de154f8955c7abc82cffebc57452dcc8c44dccbdb6ad16c71a11305ead8", - "names": [ - "packages/core/tools/src/types.ts#PtcDispatchEventData" - ], - "sources": [ - "packages/core/tools/src/types.ts:20" - ] - }, { "digest": "0eeb17e1df89b1ebc2902116fe7145e0d321ef0db26e6bdebc802cb8513a01c1", "names": [], @@ -22301,20 +22519,6 @@ "names": [], "sources": [] }, - { - "digest": "100f6dca1468538239522cde3533e5bd721d0f1a7b50bea8b0eb533ea6c96163", - "names": [], - "sources": [] - }, - { - "digest": "1020bbc3c744c060cd31c0891ae79b9e4c3e0adfa3f0be49869309b34e9ef200", - "names": [ - "packages/llm/llm/src/message.ts#DeveloperMessage" - ], - "sources": [ - "packages/llm/llm/src/message.ts:157" - ] - }, { "digest": "1033093edd0db80ff410e00830b523405e00bb0c7684948e531ff65095799625", "names": [], @@ -22334,7 +22538,7 @@ "digest": "119abe2b9c0b2f46ecb31d82b9acba4d4b30e049f655e2475e8b4ae12d5a9e25", "names": [], "sources": [ - "packages/hooks/hook-protocol/src/types.ts:19" + "packages/experimental/hook-protocol/src/types.ts:19" ] }, { @@ -22344,6 +22548,11 @@ "packages/interaction/user-approval/src/index.ts:40" ] }, + { + "digest": "124aa62078ca3311353b39cc3bf6c75da281338be6bbf672f8b361d828cddca3", + "names": [], + "sources": [] + }, { "digest": "12a0cfa619ac4a5fc6c3e8e359c8dae15769edc61497a575935c78d5842f8e42", "names": [], @@ -22378,13 +22587,6 @@ "names": [], "sources": [] }, - { - "digest": "149c5a77529cb0dc06f5ceb6273069b7606174d1e7893b5f9b1071c15306b846", - "names": [], - "sources": [ - "packages/core/agent/src/types.ts:96" - ] - }, { "digest": "15196447222782e773eb943c92b18316ce96b9af0f0cfddb6e57ba8274ecc5ff", "names": [], @@ -22429,11 +22631,6 @@ "packages/core/session/src/types.ts:427" ] }, - { - "digest": "186159f5f6f67a0b8cd095b8fe55bef42d4f25ca1a1c248f859867af2ece0467", - "names": [], - "sources": [] - }, { "digest": "18dc21f12bbac8ade8377e6fbb54179e20c3d955d73fba3244c57fb6bf001df0", "names": [ @@ -22556,15 +22753,6 @@ "names": [], "sources": [] }, - { - "digest": "1de64ea9a0849ce631b7e4e1255ae773fabd94a4a7c8d27e716f8012302e12c0", - "names": [ - "packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent" - ], - "sources": [ - "packages/subagent/subagent/src/catalog.ts:28" - ] - }, { "digest": "1df34a6c086866ffdc2dd9e8e5c581c3751a010cdf6b8538a7327f7a3237f0b4", "names": [ @@ -22613,6 +22801,15 @@ "packages/core/session/src/types.ts:196" ] }, + { + "digest": "1fa156c85450ca31063d0e0d2853028e6dac9687f2d3191fa811648210118154", + "names": [ + "packages/llm/llm/src/message.ts#MessageSource" + ], + "sources": [ + "packages/llm/llm/src/message.ts:136" + ] + }, { "digest": "1fdad624334d354820dfb3a26e28d0bc9fc1b62a33f87bab63f715b3f4391bfd", "names": [], @@ -22622,7 +22819,7 @@ "digest": "20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5", "names": [], "sources": [ - "packages/plan/plan-mode/src/index.ts:53" + "packages/plan/plan-mode/src/index.ts:52" ] }, { @@ -22644,6 +22841,13 @@ "names": [], "sources": [] }, + { + "digest": "2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5", + "names": [], + "sources": [ + "packages/session/working-directory/src/index.ts:29" + ] + }, { "digest": "224aa11d4d5f88c450822d01b99e6ca5e3a9c7f26a5ede10bae33384a09ef4dc", "names": [], @@ -22708,15 +22912,6 @@ "names": [], "sources": [] }, - { - "digest": "26df091b55c8a9b58004931ce5425b441201032cfab0b75e2ba03f428ecbd116", - "names": [ - "packages/llm/llm/src/message.ts#MessageSource" - ], - "sources": [ - "packages/llm/llm/src/message.ts:136" - ] - }, { "digest": "2765e9119edf5816ac1ab4c95c5ee32c90403f3742962eff11544f7752d59d2e", "names": [], @@ -22766,6 +22961,11 @@ "packages/llm/llm/src/types.ts:455" ] }, + { + "digest": "2ace3b8059b82b6d9837fed518ca97a99eb37c4482df7aad0e71c85489428ade", + "names": [], + "sources": [] + }, { "digest": "2aed07493b1c65b6d00de5586da1e743cf501684b9ad43a1765fb4007d4ea456", "names": [], @@ -22836,9 +23036,14 @@ "packages/core/tools/src/types.ts#PtcDispatchStartEventData" ], "sources": [ - "packages/core/tools/src/types.ts:11" + "packages/core/tools/src/types.ts:12" ] }, + { + "digest": "2fef90def36aa758ca096c5f6ad0e00dbb2101943e19ca54b05618801974894a", + "names": [], + "sources": [] + }, { "digest": "30d5a537b296685770b1241c4b4bbde19d5a31ff81aa23df73fab5e90b8fc65f", "names": [ @@ -22868,7 +23073,7 @@ "packages/llm/llm/src/call-config.ts#LlmCallConfigAdapterDefaults" ], "sources": [ - "packages/llm/llm/src/call-config.ts:36" + "packages/llm/llm/src/call-config.ts:54" ] }, { @@ -22975,11 +23180,6 @@ "packages/jobs/jobs/src/types.ts:16" ] }, - { - "digest": "3abae7324356f155cb42450c00b806d134ec93bd6439d2063b8d724162d58604", - "names": [], - "sources": [] - }, { "digest": "3b04790e08b35241ffa0d38f6d7e4f8788aa921341f6c0d7574a329397c59408", "names": [], @@ -23037,7 +23237,7 @@ "packages/core/tools/src/index.ts#ToolRestriction" ], "sources": [ - "packages/core/tools/src/index.ts:700" + "packages/core/tools/src/index.ts:701" ] }, { @@ -23071,11 +23271,6 @@ "names": [], "sources": [] }, - { - "digest": "3f42f45cbb8a06586d247e5f3763dc02c8d31a145a9a3b82fabcd101e7e224cc", - "names": [], - "sources": [] - }, { "digest": "3f74ba74acd10644c1ed6947a411d8088bd2cfac74f61bb8540f96d68e31f004", "names": [], @@ -23195,7 +23390,7 @@ "packages/subagent/subagent/src/continuation-messages.ts#AgentMessageSource" ], "sources": [ - "packages/subagent/subagent/src/continuation-messages.ts:15" + "packages/subagent/subagent/src/continuation-messages.ts:14" ] }, { @@ -23238,6 +23433,11 @@ "packages/core/session/src/types.ts:193" ] }, + { + "digest": "4be8fd28fefa5534675ea9aabd0a24efc2838b989bbc74da20a1efbd0daede8c", + "names": [], + "sources": [] + }, { "digest": "4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b", "names": [ @@ -23248,6 +23448,9 @@ "packages/experimental/agent-team/src/types.ts#TeamId", "packages/experimental/agent-team/src/types.ts#TeamMessageId", "packages/experimental/agent-team/src/types.ts#TeamTaskId", + "packages/experimental/webhook/src/brand.ts#WebhookDeliveryId", + "packages/experimental/webhook/src/brand.ts#WebhookRuleId", + "packages/experimental/webhook/src/brand.ts#WebhookSourceId", "packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion", "packages/goal/goal/src/types.ts#GoalId", "packages/interaction/commands/src/brand.ts#CommandId", @@ -23262,9 +23465,6 @@ "packages/schedule/schedule/src/types.ts#ScheduleId", "packages/session/session-title/src/index.ts#SessionTitleProviderId", "packages/session/session-title/src/types.ts#SessionTitleProviderId", - "packages/webhook/webhook/src/brand.ts#WebhookDeliveryId", - "packages/webhook/webhook/src/brand.ts#WebhookRuleId", - "packages/webhook/webhook/src/brand.ts#WebhookSourceId", "packages/workflow/workflow/src/types.ts#WorkflowRunId" ], "sources": [ @@ -23275,6 +23475,9 @@ "packages/experimental/agent-team/src/types.ts:20", "packages/experimental/agent-team/src/types.ts:32", "packages/experimental/agent-team/src/types.ts:8", + "packages/experimental/webhook/src/brand.ts:12", + "packages/experimental/webhook/src/brand.ts:6", + "packages/experimental/webhook/src/brand.ts:9", "packages/feedback/message-feedback/src/types.ts:14", "packages/goal/goal/src/types.ts:17", "packages/interaction/commands/src/brand.ts:31", @@ -23289,9 +23492,6 @@ "packages/schedule/schedule/src/types.ts:14", "packages/session/session-title/src/index.ts:42", "packages/session/session-title/src/types.ts:17", - "packages/webhook/webhook/src/brand.ts:12", - "packages/webhook/webhook/src/brand.ts:6", - "packages/webhook/webhook/src/brand.ts:9", "packages/workflow/workflow/src/types.ts:13" ] }, @@ -23321,7 +23521,7 @@ "packages/subagent/subagent/src/continuation-messages.ts#SubagentSettledMessageSource" ], "sources": [ - "packages/subagent/subagent/src/continuation-messages.ts:30" + "packages/subagent/subagent/src/continuation-messages.ts:29" ] }, { @@ -23403,6 +23603,11 @@ "packages/core/session/src/types.ts:273" ] }, + { + "digest": "53750b9abf61efb7992b0aadc3e247ec927449c63266df28230fd1139f5590ac", + "names": [], + "sources": [] + }, { "digest": "53cf1704ae0709c7678bcf070c83c619fc48ca1b5c173b74cfa8702f22a8e492", "names": [], @@ -23481,11 +23686,6 @@ "names": [], "sources": [] }, - { - "digest": "5a92f12db2059c282cdea45b6845c65e6f4a95dd3d649aeddbde6f9ae03c561f", - "names": [], - "sources": [] - }, { "digest": "5abab3a27fd2736660ecddd94b28d6bbf114986dd894880153bd1be3ccacb563", "names": [], @@ -23496,6 +23696,13 @@ "names": [], "sources": [] }, + { + "digest": "5b55817a403569fbb2d379496df2e3f3f71107f6c577a218028ce187c60b61e6", + "names": [], + "sources": [ + "packages/core/session/src/types.ts:311" + ] + }, { "digest": "5b6fb1f226ff56402db08a6aae82af76100b14056271875f69ec526a1ae05d51", "names": [], @@ -23608,11 +23815,6 @@ "names": [], "sources": [] }, - { - "digest": "6178f1edcb23f361f2a6cb2c859b1c2187220d5695ece4a3400e0d92845a7178", - "names": [], - "sources": [] - }, { "digest": "6292bcbedaa97feaec5b48a03bb8144b581b93309d9d6c905f26da7a6a7a0a4f", "names": [], @@ -23679,7 +23881,7 @@ "packages/llm/llm/src/call-config.ts#LlmCallConfig" ], "sources": [ - "packages/llm/llm/src/call-config.ts:23" + "packages/llm/llm/src/call-config.ts:31" ] }, { @@ -23809,7 +24011,7 @@ "packages/context/agent-instructions/src/state.ts#AgentInstructionSource" ], "sources": [ - "packages/context/agent-instructions/src/state.ts:37" + "packages/context/agent-instructions/src/state.ts:40" ] }, { @@ -23935,6 +24137,11 @@ "names": [], "sources": [] }, + { + "digest": "7b1ce60193fa8b69292e5843858d9cee7dc565dcb40d33c136e5d7e8b8d4296a", + "names": [], + "sources": [] + }, { "digest": "7c6129b6961cf4b2a030f36c78d2b0a5be38617651a9b81322e054ee512bee06", "names": [ @@ -24066,7 +24273,7 @@ "digest": "83079c8a3f733ac3fa603eefa0fbb4737ca4d8b69e7e2e7298b60f9ac4098693", "names": [], "sources": [ - "packages/skill/tool-skill/src/index.ts:40" + "packages/skill/tool-skill/src/index.ts:41" ] }, { @@ -24074,6 +24281,13 @@ "names": [], "sources": [] }, + { + "digest": "83a665476a9ee9af05cb1fdded12dd647d1b97b491200d7f12ef1af7cdac4428", + "names": [], + "sources": [ + "packages/core/agent/src/types.ts:96" + ] + }, { "digest": "83d6916021eede8f2d83a1872716caa1efdec39a3681890996c55147187fdd12", "names": [], @@ -24085,7 +24299,7 @@ "packages/context/agent-instructions/src/render.ts#AgentInstructionChange" ], "sources": [ - "packages/context/agent-instructions/src/render.ts:47" + "packages/context/agent-instructions/src/render.ts:53" ] }, { @@ -24176,6 +24390,15 @@ "packages/llm/llm/src/types.ts:456" ] }, + { + "digest": "89b2edff547be1ecfcd78ef18aeeb3cf507fcbf7d6093ec04fb111ef4c8e8a97", + "names": [ + "packages/llm/llm/src/message.ts#UserMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:162" + ] + }, { "digest": "8a046b52331959aa3394680d6acbe09b8a5447c6b61375f263445bfcef19b7f1", "names": [], @@ -24197,7 +24420,7 @@ "digest": "8ad45ad053b4d89f8865c17cc9ff3b8748e86045f3f0fb816269a34af2619141", "names": [], "sources": [ - "packages/hooks/hook-protocol/src/types.ts:31" + "packages/experimental/hook-protocol/src/types.ts:31" ] }, { @@ -24271,7 +24494,7 @@ "digest": "8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543", "names": [], "sources": [ - "packages/core/tools/src/index.ts:34" + "packages/core/tools/src/index.ts:35" ] }, { @@ -24397,6 +24620,11 @@ "packages/web/web-search-deepseek/src/provider.ts:64" ] }, + { + "digest": "9a36727716d0e73f681f3b725e7f28f89417c68174c8af0ff2b1ef52b08326a0", + "names": [], + "sources": [] + }, { "digest": "9a410ddf56792b2b7cd4a224a2e54dc4dee30a8e8e6b1c2b9a1d19992b217adc", "names": [ @@ -24457,7 +24685,7 @@ "digest": "9edc162949abf93bd5ff049191b345454ba104f583937ce5580fb50f067d2b94", "names": [], "sources": [ - "packages/webhook/webhook/src/types.ts:74" + "packages/experimental/webhook/src/types.ts:74" ] }, { @@ -24544,13 +24772,6 @@ "packages/experimental/agent-team/src/types.ts:234" ] }, - { - "digest": "a5d80b5a2f3a6bbaf8f0334126632b78f94d8346122c68e8122ea89906d63d26", - "names": [], - "sources": [ - "packages/core/session/src/types.ts:311" - ] - }, { "digest": "a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc", "names": [], @@ -24559,10 +24780,10 @@ { "digest": "a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb", "names": [ - "packages/hooks/hook-protocol/src/types.ts#HookDialect" + "packages/experimental/hook-protocol/src/types.ts#HookDialect" ], "sources": [ - "packages/hooks/hook-protocol/src/types.ts:48" + "packages/experimental/hook-protocol/src/types.ts:48" ] }, { @@ -24589,7 +24810,7 @@ "names": [], "sources": [ "packages/core/session/src/types.ts:383", - "packages/core/tools/src/types.ts:24" + "packages/core/tools/src/types.ts:27" ] }, { @@ -24724,11 +24945,6 @@ "names": [], "sources": [] }, - { - "digest": "ae84c5e493f94acc63cfb70389073ba616ed7ae7aa4fadde048bdcc64d49bb46", - "names": [], - "sources": [] - }, { "digest": "aec150797b655c5f15ebb7a501454b0c208fbaccec9ace49d7de2171fc02f47d", "names": [], @@ -24744,6 +24960,15 @@ "names": [], "sources": [] }, + { + "digest": "af99c95d46bed802870f1ebefe0ea11f92a03cb37b8e0cbe3f94b7aaaf509b08", + "names": [ + "packages/llm/llm/src/message.ts#DeveloperMessage" + ], + "sources": [ + "packages/llm/llm/src/message.ts:157" + ] + }, { "digest": "aff60ef947c18b012644852bdd0d5bfe72065e061dd563787c76c646b4a10f6b", "names": [], @@ -24818,6 +25043,11 @@ "packages/experimental/agent-team/src/types.ts:71" ] }, + { + "digest": "b33545b3244861bcbf73b407d24b53402710f5acacab7ae939537e5504a68dc6", + "names": [], + "sources": [] + }, { "digest": "b3681c026a0b98e98b103f057dfd217fbe904129c090efd668253ec9aea7fece", "names": [], @@ -24934,11 +25164,6 @@ "packages/goal/goal/src/types.ts:45" ] }, - { - "digest": "b83ed1b1cfffbd7bd5cca06ea72e44be57beb68b39e5a96660ce42a9e21aa411", - "names": [], - "sources": [] - }, { "digest": "b8fc636a2121df9d8423c242e03c9d23b7ab7c6fdce00e45746932c60b730f97", "names": [ @@ -24968,6 +25193,11 @@ "names": [], "sources": [] }, + { + "digest": "ba5b10ea77d68f4a29439a9bd2a14f6a560acf55731749d57237757cc72fbc06", + "names": [], + "sources": [] + }, { "digest": "ba77b58995e36f14bb13886b0493d0a1a63d5db10b1a6cd9f6ac65a72cf832ec", "names": [], @@ -25149,13 +25379,9 @@ "sources": [] }, { - "digest": "cb7b863ddb7b3eab5f18f216602feba25f47b456e73e4e72b6bcc259875c6dab", - "names": [ - "packages/llm/llm/src/message.ts#Message" - ], - "sources": [ - "packages/llm/llm/src/message.ts:196" - ] + "digest": "cbbe8377ee744964537dbafd83607219e58ba79a13f6db0d0645c7ad3d5e8a09", + "names": [], + "sources": [] }, { "digest": "cbca49bde16b656d5b8fbe4a8a507769f7b594c6287782b809c03814941bc5e3", @@ -25184,20 +25410,20 @@ "packages/llm/llm-retry/src/types.ts:17" ] }, + { + "digest": "cef171adf021a1867d244618a3dbbaafc22beb17c7f3df3ee203393271c8c6f6", + "names": [ + "packages/core/tools/src/types.ts#PtcDispatchEventData" + ], + "sources": [ + "packages/core/tools/src/types.ts:21" + ] + }, { "digest": "cf6e3aaf1e2de6480aa0157730a41b9a492108a55304100b0f7e112711dd4331", "names": [], "sources": [] }, - { - "digest": "cfa4409ab0ef198a50a1121ad5013a740760444adc225b9ea816b4667c0158fd", - "names": [ - "packages/llm/llm/src/message.ts#UserMessage" - ], - "sources": [ - "packages/llm/llm/src/message.ts:162" - ] - }, { "digest": "cffcdc19881ab61ec0a9ce18743996f7ac44ac8889f34dfac36ae745574aa22f", "names": [ @@ -25224,6 +25450,11 @@ "packages/llm/llm/src/types.ts:457" ] }, + { + "digest": "d4c799e3d51cae1040362ba4968efc484739e14416c9c1df20c2f108b4ef2eb5", + "names": [], + "sources": [] + }, { "digest": "d4e4d575abeb4b72d616dc65c17ac0eaa943cc135609395044c38d5afcb10e31", "names": [], @@ -25239,6 +25470,11 @@ "names": [], "sources": [] }, + { + "digest": "d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5", + "names": [], + "sources": [] + }, { "digest": "d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c", "names": [], @@ -25249,6 +25485,15 @@ "names": [], "sources": [] }, + { + "digest": "d901c4f5e856591c3217af983caa15bba568d580700f0958bab56b88004eced9", + "names": [ + "packages/llm/llm/src/message.ts#Message" + ], + "sources": [ + "packages/llm/llm/src/message.ts:196" + ] + }, { "digest": "d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241", "names": [], @@ -25268,6 +25513,13 @@ "names": [], "sources": [] }, + { + "digest": "da60ed58a8fa23d7aa4920ab5c1f4e3d28109cb0797ef9e5c8b125721b847582", + "names": [], + "sources": [ + "packages/session/working-directory/src/index.ts:36" + ] + }, { "digest": "dacf3d5f3e4ff619c1320e64a48dec5e7ecd7870bece65fbd449a0e63adfa521", "names": [], @@ -25678,6 +25930,15 @@ "names": [], "sources": [] }, + { + "digest": "f731ffe80810b0289b3e694d8f40af6f01865c02a622ee457be90ca808b66e87", + "names": [ + "packages/subagent/subagent/src/catalog.ts#SubagentCatalogEvent" + ], + "sources": [ + "packages/subagent/subagent/src/catalog.ts:28" + ] + }, { "digest": "f789af5ab844827eb5a4f357c0afae1eeed108c6c22d8ca1a56e586b349a7c85", "names": [], @@ -25750,9 +26011,14 @@ "packages/skill/tool-skill/src/index.ts#SkillCatalogSource" ], "sources": [ - "packages/skill/tool-skill/src/index.ts:34" + "packages/skill/tool-skill/src/index.ts:35" ] }, + { + "digest": "fc351843b8ecbd05d739c4ca4fcd4bc7cfad6c0beacbbd48389959bb1bf06cf3", + "names": [], + "sources": [] + }, { "digest": "fcf427290c6213c2a7feb57492d832ce324f5b9737a759f424dab69e6bd101a0", "names": [], @@ -25773,15 +26039,6 @@ "names": [], "sources": [] }, - { - "digest": "fe3cdc6f9050584cea4a6d6998c31334bff7899f5ff1095082a44fdf3b47e996", - "names": [ - "packages/session/session-title-llm/src/index.ts#SessionTitleLlmRequestEventData" - ], - "sources": [ - "packages/session/session-title-llm/src/index.ts:34" - ] - }, { "digest": "fe792a6cd430494503837e21e79f91677704ccdd9e870c679aabd7d43ed47ddd", "names": [], diff --git a/docs/session-format-status.i18n.yaml b/docs/session-format-status.i18n.yaml index 833cd194f6..ef3b64d39c 100644 --- a/docs/session-format-status.i18n.yaml +++ b/docs/session-format-status.i18n.yaml @@ -15,11 +15,11 @@ en: 02d18ba8a66cfb10 zh: 54b753bbcb8f8a9e /session-format-version-and-release-status/finalization-record: - en: 9a1b2f4c61551ab7 - zh: 666a2339c47343e1 + en: 15b6134dcb3a09d5 + zh: 4260c82e79baa021 /session-format-version-and-release-status/release-record: - en: ee6e54ebc2b6dd37 - zh: 928343edfd3fb943 + en: 83ce79a241b630d7 + zh: 53f3c461ef731f67 /session-format-version-and-release-status/updating-the-record: en: 7eaa730a2d163bd6 zh: 53ed7618537db2f5 diff --git a/docs/session-format-status.md b/docs/session-format-status.md index 9df313da2f..78b6701095 100644 --- a/docs/session-format-status.md +++ b/docs/session-format-status.md @@ -36,17 +36,17 @@ V4 has an accepted compatibility baseline in the [checkpoint](persistence-change Finalization does not freeze every future V4 addition and does not assert publication. The release record below retains the independently verified published version. Ordinary comments, aliases, source locations, and implementation fixes preserving the accepted meaning do not change this baseline. -Before the first V4 publication, every integration of a newer V3-writing master must pass the [explicit V3 vocabulary check](cookbook/adding-a-session-format-version.md#final-v3-vocabulary) against the recorded local source commit. Verify the source pin’s freshness and review new event payload conversions before updating the migration-owned set. After publication, the final V3 vocabulary remains historical and independent of current V4 additions. +The [final V3 vocabulary](cookbook/adding-a-session-format-version.md#final-v3-vocabulary) remains historical and independent of current V4 additions. ## Release record ```yaml session-format-release -latestReleasedVersion: 3 -evidenceTag: dsh-v0.1.5-alpha.1 +latestReleasedVersion: 4 +evidenceTag: dsh-v0.2.0-rc.2 ``` -Evidence: published product tag `dsh-v0.1.5-alpha.1`; tagged writer: `packages/core/session/src/types.ts`. +Evidence: published product tag `dsh-v0.2.0-rc.2`; tagged writer: `packages/core/session/src/types.ts`. ## Updating the record diff --git a/docs/session-format-status.zh.md b/docs/session-format-status.zh.md index 31e1710632..141917a63c 100644 --- a/docs/session-format-status.zh.md +++ b/docs/session-format-status.zh.md @@ -36,17 +36,17 @@ V4 的已接受兼容性基线保存在[检查点](persistence-changes/finalized 定稿不冻结 V4 之后的每项新增,也不表示已发布。下方发布记录保留独立验证的已发布版本。普通注释、别名、源码位置,以及保留已接受含义的实现修复,不改变该基线。 -首次发布 V4 前,每次集成仍写入 V3 的较新 master 都必须对已记录的本地源提交执行[显式 V3 词汇校验](cookbook/adding-a-session-format-version.zh.md#final-v3-vocabulary)。更新迁移所有的集合前,要核验源 pin 的新鲜度并审查新事件 payload 的转换。发布后,最终 V3 词汇保持为历史定义,与当前 V4 新增事件无关。 +最终 [V3 词汇](cookbook/adding-a-session-format-version.zh.md#final-v3-vocabulary)保持为历史定义,与当前 V4 新增事件无关。 ## 发布记录 ```yaml session-format-release -latestReleasedVersion: 3 -evidenceTag: dsh-v0.1.5-alpha.1 +latestReleasedVersion: 4 +evidenceTag: dsh-v0.2.0-rc.2 ``` -证据:已发布产品标签 `dsh-v0.1.5-alpha.1`;该标签的写入器路径:`packages/core/session/src/types.ts`。 +证据:已发布产品标签 `dsh-v0.2.0-rc.2`;该标签的写入器路径:`packages/core/session/src/types.ts`。 ## 更新记录 diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 7051478b51..76288201ad 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -3,5 +3,5 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/README.md /subsystems: - en: ab6cd11804f6b644 - zh: 7a82dc5b22c40703 + en: 34b6bc8c15b8009b + zh: 7a07e5c32b3feca1 diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index e2f8dc51e2..0960b21527 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -14,10 +14,12 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [typert.md](typert.md) | Remote invocation descriptors, lookup/Context declarations, Typert registries, and the Host Gateway/Client API boundaries | | [goal.md](goal.md) | persisted goal identity, lifecycle snapshots, activation, change records, and round attribution | | [schedule.md](schedule.md) | Host-owned reminder records, durable transitions, active and ended views, and ordinary-conversation delivery | -| [todo.md](todo.md) | the todo package's whole-list item type, durable event ownership, projection, and open-turn invariant | +| [todo.md](todo.md) | the todo package's whole-list item type, durable event ownership, and projection | | [deliverables.md](deliverables.md) | what a turn hands to the user: `PresentedFile` deliveries from `present` and the Host-served `WorkspaceChangesSummary` of changed files from git snapshots | | [commands.md](commands.md) | the human-command registry service: definitions, adapter discovery, direct invocation, results, and parsing views | | [session.md](session.md) | the full `SessionEventMap` variant catalog, `TurnEndReason`, `deriveMessages()`, execution enclosure, and standalone events | +| [working-directory.md](working-directory.md) | Session execution directories, durable changes, recovery, and user context | +| [worktrees.md](worktrees.md) | Experimental Git branch and checkout creation under existing permissions | | [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, the JSONL provider, `session/flush`, crash recovery, `SessionHeader` | | [settings.md](settings.md) | the user-settings seam: `SettingsNamespace` registration, layered resolution (defaults → composition `base` → user document), owner scopes, hot commits | | [credentials.md](credentials.md) | the credential seam: `CredentialRef` references (never values) in configuration, per-operation resolution, UI-safe `CredentialInfo`, provider source layers | @@ -47,14 +49,15 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [compaction.md](compaction.md) | the compaction seam: the `compaction/*` session events, `CompactionResult`, the `CompactionEngine` interface | | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split | | [voice-input.md](voice-input.md) | experimental named recognizers, transient audio and revision-guarded draft insertion | +| [translation.md](translation.md) | experimental anonymous text translation and explicit provider requests | | [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG | +| [claude-code-mods.md](claude-code-mods.md) | Claude Code mods through the experimental bridge: every event, `$` member, chain rule, drawing feature, and example mod that differs from Claude Code | | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` | | [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` | | [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality | | [jobs.md](jobs.md) | the background-job runtime: branded `JobId`s, the producer contract, consumer views, and `ctx.jobs` service behavior | | [permission-presets.md](permission-presets.md) | the permission-preset layer: `PresetSpec`/`PresetOption`, the derived `custom` state, the log-only `permission/preset` event | | [plan.md](plan.md) | plan mode: the log-only `plan/mode` state, pending-selection flush, `PlanModeConfig`, the `exit_plan_mode` review arc | -| [invariants.md](invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract | | [web-server.md](web-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the claimable fallback seat, index taps | | [webhook.md](webhook.md) | authenticated provider deliveries, arbitrary programmatic rules, and fire-and-forget Workspace Session creation | | [storage.md](storage.md) | the storage subsystem: the backend contract (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 31f4489d43..e5f33a3060 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -14,10 +14,12 @@ | [typert.md](typert.zh.md) | 远程调用描述符、lookup/Context 声明、Typert 注册表,以及 Host Gateway/Client API 边界 | | [goal.md](goal.zh.md) | 持久 goal 标识、生命周期快照、激活、变更记录与 Round 归属 | | [schedule.md](schedule.zh.md) | Host 拥有的提醒记录、持久转换、活动和已结束视图与普通对话交付 | -| [todo.md](todo.zh.md) | todo 包的整列表条目类型、持久事件所有权、投影和未结束轮次不变式 | +| [todo.md](todo.zh.md) | todo 包的整列表条目类型、持久事件所有权和投影 | | [deliverables.md](deliverables.zh.md) | 一轮交给用户的东西:`present` 声明的 `PresentedFile` 交付,以及由 git 快照得出、由 Host 提供的 `WorkspaceChangesSummary` 改动摘要 | | [commands.md](commands.zh.md) | 人类命令注册表服务:定义、适配器发现、直接调用、结果与解析视图 | | [session.md](session.zh.md) | 完整的 `SessionEventMap` 变体目录、`TurnEndReason`、`deriveMessages()`、执行封闭与独立事件 | +| [working-directory.md](working-directory.zh.md) | Session 执行目录、持久化变更、恢复与用户上下文 | +| [worktrees.md](worktrees.zh.md) | 现有权限下的实验性 Git 分支与检出目录创建 | | [persistence.md](persistence.zh.md) | 持久性 seam:`SessionPersistence`、JSONL 提供方、`session/flush`、崩溃恢复、`SessionHeader` | | [settings.md](settings.zh.md) | 用户设置 seam:`SettingsNamespace` 注册、分层解析(默认值 → 组合 `base` → 用户文档)、owner scope、热提交 | | [credentials.md](credentials.zh.md) | 凭据 seam:配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、提供方来源层 | @@ -47,14 +49,15 @@ | [compaction.md](compaction.zh.md) | 压缩(compaction)seam:`compaction/*` 会话事件、`CompactionResult`、`CompactionEngine` 接口 | | [subagent.md](subagent.zh.md) | subagent seam:命名提供方注册表、`SubagentStartRequest`/`Result`/`Run`、启动时与运行时能力拆分 | | [voice-input.md](voice-input.zh.md) | 实验性具名语音识别、临时音频和受版本保护的草稿插入 | +| [translation.md](translation.zh.md) | 实验性免登录文本翻译与显式 Provider 请求 | | [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG | +| [claude-code-mods.md](claude-code-mods.zh.md) | 经实验性桥接运行的 Claude Code 模组:与 Claude Code 不同的每个事件、`$` 成员、链规则、绘制特性与示例模组 | | [web.md](web.zh.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` | | [spill.md](spill.zh.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` | | [workflow.md](workflow.zh.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | | [jobs.md](jobs.zh.md) | 后台任务运行时:品牌化 `JobId`、producer 约定、消费方视图和 `ctx.jobs` 服务行为 | | [permission-presets.md](permission-presets.zh.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 | | [plan.md](plan.zh.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 | -| [invariants.md](invariants.zh.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件约定 | | [web-server.md](web-server.zh.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、可认领的回退席位、index 渲染挂接点 | | [webhook.md](webhook.zh.md) | 通过身份验证的提供方交付、任意程序化规则,以及发起 Workspace 会话创建后不等待结果 | | [storage.md](storage.zh.md) | 存储子系统:后端约定(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | diff --git a/docs/subsystems/attachment.md b/docs/subsystems/attachment.md index db1c1cfa15..12ae675e3c 100644 --- a/docs/subsystems/attachment.md +++ b/docs/subsystems/attachment.md @@ -303,7 +303,7 @@ Source: [`packages/attachment/attachment/src/index.ts`](../../packages/attachmen ### `ctx.fileUploads` — `FileUploads` -Host service owning upload storage and Agent-scoped staged receipts. +Host service owning upload storage and receipts keyed by each receiving Agent's exact Session. ```ts cordis-catalog /** @@ -330,7 +330,7 @@ registerAgentResolver(resolve: AgentResolver): () => void async uploadStream(request: { readonly sessionId: SessionId readonly data: AsyncIterable readonly signal?: AbortSignal readonly name?: string }): Promise /** - * Resolve one staged receipt inside its receiving Agent scope. + * Resolve one staged receipt for the receiving Agent's exact Session. * @param agent - receiving Agent. * @param receiptId - opaque receipt minted for one completed upload. * @returns durable file reference, or `undefined` for an unknown or foreign receipt. diff --git a/docs/subsystems/attachment.zh.md b/docs/subsystems/attachment.zh.md index 82379de504..06ea726f19 100644 --- a/docs/subsystems/attachment.zh.md +++ b/docs/subsystems/attachment.zh.md @@ -303,7 +303,7 @@ Source: [`packages/attachment/attachment/src/index.ts`](../../packages/attachmen ### `ctx.fileUploads` — `FileUploads` -Host service owning upload storage and Agent-scoped staged receipts. +Host service owning upload storage and receipts keyed by each receiving Agent's exact Session. ```ts cordis-catalog /** @@ -330,7 +330,7 @@ registerAgentResolver(resolve: AgentResolver): () => void async uploadStream(request: { readonly sessionId: SessionId readonly data: AsyncIterable readonly signal?: AbortSignal readonly name?: string }): Promise /** - * Resolve one staged receipt inside its receiving Agent scope. + * Resolve one staged receipt for the receiving Agent's exact Session. * @param agent - receiving Agent. * @param receiptId - opaque receipt minted for one completed upload. * @returns durable file reference, or `undefined` for an unknown or foreign receipt. diff --git a/docs/subsystems/boot.i18n.yaml b/docs/subsystems/boot.i18n.yaml index 4d5b515bea..52d584bd1f 100644 --- a/docs/subsystems/boot.i18n.yaml +++ b/docs/subsystems/boot.i18n.yaml @@ -6,5 +6,5 @@ en: 962b4398c9052959 zh: 14471c340a9f6e60 /profile-management/management-records: - en: 6929d1001684c024 - zh: 06d3d3f6eea848ec + en: a10d3f358e9b7c8e + zh: 28b0f44589a5020d diff --git a/docs/subsystems/boot.md b/docs/subsystems/boot.md index 142ea14b59..be79a823a3 100644 --- a/docs/subsystems/boot.md +++ b/docs/subsystems/boot.md @@ -6,17 +6,19 @@ The [boot package group](../../packages/boot/README.md) owns launcher-provided p ## Management records +`ProfilePatch` adds an optional literal `preset` row id to native Include patch fields; `id` then targets a child in that preset. [App-boot](../../packages/boot/app-boot/README.md#profiles) owns compilation, ordering, validation, and path resolution. + `PluginEntryId` identifies one Loader entry; callers obtain it from `listPlugins` rather than constructing a patch id. `PluginInfo` carries module identity, effective enablement, fiber phase and optional display `meta`, plus a unique `patchId` or a `readOnlyReason`. -`BundleInfo` carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies and the installation does not, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time. +`BundleInfo.official` identifies project-maintained shipped optional bundles and on-demand catalog entries independently of installation. `availability` identifies a readable installation-provided or profile package, or `missing`; on-demand catalog entries exclude undeclared transitive copies. `installed` records the profile dependency declaration. An on-demand `installTarget` carries the host-derived exact spec and version. `BundleInfo` also carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies and the installation does not, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time. -`InstallBundleOptions.enabled` defaults to true. False installs without selecting the bundle layer. `approvedBuilds` grants persistent script permission to the supplied pending package names before installation. `registry` names the registry asked first; absent, the configured one. +`InstallBundleOptions.saveExact` passes `--save-exact` to the shared package installer. `InstallBundleOptions.enabled` defaults to true. False installs without selecting the bundle layer. `approvedBuilds` grants persistent script permission to the supplied pending package names before installation. `registry` names the registry asked first; absent, the configured one. `PluginRegistries` carries the configured first registry, `null` for the one pnpm's own configuration names, the fallbacks asked after it, and `resolved`, the URL pnpm's own configuration names or `null` while unread. `InspectOptions.registry` names the registry a lookup asks first. -`ChangeResult.changed` reports a disk edit independently of `application`: `applied`, `restart-required`, `overridden` or `failed`. Optional `error` carries a localizable code and external diagnostic. `packageResult` records the pnpm exit code, bounded output, truncation flag and complete diagnostic log path, plus `timedOut` when the manager terminated a run that stopped printing. A terminated run is classified `timeout` whatever exit status the signal left behind, so installation and removal report failure instead of success and no further registry is asked. `pendingBuilds` lists undecided packages across the profile; `approvedBuilds` records the names granted permission by this operation; `registries` lists the registries an installation asked, in order; `failedAt` says whether the last failed run could not reach the registry it asked or the host a git or tarball spec is fetched from. +`ChangeResult.changed` reports a disk edit independently of `application`: `applied`, `restart-required`, `overridden` or `failed`. Optional `error` carries a localizable code and external diagnostic. `packageResult` records the pnpm exit code, bounded output, truncation flag and complete diagnostic log path, plus `timedOut` when the manager terminated a run that stopped printing. A terminated run is classified `timeout` whatever exit status the signal left behind, so installation and removal report failure instead of success and no further registry is asked. `pendingBuilds` lists undecided packages across the profile; `approvedBuilds` records the names granted permission by this operation; `registries` lists the registries an installation asked, in order; `bundle` and `version` name the package a finished installation added and its manifest version; `failedAt` says whether the last failed run could not reach the registry it asked or the host a git or tarball spec is fetched from. @@ -30,7 +32,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp ### `ctx.configEditor` — `ConfigEditor` -Persist complete raw configs and apply them through the normal Loader path. +Persist Host entry configs without changing preset-scoped operations, then reconcile through Loader. ```ts cordis-catalog /** Addressable profile rows; nested Includes have independent configuration ownership. @@ -109,12 +111,12 @@ Manage profile files and apply their declared reload lifecycle. */ @Remote async listPlugins(): Promise -/** Read the profile's installed bundles, the bundles this dsh installation supplies, and the selected names that are not bundles. +/** Read installed, installation-provided, and offline Official catalog bundles, plus selected non-bundle names. * A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected. * @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional * display metadata, activation selections, whether the installation offers the bundle, and removal availability. */ -@Remote listBundles(): Promise +@Remote async listBundles(): Promise /** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names. * @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now. @@ -151,7 +153,7 @@ Manage profile files and apply their declared reload lifecycle. * `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay. * @param spec One package spec, including local paths relative to the invocation directory. * @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names, - * the pending build scripts to allow for this profile before pnpm runs, and the registry asked first. + * the pending build scripts to allow, whether to save an exact dependency, and the registry asked first. * @returns Package-manager diagnostics, the registries asked, and the observed activation outcome. */ @Remote installBundle(spec: string, options?: InstallBundleOptions): Promise @@ -170,8 +172,9 @@ Manage profile files and apply their declared reload lifecycle. */ @Remote async cancelInstall(requestId: PluginInstallRequestId): Promise -/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path. - * @param name Installed dependency name. +/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path; a selected name no + * dependency holds is only deselected. + * @param name Installed dependency or selected bundle name. * @returns Removal diagnostics and the remaining profile state. */ @Remote removeBundle(name: string): Promise diff --git a/docs/subsystems/boot.zh.md b/docs/subsystems/boot.zh.md index 4495ccbd73..93024b965a 100644 --- a/docs/subsystems/boot.zh.md +++ b/docs/subsystems/boot.zh.md @@ -6,17 +6,19 @@ ## 管理记录 +`ProfilePatch` 在原生 Include patch 字段上增加可选的字面量 `preset` 条目 id;此时 `id` 指向该 preset 内的子条目。[App-boot](../../packages/boot/app-boot/README.zh.md#profiles) 定义编译、顺序、校验与路径解析规则。 + `PluginEntryId` 标识一个 Loader 条目;调用方从 `listPlugins` 获取,不自行拼接 patch id。 `PluginInfo` 包含模块标识、实际启停状态、fiber 阶段和可选的展示 `meta`,以及唯一的 `patchId` 或 `readOnlyReason`。 -`BundleInfo` 包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供、且安装不提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。 +`BundleInfo.official` 标识项目维护的随附可选组合包和按需目录项,独立于安装状态。`availability` 表示可读取的安装随附包、profile 包或 `missing`;按需目录项不计入未声明的传递依赖副本。`installed` 记录 profile 的依赖声明。按需条目的 `installTarget` 包含 Host 生成的精确 spec 和版本。`BundleInfo` 还包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供、且安装不提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。 -`InstallBundleOptions.enabled` 默认为 true,false 表示安装但不选择组合包层。`approvedBuilds` 在安装前向指定的待审批包名授予持久脚本权限。`registry` 指定首先询问的注册表;缺省为配置的那个。 +`InstallBundleOptions.saveExact` 向共享包安装器传入 `--save-exact`。`InstallBundleOptions.enabled` 默认为 true,false 表示安装但不选择组合包层。`approvedBuilds` 在安装前向指定的待审批包名授予持久脚本权限。`registry` 指定首先询问的注册表;缺省为配置的那个。 `PluginRegistries` 携带配置的第一个注册表(`null` 即 pnpm 自身配置指定的那个)、随后依次询问的备选注册表,以及 `resolved`——pnpm 自身配置指向的 URL,未读到时为 `null`。`InspectOptions.registry` 指定一次查询首先询问的注册表。 -`ChangeResult.changed` 报告磁盘修改,独立于 `application`:`applied`、`restart-required`、`overridden` 或 `failed`。可选的 `error` 包含可本地化的错误码和外部诊断。`packageResult` 记录 pnpm 退出码、有界输出、截断标志及完整诊断日志路径;当管理器终止了一个停止打印的运行,还记录 `timedOut`。被终止的运行不论信号留下什么退出状态都归类为 `timeout`,因此安装与删除都报告失败而非成功,也不会再询问下一个注册表。`pendingBuilds` 列出整个 profile 尚未决定的包;`approvedBuilds` 记录本次操作授予权限的包名;`registries` 按顺序列出一次安装问过的注册表;`failedAt` 说明最后一次失败的运行连不上的是所问的注册表,还是 git 或 tarball spec 自身拉取的主机。 +`ChangeResult.changed` 报告磁盘修改,独立于 `application`:`applied`、`restart-required`、`overridden` 或 `failed`。可选的 `error` 包含可本地化的错误码和外部诊断。`packageResult` 记录 pnpm 退出码、有界输出、截断标志及完整诊断日志路径;当管理器终止了一个停止打印的运行,还记录 `timedOut`。被终止的运行不论信号留下什么退出状态都归类为 `timeout`,因此安装与删除都报告失败而非成功,也不会再询问下一个注册表。`pendingBuilds` 列出整个 profile 尚未决定的包;`approvedBuilds` 记录本次操作授予权限的包名;`registries` 按顺序列出一次安装问过的注册表;`bundle` 与 `version` 给出完成的安装新增的包及其清单版本;`failedAt` 说明最后一次失败的运行连不上的是所问的注册表,还是 git 或 tarball spec 自身拉取的主机。 @@ -30,7 +32,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp ### `ctx.configEditor` — `ConfigEditor` -Persist complete raw configs and apply them through the normal Loader path. +Persist Host entry configs without changing preset-scoped operations, then reconcile through Loader. ```ts cordis-catalog /** Addressable profile rows; nested Includes have independent configuration ownership. @@ -109,12 +111,12 @@ Manage profile files and apply their declared reload lifecycle. */ @Remote async listPlugins(): Promise -/** Read the profile's installed bundles, the bundles this dsh installation supplies, and the selected names that are not bundles. +/** Read installed, installation-provided, and offline Official catalog bundles, plus selected non-bundle names. * A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected. * @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional * display metadata, activation selections, whether the installation offers the bundle, and removal availability. */ -@Remote listBundles(): Promise +@Remote async listBundles(): Promise /** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names. * @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now. @@ -151,7 +153,7 @@ Manage profile files and apply their declared reload lifecycle. * `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay. * @param spec One package spec, including local paths relative to the invocation directory. * @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names, - * the pending build scripts to allow for this profile before pnpm runs, and the registry asked first. + * the pending build scripts to allow, whether to save an exact dependency, and the registry asked first. * @returns Package-manager diagnostics, the registries asked, and the observed activation outcome. */ @Remote installBundle(spec: string, options?: InstallBundleOptions): Promise @@ -170,8 +172,9 @@ Manage profile files and apply their declared reload lifecycle. */ @Remote async cancelInstall(requestId: PluginInstallRequestId): Promise -/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path. - * @param name Installed dependency name. +/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path; a selected name no + * dependency holds is only deselected. + * @param name Installed dependency or selected bundle name. * @returns Removal diagnostics and the remaining profile state. */ @Remote removeBundle(name: string): Promise diff --git a/docs/subsystems/claude-code-mods.i18n.yaml b/docs/subsystems/claude-code-mods.i18n.yaml new file mode 100644 index 0000000000..67dce12eae --- /dev/null +++ b/docs/subsystems/claude-code-mods.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for claude-code-mods.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/claude-code-mods.md +/claude-code-mods-compatibility: + en: e520df3bf44e1160 + zh: 557956eaf6faf4a7 +/claude-code-mods-compatibility/packaging-and-loading: + en: 75cf021ce8babaab + zh: caa20540f8bce11a +/claude-code-mods-compatibility/events: + en: cea776ec0063c311 + zh: 6b8c8b32f2f7ede9 +/claude-code-mods-compatibility/the-chain: + en: 111c186536345342 + zh: 8ddc81c78c05a840 +/claude-code-mods-compatibility/members: + en: 11c3d07e9deea30a + zh: 3a47a1114869d845 +/claude-code-mods-compatibility/drawing: + en: 1bd1a60970fcf426 + zh: 8f6a8e069f742fb7 +/claude-code-mods-compatibility/testing: + en: 8b3530b79e09a067 + zh: c041e63f7fd8de5a +/claude-code-mods-compatibility/the-example-mods: + en: b3ef5ea831c5c48f + zh: fa64186430e7c772 diff --git a/docs/subsystems/claude-code-mods.md b/docs/subsystems/claude-code-mods.md new file mode 100644 index 0000000000..1d125affae --- /dev/null +++ b/docs/subsystems/claude-code-mods.md @@ -0,0 +1,146 @@ +# Claude Code mods: compatibility + +English | [中文](claude-code-mods.zh.md) + +Every way a mod behaves differently through this bridge than under Claude Code, as of Claude Code 2.1.287 ([mods reference](https://code.claude.com/docs/en/plugins/mods/reference), its `claude-code.d.ts`) and the [Getting started with Claude Code mods](https://claude.dev/blog/getting-started-with-claude-code-mods/) post of 2026-10-01. A row names the Claude Code behavior, what this bridge does, and the reason or the follow-up. Anything not listed here behaves as the reference describes; a hook on an event this host never raises is named in a warning when the mod loads, and a `$` call outside the served table rejects with `no implementation for .`. + +## Packaging and loading + +| Claude Code | This bridge | Reason or follow-up | +|---|---|---| +| A plugin directory with `.claude-plugin/plugin.json` and `hooks/hooks.json` naming one hooks module; `claude --plugin-dir`, marketplaces, `/plugin install` | A mod is a plugin: `defineMod({ name, version, root, userConfig, register })` wraps the hooks module and mounts through cordis.yml after the bridge; `plugin.json` and `hooks.json` are not read | Mods join the one composition mechanism every DSH plugin uses; the example directories keep their manifests so they also run under `claude --plugin-dir` | +| `userConfig` schema in `plugin.json` with typed defaults; options validated at load | `userConfig` is the defaults object of the `defineMod` spec; the plugin's cordis.yml `config` overlays it and is validated as a record of strings, numbers, booleans, and string lists | No manifest is read | +| Tiers (`user`, `project`, `managed`) and `next.to(e, tier)` | Every mod loads as `user`; `tier()` in a test is accepted and ignored; `next.to` rejects | Managed settings are not a DSH concept | +| Hot reload of the hooks module on edit; `claude plugin validate`, `claude plugin test`, types generation from `types/index.d.ts` | None of these commands; a remount re-runs `register` on the same evaluated module, so module-level variables keep their values | Follow-up only on demand; the test kit's `createModTestKit` replaces `claude plugin test` inside Vitest | +| A hooks module may be TypeScript | A `.ts` hooks module loads only where the launcher transpiles (the source launch does; a built install does not) | DSH ships plain Node | +| Mods run in-process with `$` as their only access to the host | The hooks module runs in-process with Node's globals, no access rule, and the process's full authority: `$.env` reads and writes the harness environment, `$.http.fetch` reaches any URL, `$.fs` and `$.tool.call` act as the session | No sandbox is applied to mods; mount only mods you would run as a plugin | +| `import type { … } from 'claude-code'`, `declare module 'claude-code' { interface PluginState }` | The `claude-code` module name resolves only inside this repository's test setup, to the bridge's types; a mod outside it imports `@deepseek-ai/dsh-experimental-claude-code-mods` | The type names are the bridge's own | +| `settings hooks` in `hooks.json` run beside mod hooks | Not run; mount `@deepseek-ai/dsh-hooks-claude-code` for them | Different bridge | + +## Events + +| Event | Claude Code | This bridge | +|---|---|---| +| `session.start` | Raised once per session, before the first prompt | Raised for a root agent from `agent/created`, awaited before its first turn; not raised for subagents | +| `session.end` | Raised once per session | Raised for a root agent from `agent/disposed`; `$.state` is readable until the hooks settle | +| `prompt.submit` | `e.text` is the typed prompt; `context` entries become one block each after the prompt as typed; a prompt a plugin submitted carries `origin: { kind: 'plugin', name }` | Same; `e.text` joins the text blocks of the human's own (`user`-sourced) messages in the claimed batch, a batch of injected context alone raises nothing, and a prompt `$.prompt.submit` queued carries the submitting mod as its origin | +| `turn.start`, `turn.complete` | Per turn; subagent turns carry `agentId` | Same; `turn.complete`'s `{ text }` reaches the host log, not a line under the answer; `durationMs` counts from `turn/start` | +| `tool.call` | Runs before Claude Code's permission check; `next({ ...e, command })` rewrites the arguments the tool runs with, and `next({ ...e, tool })` reroutes the call | Runs around `tools/execute`, after the harness permission decision; a hook (or `.catch` handler) that rewrites arguments or names another tool is skipped with a report — the argument case names the [pre-tool input rewrite proposal](../../.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md) — because the logged call runs as logged. `{ deny }`, `{ result }`, observing, and rewriting the result or `isError` after `next` work as described | +| `command.run` | Runs for a command the mod registered | Same | +| `ui.render` | Raised for every render site (`AbovePrompt`, `Pane`, `ToolUse`, `AssistantMessage`, `Spinner`, …) | Raised for `AbovePrompt` only, once per redraw of a session's band; `Pane` hooks run only through the test kit's `$.ui.mount({ component: 'Pane' })` | +| `tool.check`, `tool.describe`, `tool.list`, `tool.register` | Shape the tool list and permissions | Not raised; `$.tool.register` and `$.tool.list` are served as `$` calls | +| `turn.step` | Per model step | Not raised | +| Every other reference event: the other `prompt.*` events, `skill.prompt`, `attribution.text`, `command.describe`, `config.*`, the other `session.*` events, `agent.*`, `plugin.register`, `engine.create`, `telemetry.*`, `ui.resolve`, `ui.press`, `ui.input`, `ui.select`, `ui.focus`, `ui.scroll`, `ui.close`, `ui.message` | Raised by the engine | Registered, never raised, named in a warning at load | +| `classic.*` | Classic settings hooks as events | Refused at `register`, like any unknown event name | +| `.` op events (`fs.read`, `store.set`, …) | A later-loaded mod's `$` call reaches the mods loaded before it | Same; `tool.call` arrives through the tool pipeline instead | + +## The chain + +| Claude Code | This bridge | Reason | +|---|---|---| +| A hook may call `next` more than once; each call runs the chain beneath again | `next` runs once: a second call returns the first run's result | One tool execution per logged call | +| A hook that answers while its `next` is still running | The run beneath is awaited before the hook's answer settles; a failure beneath after the answer is reported | The event settles only when nothing it started is running | +| 10 s own-time budget, 1 s for `.catch`, time inside `next` and `$` calls excluded except `$.clock.sleep` | Same, configurable (`hookTimeoutMs`, `catchTimeoutMs`) | — | +| Not returning a result object is a failure | Same; `null` is accepted as an answer (a surface drawn empty) | `ui.render` with nothing to draw | +| A result's unknown fields are dropped and wrong types fail the hook | A `prompt.submit` result with wrong types keeps the original text and only the string context lines | Lenient on the one event where partial results are common | + +## `$` members + +| Namespace | Claude Code | This bridge | +|---|---|---| +| `$.plugin` | `name`, `root`, `tier` | `name`, `root` from the `defineMod` spec | +| `$.ui` | `resolve`, `invalidate`, `open`, `close`, `panes`, `focus`, `log`, `toast`, `status`, `notice`, `ask`, `blit`, `copy`, `press`, `input`, `select` | `resolve`, `invalidate`, `open`, `close`, `panes`, `log`, `toast`, `status`, `ask`; `open` answers `{ isPlaced: false }` with a reason and redraws the band; `log`, `toast`, `status` reach the host log; the rest reject | +| `$.command` | `register`, `run`, `list` | Same; `run` of a command nobody answers rejects with the registering mod named | +| `$.tool` | `register`, `call`, `list` | Same; a registered tool is `mcp____`; `call` runs the harness pipeline and injects a tool's deferred contexts into the session | +| `$.prompt` | `submit`, `compose` | `submit`; the message is `user`-sourced, framed `Message from the "" mod:` unless `asUser`, and the `prompt.submit` it raises names the mod as origin | +| `$.session` | `id`, `cwd`, `root`, `repo`, `model`, `turns`, `messages`, `usage`, `version`, `send`, `append`, `authorize`, `compact`, `surfaces` | `id`, `cwd`, `root`, `model`, `turns`, `messages`, `usage`, `version`; `cwd` reports the committed current directory; `root` retains the original project; `usage.context.window` is `0` and `percent` absent until the token meter knows the route's window; `rateLimits` is empty; `version` names this bridge | +| `$.state` | Per session, survives hot reload | Per session, in memory; a read during `ui.render` subscribes the band to the slot | +| `$.store` | Per plugin, durable | Same, in the `claude_code_mods` storage domain, 4 MiB per plugin | +| `$.clock` | `now`, `sleep`, `after`, `every`; timers die with the plugin | Same; a timer also dies with the session whose event scheduled it, and `sleep` rejects when the event is cancelled | +| `$.fs` | `read`, `write`, `list`, `exists`, `stat`, `ancestors` | All but `ancestors`; 4 MiB per file; `stat.mtimeMs` is `0` | +| `$.process` | `run`, `spawn` | `run` only, argv without a shell, `processTimeoutMs` default | +| `$.http` | `fetch` | Same, bodies up to 4 MiB | +| `$.env` | `get`, `set` | Same; the process environment is shared by every session and plugin | +| `$.model`, `$.agent`, `$.config`, `$.settings`, `$.mcp`, `$.audio`, `$.telemetry`, `$.turn` | Served | Reject with `no implementation`; `$.model.complete` waits on a logged side-request event | + +## Drawing + +| Claude Code | This bridge | +|---|---| +| `Box`, `Text`, `Button`, `Input`, `Spacer`, and the host's own element kinds | `Box`, `Text`, `Button` from `$.ui.resolve(e)`; the tree is validated, and a `Button`'s `onPress` stays in the host behind a per-drawing action id | +| `Box` props: `flexDirection`, `padding*`, `gap`, `border*`, `width`, `height`, `overflow`, `scroll` | `flexDirection`, `padding`, `paddingX`, `paddingY`, `gap`, `border`, `borderColor`; the others serialize and the Web band ignores them | +| `Text` props: `color`, `bold`, `dimColor`, `italic`, `underline`, `wrap`, `truncate` | `color` (the terminal palette names), `bold`, `dimColor`, `italic`, `underline`; the Web band wraps | +| `Button` props: `label`, `hotkey`, `disabled`, `onPress`; a bare digit arms the hotkey | `label`, `hotkey` (shown as a hint), `disabled`, `onPress` (a click) | +| The band is one instance; `hasSurvey`, `isWorking`, `maxRows`, `bodyColumns` reflect the terminal | One instance per session, consulted in mod load order; `bodyColumns` and `viewport.columns` are the bridge's `bandColumns` (default `120`), `maxRows` its `bandRows` (default `10`), `hasSurvey` and `isWorking` are `false`; the Web band wraps instead of scrolling | +| The band redraws on the host's frame and when a `$.state` value a render read changes | Redraws after `session.start`, when a `$.state` value the last drawing read is written, after `$.ui.open`, `$.ui.close`, `$.ui.invalidate`, after each `tool.call` chain and `turn.complete` settle, and after a press; a mod that changes module-level state at other times draws it at the next trigger. A press runs `onPress` without a time limit | +| `Pane` with `focus`, `scroll`, docked beside the transcript | No pane: `$.ui.open` answers `{ isPlaced: false }` so a mod falls back to the band; the test kit mounts `Pane` hooks directly | +| Hotkeys, `TextInput`, focus, scrolling, `ui.press`, `ui.input`, `ui.select` | Not served; follow-up | + +## Testing + +| `claude-code/testing` | This bridge | +|---|---| +| `describe`, `test(name, ($, on) => …)`, `expect`, `mock`, `tier` | Inside this repository the same module name provides them over Vitest; `test` infers the mod from the test file's location (`/tests/*.test.ts` → `/index.ts`) and `defineModTests` overrides it. Outside, `createModTestKit` and `mock` come from `@deepseek-ai/dsh-experimental-claude-code-mods/testing` | +| `on` stubs the engine's answers; the kit answers `ui.*` itself | Same; `ui.open` answers `{ isPlaced: false }`, `ui.invalidate` and `ui.close` succeed; `session.cwd`, `process.run`, `fs.*`, `store.*`, `env.*` need a stub (`mock.store`, `mock.env`, `mock.clock` answer whole namespaces) | +| `$.ui.mount({ plugin, surface, component, props })` → `find`, `findAll`, `press`, `unmount` | Same, plus `tree()` and `text()`; every read renders afresh through every loaded mod's hooks; `plugin` and `surface` are accepted for source compatibility | +| 5 s hook budget in tests | Same (`budgetMs`) | + +## The example mods + +| Mod | Source | Through this bridge | +|---|---|---| +| Token Weather | The post's module, type contract, and test, unchanged | Runs as published; the test's three `as any` casts are dropped because the kit is typed. Draws in the band from `$.session.usage` through the token meter | +| Blast Radius | The post's `tool.call` hook unchanged; `classify`, `measure`, and the trees completed to the post's description | Holds the command with real `sleep` polls, draws the report in the band (`isPlaced: false`), and Proceed or Cancel decides; load it before Token Weather | +| Replay Theater | The post's five hooks unchanged; `stepsFor`, the diff, `openReplay`, the pane and the hint completed | Records Edit and Write calls, hints in the band after the turn, and steps through the diffs in the band because no pane is placed | +| `diff` ([anthropics/claude-code](https://github.com/anthropics/claude-code/tree/main/mods/diff), read at `52c7644`) | TypeScript, ~570 files, hooks `command.run`, `prompt.submit`, `session.start`, `tool.call`, `ui.close`, `ui.focus`, `ui.render`, `ui.scroll` | Not runnable: needs `$.settings.read`, `$.telemetry.*`, `$.session.messages` over a pane, `ui.focus` and `ui.scroll` events, and a placed `Pane`. Assessed by reading; the repository's terms do not allow a fixture copy | +| `agents-md` (same repository) | Hooks `agent.spawn`, `prompt.context`, `session.start`, `tool.call` | Not runnable: `prompt.context` and `agent.spawn` are not raised and `$.fs.ancestors`, `$.telemetry.*` are not served | +| `sec-default` (same repository) | Hooks `tool.check`, `prompt.compose`, `settings.read`, `classic.*`, `plugin.register`, … | Not runnable: managed-tier policy over events this host does not raise | +| `telemetry` (same repository) | Hooks `engine.create`, `telemetry.*`, `session.start`, `session.end`; calls `$.session.repo`, `$.settings.read` | Not runnable: `telemetry.*` and `engine.create` are not raised and the two `$` members are not served | + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.claudeCodeMods` — `ClaudeCodeMods` + +The bridge service: loaded mods, their hooks, and the harness listeners that raise their events. Mods join through ClaudeCodeMods.add, which defineMod calls when a mod plugin mounts. The Remote methods let a Client draw each session's band above the prompt and press its buttons. + +```ts cordis-catalog +/** + * Watch the band above the prompt of one session: the current drawing, then + * every redraw, until the Client stops watching. + * @param agent - the session's agent, resolved by the Gateway. + * @param signal - carrier cancellation. + * @returns the band's snapshots. + */ +@Remote({ mode: 'stream' }) watchBand(agent: Agent, signal: AbortSignal): AsyncIterable + +/** + * Press a button of the band's current drawing: runs the mod's `onPress` and redraws. + * @param agent - the session's agent, resolved by the Gateway. + * @param generation - the drawing the Client saw. + * @param actionId - the button's action id in that drawing. + * @returns the snapshot after the press. + */ +@Remote pressBand(agent: Agent, generation: number, actionId: string): Promise + +/** + * Load one mod beneath every mod loaded before it: run its `register`, + * keep its hooks, and report which of its events this host never raises. + * @param definition - the mod as its plugin defined it. + * @returns the disposer that removes the mod's hooks, closes its timers, and releases its registrations. + * @throws Error when the name is invalid or taken, or when `register` throws (Claude Code's `hooks module did not load` wording). + */ +async add(definition: ModDefinition): Promise<() => Promise> +``` + +Types: [Agent](core.md) + +Source: [`packages/experimental/claude-code-mods/src/index.ts`](../../packages/experimental/claude-code-mods/src/index.ts) + diff --git a/docs/subsystems/claude-code-mods.zh.md b/docs/subsystems/claude-code-mods.zh.md new file mode 100644 index 0000000000..696b6af1e7 --- /dev/null +++ b/docs/subsystems/claude-code-mods.zh.md @@ -0,0 +1,146 @@ +# Claude Code 模组:兼容性 + +[English](claude-code-mods.md) | 中文 + +模组经本桥接运行时与在 Claude Code 下行为不同的每一处,基准为 Claude Code 2.1.287([模组参考](https://code.claude.com/docs/en/plugins/mods/reference)及其 `claude-code.d.ts`)与 2026-10-01 的 [Getting started with Claude Code mods](https://claude.dev/blog/getting-started-with-claude-code-mods/) 一文。每行给出 Claude Code 的行为、本桥接的做法,以及原因或后续工作。未列出的一切均按参考文档描述的方式运行;对本宿主永不触发的事件注册钩子会在模组加载时以警告点名,服务表之外的 `$` 调用以 `no implementation for .` 拒绝。 + +## 打包与加载 + +| Claude Code | 本桥接 | 原因或后续 | +|---|---|---| +| 一个插件目录,含 `.claude-plugin/plugin.json` 与指定一个钩子模块的 `hooks/hooks.json`;`claude --plugin-dir`、市场、`/plugin install` | 模组就是插件:`defineMod({ name, version, root, userConfig, register })` 包装钩子模块,并在桥接之后经 cordis.yml 挂载;不读取 `plugin.json` 与 `hooks.json` | 模组加入每个 DSH 插件都使用的同一组合机制;示例目录保留其清单,因此也能在 `claude --plugin-dir` 下运行 | +| `plugin.json` 中带类型默认值的 `userConfig` schema;选项在加载时校验 | `userConfig` 是 `defineMod` 规格的默认值对象;插件的 cordis.yml `config` 覆盖其上,并按字符串、数字、布尔与字符串列表的记录校验 | 不读取清单 | +| 层级(`user`、`project`、`managed`)与 `next.to(e, tier)` | 每个模组都以 `user` 加载;测试中的 `tier()` 被接受并忽略;`next.to` 拒绝 | 托管设置不是 DSH 的概念 | +| 编辑时热重载钩子模块;`claude plugin validate`、`claude plugin test`、由 `types/index.d.ts` 生成类型 | 这些命令都没有;重新挂载会在同一已求值模块上重新运行 `register`,模块级变量保留其值 | 仅按需跟进;测试工具包的 `createModTestKit` 在 Vitest 中替代 `claude plugin test` | +| 钩子模块可以是 TypeScript | `.ts` 钩子模块只在启动器转译时可加载(源码启动可以;构建后的安装不行) | DSH 交付的是纯 Node | +| 模组在进程内运行,`$` 是其访问宿主的唯一途径 | 钩子模块以 Node 全局对象在进程内运行,没有访问规则,并拥有进程的全部权限:`$.env` 读写 harness 环境变量,`$.http.fetch` 可访问任意 URL,`$.fs` 与 `$.tool.call` 以会话的身份行动 | 不对模组施加沙箱;只挂载你愿意作为插件运行的模组 | +| `import type { … } from 'claude-code'`、`declare module 'claude-code' { interface PluginState }` | `claude-code` 模块名只在本仓库的测试设置中解析到桥接的类型;仓库外的模组导入 `@deepseek-ai/dsh-experimental-claude-code-mods` | 类型名是桥接自己的 | +| `hooks.json` 中的设置钩子与模组钩子并行运行 | 不运行;为它们挂载 `@deepseek-ai/dsh-hooks-claude-code` | 不同的桥接 | + +## 事件 + +| 事件 | Claude Code | 本桥接 | +|---|---|---| +| `session.start` | 每会话触发一次,在首个提示词之前 | 由根智能体的 `agent/created` 触发,在其首轮之前等待完成;不为子智能体触发 | +| `session.end` | 每会话触发一次 | 由根智能体的 `agent/disposed` 触发;钩子结束前 `$.state` 可读 | +| `prompt.submit` | `e.text` 是输入的提示词;`context` 条目各成一块跟在输入的提示词之后;插件提交的提示词带 `origin: { kind: 'plugin', name }` | 相同;`e.text` 拼接已认领批次中人类自己(`user` 来源)消息的文本块,仅含注入上下文的批次不触发,`$.prompt.submit` 排入的提示词以提交的模组为来源 | +| `turn.start`、`turn.complete` | 每轮触发;子智能体的轮带 `agentId` | 相同;`turn.complete` 的 `{ text }` 到达宿主日志,而非答案下方的一行;`durationMs` 从 `turn/start` 起算 | +| `tool.call` | 在 Claude Code 的权限检查之前运行;`next({ ...e, command })` 改写工具实际运行的参数,`next({ ...e, tool })` 把调用改派给另一工具 | 包裹 `tools/execute` 运行,在 harness 权限决定之后;改写参数或改名工具的钩子(或 `.catch` 处理器)会被跳过并报告——参数情形指向[工具前输入改写提案](../../.agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.zh.md)——因为已写入日志的调用按原样运行。`{ deny }`、`{ result }`、观察以及在 `next` 之后改写结果或 `isError` 均按描述工作 | +| `command.run` | 为模组注册的命令运行 | 相同 | +| `ui.render` | 为每个渲染位点触发(`AbovePrompt`、`Pane`、`ToolUse`、`AssistantMessage`、`Spinner`……) | 只为 `AbovePrompt` 触发,每次会话横幅重绘一次;`Pane` 钩子只经测试工具包的 `$.ui.mount({ component: 'Pane' })` 运行 | +| `tool.check`、`tool.describe`、`tool.list`、`tool.register` | 塑造工具列表与权限 | 不触发;`$.tool.register` 与 `$.tool.list` 作为 `$` 调用服务 | +| `turn.step` | 每个模型步骤 | 不触发 | +| 参考中的其他所有事件:其他 `prompt.*` 事件、`skill.prompt`、`attribution.text`、`command.describe`、`config.*`、其他 `session.*` 事件、`agent.*`、`plugin.register`、`engine.create`、`telemetry.*`、`ui.resolve`、`ui.press`、`ui.input`、`ui.select`、`ui.focus`、`ui.scroll`、`ui.close`、`ui.message` | 由引擎触发 | 可注册,永不触发,在加载时以警告点名 | +| `classic.*` | 经典设置钩子作为事件 | 在 `register` 时拒绝,与任何未知事件名相同 | +| `.` 操作事件(`fs.read`、`store.set`……) | 更晚加载的模组的 `$` 调用到达在它之前加载的模组 | 相同;`tool.call` 则经由工具流水线到达 | + +## 链 + +| Claude Code | 本桥接 | 原因 | +|---|---|---| +| 钩子可多次调用 `next`;每次调用都再次运行下层链 | `next` 只运行一次:第二次调用返回第一次运行的结果 | 每条写入日志的调用只执行一次工具 | +| 钩子在其 `next` 仍在运行时作答 | 下层运行会在钩子答案落定前被等待;答案之后的下层失败会被报告 | 事件只在其启动的一切都结束后落定 | +| 自身时间预算 10 秒,`.catch` 为 1 秒,`next` 与 `$` 调用内的时间不计,`$.clock.sleep` 除外 | 相同,可配置(`hookTimeoutMs`、`catchTimeoutMs`) | — | +| 不返回结果对象视为失败 | 相同;`null` 被接受为答案(绘制为空的表面) | 无可绘制内容的 `ui.render` | +| 结果中的未知字段被丢弃,类型错误使钩子失败 | 类型错误的 `prompt.submit` 结果保留原文本,并只取字符串形式的 context 行 | 对这一常见部分结果的事件从宽处理 | + +## `$` 成员 + +| 命名空间 | Claude Code | 本桥接 | +|---|---|---| +| `$.plugin` | `name`、`root`、`tier` | 来自 `defineMod` 规格的 `name`、`root` | +| `$.ui` | `resolve`、`invalidate`、`open`、`close`、`panes`、`focus`、`log`、`toast`、`status`、`notice`、`ask`、`blit`、`copy`、`press`、`input`、`select` | `resolve`、`invalidate`、`open`、`close`、`panes`、`log`、`toast`、`status`、`ask`;`open` 带原因回答 `{ isPlaced: false }` 并重绘横幅;`log`、`toast`、`status` 到达宿主日志;其余拒绝 | +| `$.command` | `register`、`run`、`list` | 相同;`run` 一个无人回答的命令时拒绝,并点名注册它的模组 | +| `$.tool` | `register`、`call`、`list` | 相同;注册的工具为 `mcp____`;`call` 运行 harness 流水线,并把工具推迟的上下文注入会话 | +| `$.prompt` | `submit`、`compose` | `submit`;消息为 `user` 来源,除非 `asUser` 否则套上 `Message from the "" mod:` 框架,它触发的 `prompt.submit` 以该模组为来源 | +| `$.session` | `id`、`cwd`、`root`、`repo`、`model`、`turns`、`messages`、`usage`、`version`、`send`、`append`、`authorize`、`compact`、`surfaces` | `id`、`cwd`、`root`、`model`、`turns`、`messages`、`usage`、`version`;`cwd` 报告已提交的当前目录;`root` 保留原始项目;在 token 计量器得知路由窗口前 `usage.context.window` 为 `0` 且无 `percent`;`rateLimits` 为空;`version` 指明本桥接 | +| `$.state` | 按会话,热重载后保留 | 按会话,内存中;`ui.render` 期间的读取让横幅订阅该槽位 | +| `$.store` | 按插件,持久 | 相同,在 `claude_code_mods` 存储域中,每插件 4 MiB | +| `$.clock` | `now`、`sleep`、`after`、`every`;定时器随插件消亡 | 相同;定时器还随安排它的事件所属会话消亡,事件取消时 `sleep` 拒绝 | +| `$.fs` | `read`、`write`、`list`、`exists`、`stat`、`ancestors` | 除 `ancestors` 外全部;每文件 4 MiB;`stat.mtimeMs` 为 `0` | +| `$.process` | `run`、`spawn` | 仅 `run`,不经 shell 的 argv,默认 `processTimeoutMs` | +| `$.http` | `fetch` | 相同,正文最多 4 MiB | +| `$.env` | `get`、`set` | 相同;进程环境由所有会话与插件共享 | +| `$.model`、`$.agent`、`$.config`、`$.settings`、`$.mcp`、`$.audio`、`$.telemetry`、`$.turn` | 已服务 | 以 `no implementation` 拒绝;`$.model.complete` 等待一个写入日志的副请求事件 | + +## 绘制 + +| Claude Code | 本桥接 | +|---|---| +| `Box`、`Text`、`Button`、`Input`、`Spacer` 以及宿主自己的元素种类 | 来自 `$.ui.resolve(e)` 的 `Box`、`Text`、`Button`;树经校验,`Button` 的 `onPress` 留在宿主中,位于按绘制分配的动作 id 之后 | +| `Box` 属性:`flexDirection`、`padding*`、`gap`、`border*`、`width`、`height`、`overflow`、`scroll` | `flexDirection`、`padding`、`paddingX`、`paddingY`、`gap`、`border`、`borderColor`;其余会序列化,Web 横幅忽略 | +| `Text` 属性:`color`、`bold`、`dimColor`、`italic`、`underline`、`wrap`、`truncate` | `color`(终端调色板名)、`bold`、`dimColor`、`italic`、`underline`;Web 横幅会换行 | +| `Button` 属性:`label`、`hotkey`、`disabled`、`onPress`;单个数字键触发热键 | `label`、`hotkey`(显示为提示)、`disabled`、`onPress`(点击) | +| 横幅是一个实例;`hasSurvey`、`isWorking`、`maxRows`、`bodyColumns` 反映终端 | 每会话一个实例,按模组加载顺序咨询;`bodyColumns` 与 `viewport.columns` 取桥接的 `bandColumns`(默认 `120`),`maxRows` 取 `bandRows`(默认 `10`),`hasSurvey` 与 `isWorking` 为 `false`;Web 横幅换行而非滚动 | +| 横幅随宿主帧重绘,并在渲染读取过的 `$.state` 值变化时重绘 | 在 `session.start` 之后、上次绘制读取过的 `$.state` 值被写入时、在 `$.ui.open`、`$.ui.close`、`$.ui.invalidate` 之后、在每条 `tool.call` 链与 `turn.complete` 结束后以及按下之后重绘;在其他时刻改变模块级状态的模组在下一次触发时绘出。按下运行 `onPress` 时没有时间限制 | +| 带 `focus`、`scroll`、停靠在对话旁的 `Pane` | 没有面板:`$.ui.open` 回答 `{ isPlaced: false }`,模组回退到横幅;测试工具包直接挂载 `Pane` 钩子 | +| 热键、`TextInput`、焦点、滚动、`ui.press`、`ui.input`、`ui.select` | 不服务;后续工作 | + +## 测试 + +| `claude-code/testing` | 本桥接 | +|---|---| +| `describe`、`test(name, ($, on) => …)`、`expect`、`mock`、`tier` | 在本仓库内同一模块名基于 Vitest 提供它们;`test` 从测试文件位置推断模组(`/tests/*.test.ts` → `/index.ts`),`defineModTests` 可覆盖。仓库外则从 `@deepseek-ai/dsh-experimental-claude-code-mods/testing` 获取 `createModTestKit` 与 `mock` | +| `on` 为引擎的答案打桩;工具包自行回答 `ui.*` | 相同;`ui.open` 回答 `{ isPlaced: false }`,`ui.invalidate` 与 `ui.close` 成功;`session.cwd`、`process.run`、`fs.*`、`store.*`、`env.*` 需要桩(`mock.store`、`mock.env`、`mock.clock` 回答整个命名空间) | +| `$.ui.mount({ plugin, surface, component, props })` → `find`、`findAll`、`press`、`unmount` | 相同,另有 `tree()` 与 `text()`;每次读取都经所有已加载模组的钩子重新渲染;`plugin` 与 `surface` 为源码兼容而接受 | +| 测试中钩子预算 5 秒 | 相同(`budgetMs`) | + +## 示例模组 + +| 模组 | 来源 | 经本桥接 | +|---|---|---| +| Token Weather | 文中的模块、类型契约与测试,原样 | 按发布原样运行;测试中的三处 `as any` 转换因工具包带类型而去掉。通过 token 计量器从 `$.session.usage` 在横幅中绘制 | +| Blast Radius | 文中的 `tool.call` 钩子原样;`classify`、`measure` 与树按文中描述补全 | 以真实的 `sleep` 轮询持有命令,在横幅中绘制报告(`isPlaced: false`),由 Proceed 或 Cancel 决定;请加载在 Token Weather 之前 | +| Replay Theater | 文中的五个钩子原样;`stepsFor`、差异、`openReplay`、面板与提示补全 | 记录 Edit 与 Write 调用,轮结束后在横幅中提示,并因无面板放置而在横幅中逐步浏览差异 | +| `diff`([anthropics/claude-code](https://github.com/anthropics/claude-code/tree/main/mods/diff),于 `52c7644` 阅读) | TypeScript,约 570 个文件,钩住 `command.run`、`prompt.submit`、`session.start`、`tool.call`、`ui.close`、`ui.focus`、`ui.render`、`ui.scroll` | 不可运行:需要 `$.settings.read`、`$.telemetry.*`、面板上的 `$.session.messages`、`ui.focus` 与 `ui.scroll` 事件以及已放置的 `Pane`。以阅读方式评估;该仓库的条款不允许复制夹具 | +| `agents-md`(同一仓库) | 钩住 `agent.spawn`、`prompt.context`、`session.start`、`tool.call` | 不可运行:`prompt.context` 与 `agent.spawn` 不触发,`$.fs.ancestors`、`$.telemetry.*` 不服务 | +| `sec-default`(同一仓库) | 钩住 `tool.check`、`prompt.compose`、`settings.read`、`classic.*`、`plugin.register`…… | 不可运行:基于本宿主不触发的事件的托管层策略 | +| `telemetry`(同一仓库) | 钩住 `engine.create`、`telemetry.*`、`session.start`、`session.end`;调用 `$.session.repo`、`$.settings.read` | 不可运行:`telemetry.*` 与 `engine.create` 不触发,两个 `$` 成员不服务 | + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.claudeCodeMods` — `ClaudeCodeMods` + +The bridge service: loaded mods, their hooks, and the harness listeners that raise their events. Mods join through ClaudeCodeMods.add, which defineMod calls when a mod plugin mounts. The Remote methods let a Client draw each session's band above the prompt and press its buttons. + +```ts cordis-catalog +/** + * Watch the band above the prompt of one session: the current drawing, then + * every redraw, until the Client stops watching. + * @param agent - the session's agent, resolved by the Gateway. + * @param signal - carrier cancellation. + * @returns the band's snapshots. + */ +@Remote({ mode: 'stream' }) watchBand(agent: Agent, signal: AbortSignal): AsyncIterable + +/** + * Press a button of the band's current drawing: runs the mod's `onPress` and redraws. + * @param agent - the session's agent, resolved by the Gateway. + * @param generation - the drawing the Client saw. + * @param actionId - the button's action id in that drawing. + * @returns the snapshot after the press. + */ +@Remote pressBand(agent: Agent, generation: number, actionId: string): Promise + +/** + * Load one mod beneath every mod loaded before it: run its `register`, + * keep its hooks, and report which of its events this host never raises. + * @param definition - the mod as its plugin defined it. + * @returns the disposer that removes the mod's hooks, closes its timers, and releases its registrations. + * @throws Error when the name is invalid or taken, or when `register` throws (Claude Code's `hooks module did not load` wording). + */ +async add(definition: ModDefinition): Promise<() => Promise> +``` + +Types: [Agent](core.zh.md) + +Source: [`packages/experimental/claude-code-mods/src/index.ts`](../../packages/experimental/claude-code-mods/src/index.ts) + diff --git a/docs/subsystems/commands.i18n.yaml b/docs/subsystems/commands.i18n.yaml index 6ffccc99cd..83b6356f56 100644 --- a/docs/subsystems/commands.i18n.yaml +++ b/docs/subsystems/commands.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/commands.md /human-commands: - en: e3bfa72a37c4afcd - zh: 887432898b31115d + en: cc09de9b4b08d24f + zh: 02217510953c535b /human-commands/input-metadata: en: 9c7fd38926610b58 zh: 40f1b2fd9b18ed52 diff --git a/docs/subsystems/commands.md b/docs/subsystems/commands.md index dc6ad74da8..bc64f1a722 100644 --- a/docs/subsystems/commands.md +++ b/docs/subsystems/commands.md @@ -2,7 +2,7 @@ English | [中文](commands.zh.md) -The human-command registry service from [`dsh-commands`](../../packages/interaction/commands). Interactive adapters use it to discover and directly execute plugin-owned commands for an exact agent without creating a model message. The [command Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) owns dispatch and lifecycle rationale; the [package README](../../packages/interaction/commands/README.md) owns composition and limitations. +The human-command registry service from [`dsh-commands`](../../packages/interaction/commands). Interactive adapters use it to discover and directly execute plugin-owned commands for an exact agent without creating a model message. The [historical command Agent Note](../../.agents/notes/archived/feature/2026-07-19-plugin-command-registration.md) records dispatch and lifecycle rationale; the [package README](../../packages/interaction/commands/README.md) owns composition and limitations. Source: [`packages/interaction/commands/src/index.ts`](../../packages/interaction/commands/src/index.ts) diff --git a/docs/subsystems/commands.zh.md b/docs/subsystems/commands.zh.md index 57a60a87f7..9edada77ad 100644 --- a/docs/subsystems/commands.zh.md +++ b/docs/subsystems/commands.zh.md @@ -2,7 +2,7 @@ [English](commands.md) | 中文 -[`dsh-commands`](../../packages/interaction/commands) 提供的用户命令注册表服务。交互式适配器用它发现插件拥有的命令,并针对确切的 agent(智能体)直接执行这些命令,而不创建模型消息。[命令 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md) 负责分发与生命周期的决策依据;[包 README](../../packages/interaction/commands/README.zh.md) 负责组合方式与限制。 +[`dsh-commands`](../../packages/interaction/commands) 提供的用户命令注册表服务。交互式适配器用它发现插件拥有的命令,并针对确切的 agent(智能体)直接执行这些命令,而不创建模型消息。[历史命令 Agent Note](../../.agents/notes/archived/feature/2026-07-19-plugin-command-registration.md) 负责分发与生命周期的决策依据;[包 README](../../packages/interaction/commands/README.zh.md) 负责组合方式与限制。 来源:[`packages/interaction/commands/src/index.ts`](../../packages/interaction/commands/src/index.ts) diff --git a/docs/subsystems/compaction.i18n.yaml b/docs/subsystems/compaction.i18n.yaml index b4be04a060..76a4ac7bf8 100644 --- a/docs/subsystems/compaction.i18n.yaml +++ b/docs/subsystems/compaction.i18n.yaml @@ -3,11 +3,11 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/compaction.md /compaction: - en: 7eb1d2d84fb85447 - zh: 1927119f75c0dd45 + en: 2b372c8f979dfc0e + zh: 818c50976481b90b /compaction/the-compaction-session-events: - en: e75ae617952aa929 - zh: a0e5780f46dff510 + en: bc9bbbe82efc308c + zh: 0b2b7f70f5df5d99 /compaction/image-offload: en: 19632b674911826b zh: b29ef7ff4ae58e58 diff --git a/docs/subsystems/compaction.md b/docs/subsystems/compaction.md index 6e135025ab..f2e8848bcf 100644 --- a/docs/subsystems/compaction.md +++ b/docs/subsystems/compaction.md @@ -2,13 +2,13 @@ English | [中文](compaction.zh.md) -The compaction seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) split like bash: Service Definition ([dsh-compaction](../../packages/compaction/compaction), `ctx.compaction`), Service Provider (a backend such as [dsh-compaction-basic](../../packages/compaction/compaction-basic)), and human Consumer ([dsh-command-compact](../../packages/compaction/command-compact)). Compaction is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A tokenizer- or template-based backend is a sibling package implementing the same interface. Unlike bash, the interface necessarily depends on `dsh-session` and `dsh-llm`: its verbs act on an agent-owned `Session`, and its durable summary event uses the `ContentBlock` vocabulary (see the [compaction capability-seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)). +The compaction seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) split like bash: Service Definition ([dsh-compaction](../../packages/compaction/compaction), `ctx.compaction`), Service Provider (a backend such as [dsh-compaction-basic](../../packages/compaction/compaction-basic)), and human Consumer ([dsh-command-compact](../../packages/compaction/command-compact)). Compaction is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A tokenizer- or template-based backend is a sibling package implementing the same interface. Unlike bash, the interface necessarily depends on `dsh-session` and `dsh-llm`: its verbs act on an agent-owned `Session`, and its durable summary event uses the `ContentBlock` vocabulary (see the [compaction capability-seam reference](../../packages/compaction/compaction/README.md)). Source: [`packages/compaction/compaction/src/types.ts`](../../packages/compaction/compaction/src/types.ts) ## The `compaction/*` session events -Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the lock, summary, selected range, shadowed event seqs, token count, and model call without joining the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', startSeq, endSeq }` — the only surface mutation performed by summary compaction. The [Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) owns the rationale for reusing `user/message`. +Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the lock, summary, selected range, shadowed event seqs, token count, and model call without joining the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', startSeq, endSeq }` — the only surface mutation performed by summary compaction. The [historical Agent Note](../../.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md) records the rationale for reusing `user/message`. | Event | Payload | Role | |---|---|---| diff --git a/docs/subsystems/compaction.zh.md b/docs/subsystems/compaction.zh.md index 9b82fb7a7b..38aca41794 100644 --- a/docs/subsystems/compaction.zh.md +++ b/docs/subsystems/compaction.zh.md @@ -2,13 +2,13 @@ [English](compaction.md) | 中文 -压缩 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md),与 bash 一样分为 Service Definition([dsh-compaction](../../packages/compaction/compaction),`ctx.compaction`)、Service Provider(例如 [dsh-compaction-basic](../../packages/compaction/compaction-basic) 后端)和面向用户的 Consumer([dsh-command-compact](../../packages/compaction/command-compact))。压缩是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.zh.md) 中。基于 tokenizer 或模板的后端是实现同一接口的兄弟包。与 bash 不同,该接口必然依赖 `dsh-session` 和 `dsh-llm`:其动词作用于 agent 所有的 `Session`,而其持久摘要事件使用 `ContentBlock` 词汇(见[压缩能力 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md))。 +压缩 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md),与 bash 一样分为 Service Definition([dsh-compaction](../../packages/compaction/compaction),`ctx.compaction`)、Service Provider(例如 [dsh-compaction-basic](../../packages/compaction/compaction-basic) 后端)和面向用户的 Consumer([dsh-command-compact](../../packages/compaction/command-compact))。压缩是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.zh.md) 中。基于 tokenizer 或模板的后端是实现同一接口的兄弟包。与 bash 不同,该接口必然依赖 `dsh-session` 和 `dsh-llm`:其动词作用于 agent 所有的 `Session`,而其持久摘要事件使用 `ContentBlock` 词汇(见[压缩能力 seam 参考](../../packages/compaction/compaction/README.zh.md))。 源码:[`packages/compaction/compaction/src/types.ts`](../../packages/compaction/compaction/src/types.ts) ## `compaction/*` 会话事件 -压缩通过声明合并为 [`SessionEventMap`](session.zh.md) 扩展三种事件类型。三者都**仅写入日志**——它们记录锁、摘要、选中范围、被遮蔽事件 seq、token 数以及模型调用,绝不进入 surface。这里有意不扩展 `SurfaceEventType`(只有产生消息的事件才到达模型),因此摘要本身承载在另一条带有 `surfaceOp: { op: 'replace', startSeq, endSeq }` 的 `user/message` 上——这是摘要压缩执行的唯一 surface 变更。[Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md) 负责复用 `user/message` 的决策依据。 +压缩通过声明合并为 [`SessionEventMap`](session.zh.md) 扩展三种事件类型。三者都**仅写入日志**——它们记录锁、摘要、选中范围、被遮蔽事件 seq、token 数以及模型调用,绝不进入 surface。这里有意不扩展 `SurfaceEventType`(只有产生消息的事件才到达模型),因此摘要本身承载在另一条带有 `surfaceOp: { op: 'replace', startSeq, endSeq }` 的 `user/message` 上——这是摘要压缩执行的唯一 surface 变更。[历史Agent Note](../../.agents/notes/archived/feature/2026-06-18-compaction-capability-seam.md) 负责复用 `user/message` 的决策依据。 | 事件 | 载荷 | 作用 | |---|---|---| diff --git a/docs/subsystems/conversation.i18n.yaml b/docs/subsystems/conversation.i18n.yaml index 9a57ab6f2a..b8f7380bbf 100644 --- a/docs/subsystems/conversation.i18n.yaml +++ b/docs/subsystems/conversation.i18n.yaml @@ -15,11 +15,11 @@ en: 62c0a5ae4ed7e1aa zh: 787ad4d04b79adaf /conversation-assembly/replayable-event-families: - en: 17f9e11d512b2b8e - zh: 6b7a9c1139eededb + en: c216e2df128b8cb9 + zh: e61312f1f280332d /conversation-assembly/definition-and-typed-chat-payload: - en: c7ea2c9192f80274 - zh: 742b5c31af1e4ade + en: 5f24557a050d1df0 + zh: 47735627f802b6fa /conversation-assembly/predecessor-reads: en: da849b3b5da81da8 zh: 37a647a439bc24b1 diff --git a/docs/subsystems/conversation.md b/docs/subsystems/conversation.md index dca3d51339..ba4eb88102 100644 --- a/docs/subsystems/conversation.md +++ b/docs/subsystems/conversation.md @@ -69,7 +69,7 @@ Use the producer-owned branded id type across the process boundary. Put the `Ses Incremental events are supported. Prefer whole-value checkpoints when the producer can emit them cheaply, because they remain useful when the start is outside the loaded window. Each delta must carry the stable id and produce deterministic State when replayed in ascending log `seq`; it must not depend on live-only memory. If the current history window contains only updates, the assembler keeps a pending Context and builds no State until an older page supplies the start. If the product must render before the start is loaded, a terminal or checkpoint event must carry enough whole fallback state for the Definition to build that result directly; do not recover it by scanning unrelated events. -Live Assistant deltas arrive as Client-only `assistant/live-chunk` events. Reconnect baselines expand the active process-local compact stream into the same transient events, while durable `assistant/message` and `assistant/attempt` events embed complete compact streams for history replay. A transient event may initialize a Context. A named tool delta and its later tool/call can both match as start under the same callId; only the earliest calls start(), and later Matches call update(). Historical pages do not expand settled messages into live deltas. A Definition that consumes Assistant output handles live chunks and durable settlements in the same `match()` and `update()` methods, while unrelated Definitions return `null` without expanding a stream. +Live Assistant deltas arrive as Client-only `assistant/live-chunk` events. Reconnect baselines expand the active process-local compact stream into the same transient events, while durable `assistant/message` and `assistant/attempt` events embed complete compact streams for history replay. A transient event may initialize a Context. Every tool delta and its later `tool/call` can match as start under the same callId, even before the name arrives; only the earliest calls `start()`, and later Matches call `update()`. A preparing Tool node becomes visible only after its name is available. Historical pages do not expand settled messages into live deltas. A Definition that consumes Assistant output handles live chunks and durable settlements through the same matching and update interfaces, while unrelated Definitions return `null` without expanding a stream. ## Definition and typed Chat payload @@ -245,6 +245,8 @@ export function apply(ctx: ClientContext): void { `match(event)` is an identity extractor, not a fold: it receives only the current `SessionEventLike` and returns the Definition-local id and lifecycle role. After a match, the assembler locates the Context by `(kind, id)` and initializes from its earliest loaded start, whether durable or transient. Every later Match calls `update`, including another start. Removing transient Matches reselects the start from remaining evidence and recomputes State; no remaining start leaves State undefined. Both functions return the State that the engine adopts; returning a new immutable value is preferred, but a function that mutates and returns the same object has the same adoption semantics. +`ConversationNodeDefinitionInput` accepts either this function or a read-only table from event type to matching function. Function-form registrations receive every event; table registrations receive only their listed types. The Registry copies table entries and precomputes ordered candidate Sets when registrations change, keeping candidate construction out of per-event dispatch. Replace a table by unregistering and registering again. Resolved `ConversationNodeDefinition` values retain a callable `match(event)` in both cases. + `buildLocationData(context, scope)` optionally publishes Definition-owned data onto an engine-owned Turn or Step. Use declaration merging to give each key a precise value type. Another Node in the same Location can consume that value through its constrained slot hook, such as `useTurnData(key)`, without receiving the Session or scanning `snapshot.chat.nodes`. `target` and `buildViewNode(context)` declare one target-owned rendering contribution and must appear together. Preserve `context.key` as the React-facing identity, choose `anchorSeq` from the matched event ordering, and return only renderer-ready data. Once a target Node has been published, keep returning the same key; use `visibility: 'hidden'` when it must temporarily leave the visible flow rather than withdrawing it with `null`. diff --git a/docs/subsystems/conversation.zh.md b/docs/subsystems/conversation.zh.md index 18d2a2b282..7115bb90b8 100644 --- a/docs/subsystems/conversation.zh.md +++ b/docs/subsystems/conversation.zh.md @@ -69,7 +69,7 @@ Group 存储在安装前校验完整提交结果:根 Group 引用与记录一 系统支持增量事件。如果生产方能以较低成本发出 whole-value checkpoint,应优先采用,因为 start 位于已加载窗口之外时它仍可直接使用。每条 delta 都必须携带稳定 id,并且按照日志 `seq` 升序回放时能够确定性地产生 State;它不能依赖只存在于实时内存中的状态。如果当前历史窗口只有 update,Assembler 会保留一个 pending Context,并在更早分页补齐 start 前不构造 State。如果产品必须在 start 尚未加载时渲染,terminal 或 checkpoint 事件就必须携带足够的完整 fallback 状态,让 Definition 能直接构造结果;不要通过扫描无关事件恢复它。 -实时 Assistant delta 作为 Client-only `assistant/live-chunk` event 到达。重连 baseline 会把活跃的进程内紧凑 stream 展开为相同的瞬态 event,持久 `assistant/message` 与 `assistant/attempt` event 则嵌入完整紧凑 stream 供历史回放。瞬态 event 可以初始化 Context。具名工具 delta 和后续 tool/call 可以按同一个 callId 匹配为 start;只有最早的 Match 调用 start(),后续 Match 调用 update()。历史分页不把已结束消息展开为实时 delta。消费 Assistant 输出的 Definition 在同一组 `match()` 与 `update()` 方法里处理 live chunk 与持久 settlement,其他 Definition 直接返回 `null`,无需展开 stream。 +实时 Assistant delta 作为 Client-only `assistant/live-chunk` event 到达。重连 baseline 会把活跃的进程内紧凑 stream 展开为相同的瞬态 event,持久 `assistant/message` 与 `assistant/attempt` event 则嵌入完整紧凑 stream 供历史回放。瞬态 event 可以初始化 Context。每条工具 delta 和后续 `tool/call` 都可以按同一个 callId 匹配为 start,即使名称尚未到达;只有最早的 Match 调用 `start()`,后续 Match 调用 `update()`。准备态 Tool 节点只有在名称可用后才显示。历史分页不把已结束消息展开为实时 delta。消费 Assistant 输出的 Definition 通过同一组匹配和更新接口处理 live chunk 与持久 settlement,其他 Definition 直接返回 `null`,无需展开 stream。 ## Definition 与类型化 Chat payload @@ -245,6 +245,8 @@ export function apply(ctx: ClientContext): void { `match(event)` 是身份提取器,不是 fold:它只能收到当前 `SessionEventLike`,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context;当前最早的 start 初始化 State,不论它是持久事件还是瞬态事件。后续所有 Match,包括其他 start,都调用 `update`。移除瞬态 Match 后,从剩余事件重新选择 start 并重算 State;没有剩余 start 时 State 为 undefined。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。 +`ConversationNodeDefinitionInput` 接受上述函数,或事件类型到匹配函数的只读表。函数形式的注册接收所有事件;表形式只接收列出的类型。Registry 在注册关系变化时复制表条目并预计算有序候选 Set,不在逐事件分发时构造候选集合。替换表时先注销,再重新注册。两种形式解析后的 `ConversationNodeDefinition` 都保留可调用的 `match(event)`。 + `buildLocationData(context, scope)` 可以把 Definition 拥有的数据发布到引擎拥有的 Turn 或 Step 上。通过 declaration merging 为每个 key 指定精确 value 类型。同一 Location 内的另一个 Node 可以使用受限 slot hook(例如 `useTurnData(key)`)读取该值,无须取得 Session,也无须扫描 `snapshot.chat.nodes`。 `target` 与 `buildViewNode(context)` 必须同时声明一项由 target 拥有的渲染贡献。把 `context.key` 保留为 React 侧身份,根据持久排序证据选择 `anchorSeq`,并且只返回 renderer 可以直接使用的数据。某个 target Node 一旦发布,就要继续返回同一个 key;需要暂时离开可见流时使用 `visibility: 'hidden'`,不要改为返回 `null` 撤回它。 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 15c70ebca6..665df11563 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -106,7 +106,7 @@ interface Agent { * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens * its turn boundary, even when its message is cleared before the driver - * claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)). + * claims ([driver wake convergence](../../agent-loop/src/agent.ts)). * @param message - identified content and the source that supplied it. * @param target - the preferred next-turn or next-step inbox boundary. * @param wakeup - whether delivery may wake the driver. @@ -495,8 +495,14 @@ Registry of YAML-declared presets and the revisions live Agents retain. */ async register(definition: PresetDefinition): Promise<() => Promise> +/** Inspect retained revisions, or the exact revision an Agent joined. + * @param ctx - optional Agent context; omission includes all retained revisions. + * @returns detached definition and module row identities, resolution bases, and isolation diagnostics; no match returns an empty list. + */ +inspectCompositions(ctx?: Context): AgentPresetInspection[] + /** Read every declared preset, including activation failures. - * @returns Display metadata and loading diagnostics. + * @returns Display metadata, declaring Loader row identities, and loading diagnostics. */ async list(): Promise diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 03de9ddb7b..44ee6d3605 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -110,7 +110,7 @@ interface Agent { * turn and runs when the aborted activity converges to idle; a `disposed` * cancel leaves it parked. A wake submitted while already idle always opens * its turn boundary, even when its message is cleared before the driver - * claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)). + * claims ([driver wake convergence](../../agent-loop/src/agent.ts)). * @param message - identified content and the source that supplied it. * @param target - the preferred next-turn or next-step inbox boundary. * @param wakeup - whether delivery may wake the driver. @@ -505,8 +505,14 @@ Registry of YAML-declared presets and the revisions live Agents retain. */ async register(definition: PresetDefinition): Promise<() => Promise> +/** Inspect retained revisions, or the exact revision an Agent joined. + * @param ctx - optional Agent context; omission includes all retained revisions. + * @returns detached definition and module row identities, resolution bases, and isolation diagnostics; no match returns an empty list. + */ +inspectCompositions(ctx?: Context): AgentPresetInspection[] + /** Read every declared preset, including activation failures. - * @returns Display metadata and loading diagnostics. + * @returns Display metadata, declaring Loader row identities, and loading diagnostics. */ async list(): Promise diff --git a/docs/subsystems/credentials.md b/docs/subsystems/credentials.md index eb708f95f9..ef0b5b04c3 100644 --- a/docs/subsystems/credentials.md +++ b/docs/subsystems/credentials.md @@ -420,7 +420,7 @@ Source: [`packages/credentials/credentials/src/types.ts`](../../packages/credent #### `credentials/reference-updated` — emit -Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome. ```ts cordis-catalog /** @@ -428,10 +428,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o * `unset`, or an external edit observed in storage. Ambient * process-environment changes are not observable and never emit. Listener * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. + * alike — without changing the committed operation's outcome. * @param ref - the reference whose stored value changed. * @mode emit */ diff --git a/docs/subsystems/credentials.zh.md b/docs/subsystems/credentials.zh.md index 9a6ce9f3fa..d6a8d73938 100644 --- a/docs/subsystems/credentials.zh.md +++ b/docs/subsystems/credentials.zh.md @@ -420,7 +420,7 @@ Source: [`packages/credentials/credentials/src/types.ts`](../../packages/credent #### `credentials/reference-updated` — emit -Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome. ```ts cordis-catalog /** @@ -428,10 +428,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o * `unset`, or an external edit observed in storage. Ambient * process-environment changes are not observable and never emit. Listener * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. + * alike — without changing the committed operation's outcome. * @param ref - the reference whose stored value changed. * @mode emit */ diff --git a/docs/subsystems/goal.i18n.yaml b/docs/subsystems/goal.i18n.yaml index c4e92f958e..a0813d8c7f 100644 --- a/docs/subsystems/goal.i18n.yaml +++ b/docs/subsystems/goal.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/goal.md /same-session-goals: - en: f76138e8235819ce - zh: fdb595b0451cd8f7 + en: 0ee34eaeb38c55a7 + zh: f485845efec09818 /same-session-goals/identity-and-lifecycle: en: 539925bba84c069e zh: 2f8ca46c076a393d diff --git a/docs/subsystems/goal.md b/docs/subsystems/goal.md index ca49f0cd4f..a52ec08f6d 100644 --- a/docs/subsystems/goal.md +++ b/docs/subsystems/goal.md @@ -2,7 +2,7 @@ English | [中文](goal.zh.md) -Types shared by the event-sourced goal service and its policy consumers. The [goal-domain Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) owns the persistence and activation decisions; this page records the exact fields and variants from [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts). +Types shared by the event-sourced goal service and its policy consumers. The [goal-domain reference](../../packages/goal/goal/README.md) owns the persistence and activation decisions; this page records the exact fields and variants from [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts). ## Identity and lifecycle diff --git a/docs/subsystems/goal.zh.md b/docs/subsystems/goal.zh.md index 4567f327a9..4883c0752e 100644 --- a/docs/subsystems/goal.zh.md +++ b/docs/subsystems/goal.zh.md @@ -2,7 +2,7 @@ [English](goal.md) | 中文 -事件溯源目标服务及其策略消费方共享的类型。[目标领域 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md) 负责记录持久化与激活决策;本页记录 [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts) 中的确切字段和变体。 +事件溯源目标服务及其策略消费方共享的类型。[目标领域 参考](../../packages/goal/goal/README.zh.md) 负责记录持久化与激活决策;本页记录 [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts) 中的确切字段和变体。 ## 标识与生命周期 diff --git a/docs/subsystems/invariants.i18n.yaml b/docs/subsystems/invariants.i18n.yaml deleted file mode 100644 index 8cbba766c6..0000000000 --- a/docs/subsystems/invariants.i18n.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Bilingual-pair consistency record for invariants.md (docs/i18n/README.md): per heading -# section, a hash of its English and Chinese blocks outside code blocks and generated regions. -# After editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/subsystems/invariants.md -/runtime-invariants: - en: b48053808b8bb042 - zh: 7be7763ded5f3a6e -/runtime-invariants/selection: - en: 2d3bb3a2a0212e2d - zh: 8c8f1e1ba5790ba4 -/runtime-invariants/the-installer: - en: ba1607612e5ed640 - zh: 33459bdb7c42443c -/runtime-invariants/the-service: - en: 6368b54b2cbdb36c - zh: 4859ac095e536d42 -/runtime-invariants/the-companion-contract: - en: 414b986025768325 - zh: b4071f8b39f8f755 diff --git a/docs/subsystems/invariants.md b/docs/subsystems/invariants.md deleted file mode 100644 index 2d1856dfd0..0000000000 --- a/docs/subsystems/invariants.md +++ /dev/null @@ -1,88 +0,0 @@ -# Runtime Invariants - -English | [中文](invariants.zh.md) - -[dsh-invariants](../../packages/runtime-diagnostics/invariants) is the configurable registry service (`ctx.invariants`) for package-owned runtime invariant checks. It is one support-group package, not a three-package capability seam, and not part of the agent-loop spine: the registry owns selection, name reservation, child-fiber lifecycle, and package-attributed failure, while every workspace package publishes a `./invariant` companion plugin that registers checks under its exact npm package name. What a check may assert — authoritative event streams or mutable data, never service or method presence — is the runtime-invariants convention in [AGENTS.md](../../AGENTS.md#conventions). - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - -## Selection - -```ts type-equiv -/** Runtime invariant selection configured on the service plugin. */ -interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - -A package is selected when the service is enabled, the allowlist is empty or at least one pattern matches its full npm name, and no blocklist pattern matches — a blocklist match overrides an allowlist match. Entries compile with `new RegExp(source)`: matching is unanchored unless the source supplies `^` and `$`, and `/pattern/flags` syntax is not parsed. Validation fails loud at service startup: a blank, whitespace-padded, duplicate, or invalid entry throws instead of being skipped. A valid pattern may match no currently loaded package, so later loading and HMR stay deterministic; filters are fixed for the service lifetime ([README](../../packages/runtime-diagnostics/invariants/README.md)). - -## The installer - -```ts type-equiv -/** - * Throw a package-attributed invariant failure. - * @param message - violated package contract without the standard prefix. - * @returns never because reporting a violation throws. - */ -type InvariantFailure = (message: string) => never -``` - -```ts type-equiv -/** Install one package's checks into the registration's child context. */ -interface InvariantInstaller { - /** - * Install the package contribution. - * @param ctx - child context owned by this invariant registration. - * @param fail - reporter bound to the registering package name. - * @returns nothing, or a promise settling after asynchronous checks finish. - */ - (ctx: Context, fail: InvariantFailure): void | Promise - /** Services the child installer fiber may access. */ - readonly inject?: Inject -} -``` - -An enabled installer runs in a dedicated child Cordis fiber; `installer.inject` declares the services that fiber may access, and synchronous or asynchronous installer completion is joined before the registration succeeds. `fail(message)` throws `InvariantError` — `extends Error` with stable `code: 'INVARIANT'`, the owning `packageName`, and a message prefixed `invariant violated by "": …` — so a violation is attributable without the registry importing any product package. - -## The service - -`ctx.invariants.register(packageName, installer)` reserves one active registration for the full npm package name and returns its effect-scoped disposer. The reservation holds even when filters keep the installer inactive, so two plugins can never silently claim the same package name; a duplicate, blank, or whitespace-containing name throws. An installer failure disposes the child fiber and releases the reservation atomically. The service owns every registration fiber while the returned disposer also belongs to the companion fiber: unloading either side removes listeners, trace state, and the reservation, so a companion can reload and register the same name again without retained state. - -## The companion contract - -Every workspace package owns a `./invariant` companion ([package contract](../../packages/AGENTS.md)); publication and registration are exhaustive, but assertions are deliberately not synthetic. A companion installs a check only when its package owns an observable event or mutable-data relationship; otherwise it exports an empty installer whose leading comment starts `No runtime invariant:` and explains, package-specifically, why nothing is checkable. `pnpm run verify-package-invariants` mechanically rejects generated markers, unexplained empty installers, non-empty installers that omit or ignore the reporter, incorrect registration names, and incomplete export, publication, dependency, or bundle wiring ([mechanical-rule Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md)). The catalog of executable companions and the standard composition live in the [package README](../../packages/runtime-diagnostics/invariants/README.md). - - - - - -## Cordis API - -Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - - -### `ctx.invariants` — `InvariantRegistry` - -Package-owned invariant registry with global and regex-based selection. - -```ts cordis-catalog -/** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ -register(packageName: string, installer: InvariantInstaller): () => void -``` - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - diff --git a/docs/subsystems/invariants.zh.md b/docs/subsystems/invariants.zh.md deleted file mode 100644 index 15760a9b11..0000000000 --- a/docs/subsystems/invariants.zh.md +++ /dev/null @@ -1,88 +0,0 @@ -# 运行时不变式 - -[English](invariants.md) | 中文 - -[dsh-invariants](../../packages/runtime-diagnostics/invariants) 是面向包自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 配套插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定。 - -源码:[`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - -## 选择 - -```ts type-equiv -/** Runtime invariant selection configured on the service plugin. */ -interface Config { - /** Global switch; defaults to `true`. */ - readonly enabled?: boolean - /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ - readonly package_allowlist?: string[] - /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ - readonly package_blocklist?: string[] -} -``` - -一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时明确报错:空白、首尾带空白、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/runtime-diagnostics/invariants/README.zh.md))。 - -## 安装器 - -```ts type-equiv -/** - * Throw a package-attributed invariant failure. - * @param message - violated package contract without the standard prefix. - * @returns never because reporting a violation throws. - */ -type InvariantFailure = (message: string) => never -``` - -```ts type-equiv -/** Install one package's checks into the registration's child context. */ -interface InvariantInstaller { - /** - * Install the package contribution. - * @param ctx - child context owned by this invariant registration. - * @param fail - reporter bound to the registering package name. - * @returns nothing, or a promise settling after asynchronous checks finish. - */ - (ctx: Context, fail: InvariantFailure): void | Promise - /** Services the child installer fiber may access. */ - readonly inject?: Inject -} -``` - -被启用的安装器在专属的子 Cordis fiber 中运行;`installer.inject` 声明该 fiber 可以访问的服务,注册成功之前会先等待安装器同步或异步地执行完毕。`fail(message)` 抛出 `InvariantError`(`extends Error`,带稳定的 `code: 'INVARIANT'`、所属 `packageName`,以及前缀为 `invariant violated by "": …` 的消息),因此违规可归因,而注册表无需导入任何产品包。 - -## 服务 - -`ctx.invariants.register(packageName, installer)` 为完整 npm 包名保留唯一一个活跃注册,并返回其绑定到 effect 的 disposer。即使过滤器使安装器保持不活跃,保留依然成立,因此两个插件绝不可能静默地认领同一个包名;重复、空白或含空白字符的名称会抛出异常。安装器失败会原子地 dispose(资源释放)子 fiber 并释放保留。服务拥有每个注册 fiber,而返回的 disposer 同时属于配套插件的 fiber:卸载任一侧都会移除监听器、trace 状态和保留项,因此配套插件可以重载并再次注册同一名称,不留残余状态。 - -## 配套插件约定 - -每个工作区包都拥有一个 `./invariant` 配套插件([包约定](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包拥有某个可观察事件或某种可变数据关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.zh.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/runtime-diagnostics/invariants/README.zh.md)。 - - - - - -## Cordis API - -Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). - - - -### `ctx.invariants` — `InvariantRegistry` - -Package-owned invariant registry with global and regex-based selection. - -```ts cordis-catalog -/** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ -register(packageName: string, installer: InvariantInstaller): () => void -``` - -Source: [`packages/runtime-diagnostics/invariants/src/index.ts`](../../packages/runtime-diagnostics/invariants/src/index.ts) - diff --git a/docs/subsystems/jobs.i18n.yaml b/docs/subsystems/jobs.i18n.yaml index 12ed09735f..a047d45923 100644 --- a/docs/subsystems/jobs.i18n.yaml +++ b/docs/subsystems/jobs.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/jobs.md /background-job-runtime: - en: c2ca80844a3afaf3 - zh: a8b48d41aa75427a + en: dd515d8548864a91 + zh: 16a117d39989a373 /background-job-runtime/ids-and-status: en: 21cd01e194bd3a83 zh: 33cf767ac89e3406 @@ -12,8 +12,8 @@ en: e6c0d8fbfce13143 zh: b774b97709c77c9c /background-job-runtime/producer-contract: - en: 2b9c087048b73091 - zh: 9d1ff560ce79c9b1 + en: af0be338a0ba5e60 + zh: fc0eefb8c31f3a98 /background-job-runtime/the-output-ring: en: eee762c5ae896a2d zh: 93984e2007381ae8 diff --git a/docs/subsystems/jobs.md b/docs/subsystems/jobs.md index 461515bd51..2e893ac194 100644 --- a/docs/subsystems/jobs.md +++ b/docs/subsystems/jobs.md @@ -2,7 +2,7 @@ English | [中文](jobs.zh.md) -Types shared by long-running producers, `ctx.jobs`, and job controls. The [seam consolidation Agent Note](../../.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md) owns the current design and the [runtime Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) its origin; this page records the exact fields and variants from [`packages/jobs/jobs/src/types.ts`](../../packages/jobs/jobs/src/types.ts) and the client-safe [`view.ts`](../../packages/jobs/jobs/src/view.ts) leaf. +Types shared by long-running producers, `ctx.jobs`, and job controls. The [seam consolidation Agent Note](../../.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.md) owns the current design and the [runtime reference](../../packages/jobs/jobs/README.md) its origin; this page records the exact fields and variants from [`packages/jobs/jobs/src/types.ts`](../../packages/jobs/jobs/src/types.ts) and the client-safe [`view.ts`](../../packages/jobs/jobs/src/view.ts) leaf. ## Ids and status @@ -15,7 +15,6 @@ Types shared by long-running producers, `ctx.jobs`, and job controls. The [seam */ interface JobKindMap { bash: 'bash' - subagent: 'subagent' } ``` @@ -87,7 +86,7 @@ interface JobToolCall { * the output ring. */ interface JobSpec { - /** Producer kind — also the id prefix (`bash`, `subagent`, …). */ + /** Producer kind — also the id prefix (`bash`, …). */ kind: JobKind /** One-line model-facing label (the command; the delegation description). */ label: string @@ -151,7 +150,7 @@ interface JobHandle { } ``` -`JobHooks.done` resolves after the producer releases its resources, not merely when work finishes. A job whose result is a value rather than a stream — a subagent's report, a workflow's rendered result — returns it as `JobOutcome.result`; the model's first read after settlement carries it once. +`JobHooks.done` resolves after the producer releases its resources, not merely when work finishes. A job whose result is a value rather than a stream — a workflow's rendered result — returns it as `JobOutcome.result`; the model's first read after settlement carries it once. ```ts type-equiv /** Hooks through which the runtime controls and observes producer work. */ diff --git a/docs/subsystems/jobs.zh.md b/docs/subsystems/jobs.zh.md index f60b17252c..9460709ddf 100644 --- a/docs/subsystems/jobs.zh.md +++ b/docs/subsystems/jobs.zh.md @@ -2,7 +2,7 @@ [English](jobs.md) | 中文 -长时间运行的生产方、`ctx.jobs` 与任务控制命令共用的类型。[seam 收敛 Agent Note](../../.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md) 负责当前设计,[运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md) 记录其起源;本页记录 [`packages/jobs/jobs/src/types.ts`](../../packages/jobs/jobs/src/types.ts) 与客户端安全叶子 [`view.ts`](../../packages/jobs/jobs/src/view.ts) 中的确切字段与变体。 +长时间运行的生产方、`ctx.jobs` 与任务控制命令共用的类型。[seam 收敛 Agent Note](../../.agents/notes/implemented/architecture/2026-09-03-jobs-seam-consolidation.zh.md) 负责当前设计,[运行时 reference](../../packages/jobs/jobs/README.zh.md) 记录其起源;本页记录 [`packages/jobs/jobs/src/types.ts`](../../packages/jobs/jobs/src/types.ts) 与客户端安全叶子 [`view.ts`](../../packages/jobs/jobs/src/view.ts) 中的确切字段与变体。 ## ID 与状态 @@ -15,7 +15,6 @@ */ interface JobKindMap { bash: 'bash' - subagent: 'subagent' } ``` @@ -87,7 +86,7 @@ interface JobToolCall { * the output ring. */ interface JobSpec { - /** Producer kind — also the id prefix (`bash`, `subagent`, …). */ + /** Producer kind — also the id prefix (`bash`, …). */ kind: JobKind /** One-line model-facing label (the command; the delegation description). */ label: string @@ -151,7 +150,7 @@ interface JobHandle { } ``` -`JobHooks.done` 会在生产方释放其资源后 resolve,而不是仅在工作完成时 resolve。结果是一个值而非流的 job——subagent 的报告、workflow 渲染出的结果——把它作为 `JobOutcome.result` 返回;模型在结算后的第一次读取携带它一次。 +`JobHooks.done` 会在生产方释放其资源后 resolve,而不是仅在工作完成时 resolve。结果是一个值而非流的 job——workflow 渲染出的结果——把它作为 `JobOutcome.result` 返回;模型在结算后的第一次读取携带它一次。 ```ts type-equiv /** Hooks through which the runtime controls and observes producer work. */ diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index a1d22cc698..8491f61b6d 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -12,8 +12,8 @@ en: a18fe401763efde4 zh: ef59a76d9c694654 /llm-streaming/compact-assistant-streams: - en: 4d0d51bc3c4cbc9b - zh: b5c386d8e995dd06 + en: a2d35127e0b8ca72 + zh: fb1ba6e5a69adef7 /llm-streaming/llmfailure: en: 13415501869b4ca9 zh: 723c7533cce3d1d6 @@ -27,8 +27,8 @@ en: cbab49d689311d4d zh: 76246140eabaa55e /llm-streaming/appidentity-app-attribution: - en: 7ff4b18ef82a959d - zh: 8a40e6c7c57258a4 + en: e053f18c7dc070a8 + zh: 90ded9a892ed2fd5 /llm-streaming/tokenusage: en: 33830f78fdb845fa zh: f43a4502c4c3bfff @@ -36,11 +36,11 @@ en: 58dec2259b0b1c6f zh: fc99fe965acaabf3 /llm-streaming/the-model-request: - en: ddca84b082554567 - zh: 804ffb1cb0c94bfa + en: 824d18f76f990711 + zh: 8a2895513652e528 /llm-streaming/the-model-request/the-request-envelope-llmcallconfig-and-the-logged-header: - en: 8b6e6b65ccea943f - zh: bce1edd6386e37b1 + en: f2e6932e211f179b + zh: 75112586a6f3f4aa /llm-streaming/official-deepseek-request-extensions: en: b19f291f094157ed zh: 682f13a833e8bb52 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index a8c7b47bd7..f87c1e9d45 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -223,7 +223,7 @@ type StreamChunk = `snapshot()` returns a detached immutable stream. `expandAssistantStream()` strictly checks record keys, member counts, indexes, timestamps, tool-call identity, and lossless JSON before recreating the exact timed chunk sequence. The Session log embeds this stream in `assistant/message` for a surface result or `assistant/attempt` for an attempt with no surface message. -Process-local `agent/assistant-stream` frames carry live presentation. Durable replay and restore validation still expand the embedded settlement; telemetry, token accounting, and Host folds read the compact records directly. Record-level readers (`assistantStreamFirstTokenTime`, `assistantStreamHasVisibleContent`, `assistantStreamHasVisibleText`, `lastAssistantStreamChunk`, `assistantStreamChunks`, `joinAssistantStreamText`, `assembleAssistantStream`, and the per-run `runFirstTokenTime` and `runFirstVisibleTime`) answer consumer questions in one pass over the records with early exit, so a large history costs O(records) per settlement instead of O(members) expansion ([fold decision](../../.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.md)). `expandAssistantStream()` remains the validating path for records read at a durable boundary and for consumers that need every member. +Process-local `agent/assistant-stream` frames carry live presentation. Durable replay and restore validation still expand the embedded settlement; telemetry, token accounting, and Host folds read the compact records directly. Record-level readers (`assistantStreamFirstTokenTime`, `assistantStreamHasVisibleContent`, `assistantStreamHasVisibleText`, `lastAssistantStreamChunk`, `assistantStreamChunks`, `joinAssistantStreamText`, `assembleAssistantStream`, and the per-run `runFirstTokenTime` and `runFirstVisibleTime`) answer consumer questions in one pass over the records with early exit, so a large history costs O(records) per settlement instead of O(members) expansion ([fold reference](../../packages/llm/llm/README.md)). `expandAssistantStream()` remains the validating path for records read at a durable boundary and for consumers that need every member. ## `LlmFailure` @@ -311,7 +311,7 @@ Retry configuration resolves before route registration into an immutable discrim ## `AppIdentity` — app attribution -The static public application identity every adapter sends to providers ([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts)). `attributionHeaders(identity?)` maps it to the standard `User-Agent` header only; OpenRouter-specific app attribution headers are intentionally not supported by this contract. The default `APP_IDENTITY` sources its version from the package manifest; every field is a public product fact - no secrets, paths, session ids, or per-user identifiers, and nothing per-request may influence the values. Rationale: [Mandatory `User-Agent` attribution](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md). +The static public application identity every adapter sends to providers ([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts)). `attributionHeaders(identity?)` maps it to the standard `User-Agent` header only; OpenRouter-specific app attribution headers are intentionally not supported by this contract. The default `APP_IDENTITY` sources its version from the package manifest; every field is a public product fact - no secrets, paths, session ids, or per-user identifiers, and nothing per-request may influence the values. ```ts type-equiv /** @@ -529,7 +529,7 @@ interface LlmModelContext { } ``` -Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the ordered set, display names, and optional deployment default. +Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the choices in increasing effort order, display names, and optional deployment default. Ordering compares selectable controls, not predicted token use, cost, or latency. ```ts type-equiv /** Adapter-owned identifier for one model's selectable reasoning effort. */ @@ -551,7 +551,7 @@ interface LlmReasoningEffortInfo { ```ts type-equiv /** Selectable reasoning efforts for one exact provider/model route. */ interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ + /** Supported efforts from least to greatest selectable reasoning effort, not predicted token use or latency. */ efforts: readonly LlmReasoningEffortInfo[] /** * Adapter-configured default materialized into requests when callers omit @@ -634,8 +634,8 @@ interface GenerateOptions { sessionId?: Branded<'SessionId'> /** * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. + * map the purpose to model-hidden transport metadata. Ordinary conversation + * requests leave it unset. */ purpose?: 'compaction' | 'session-title' } @@ -740,10 +740,20 @@ The loop builds each request from logged state. `EpochHeader` records call confi `agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. On step admission, this waterfall and preparation run after assembly and `step/start` but before the system prompt and accepted user batch are committed; cancellation during either commits neither. The prepared capability governs prompt reconciliation, and the call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests. -On the wire, a loop-built request is the derived history alone: the rendered prompt travels as the leading `system`-role message (surface node 0, a `system/message` event) and, when the prepared call declares `systemPromptUpdate: 'in-history'`, a non-empty changed prompt may follow the cached history as a later `system`-role message that the model reads as the effective prompt; the request's `system` field is unset — `GenerateOptions.system` serves direct one-shot callers such as title providers. An empty rendering leaves no system messages in derived history, even when earlier requests retained several prompt versions. The logged request ends with the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request and rejects a loop request carrying a `system` field. +On the wire, a loop-built request is the derived history alone: the rendered prompt travels as the leading `system`-role message (surface node 0, a `system/message` event) and, when the prepared call declares `systemPromptUpdate: 'in-history'`, a non-empty changed prompt may follow the cached history as a later `system`-role message that the model reads as the effective prompt; the request's `system` field is unset — `GenerateOptions.system` serves direct one-shot callers such as title providers. An empty rendering leaves no system messages in derived history, even when earlier requests retained several prompt versions. The logged request ends with the newest `user/message` on a turn's first step and the previous step's tool results on later steps. FIXME(call-config-shape): revisit which remaining fields are genuinely epoch-level for cache purposes (`model` and the model-owned reasoning effort are explicit; the sampling scalars sit here out of caution). +```ts type-equiv +/** Concrete generation settings; omitted controls use the selected route's defaults. */ +interface LlmCallControls { + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + ```ts type-equiv /** * Provider, model, reasoning effort, and sampling scalars of one conversation's @@ -751,16 +761,28 @@ FIXME(call-config-shape): revisit which remaining fields are genuinely epoch-lev * the loop builds requests from the logged header rather than accepting these * per call. */ -interface LlmCallConfig { +interface LlmCallConfig extends LlmCallControls { provider: string model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] } ``` +```ts type-equiv +/** + * Synchronous, pure configuration of one call before defaults and validation. + * Compose functions in the desired order; later writes replace earlier ones. + * The returned controls cannot change the captured route. Errors reject + * preparation before dispatch; only the resolved configuration is recordable. + * @param controls - detached, deeply frozen proposed controls, without defaults. + * @param model - detached, deeply frozen metadata from the captured adapter generation. + * @returns concrete controls; omitted fields receive the route's defaults. + */ +type ConfigureCall = ( + controls: Readonly, + model: Readonly, +) => LlmCallControls +``` + ```ts type-equiv /** * Effective config fields supplied by exact-model adapter resolution rather @@ -1054,11 +1076,15 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise +async prepareCall( config: LlmCallConfig, signal?: AbortSignal, configure?: ConfigureCall, ): Promise /** * Stream one model call as raw chunks (token-level deltas). Replay state is diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index 89148283d9..2f3c90e096 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -223,7 +223,7 @@ type StreamChunk = `snapshot()` 返回分离且不可变的 stream。`expandAssistantStream()` 会严格检查 record key、成员数、index、时间戳、tool-call identity 与无损 JSON,再重建精确的带时间 chunk 序列。Session 日志会把该 stream 嵌入作为 surface result 的 `assistant/message`,或嵌入没有 surface message 的 `assistant/attempt`。 -进程本地 `agent/assistant-stream` frame 承载实时呈现。持久回放与恢复校验仍会展开内嵌 settlement;遥测、token 记账与 Host 折叠直接读取紧凑记录。记录级读取器(`assistantStreamFirstTokenTime`、`assistantStreamHasVisibleContent`、`assistantStreamHasVisibleText`、`lastAssistantStreamChunk`、`assistantStreamChunks`、`joinAssistantStreamText`、`assembleAssistantStream` 以及按 run 的 `runFirstTokenTime` 与 `runFirstVisibleTime`)以提前退出在一次扫描内回答消费方问题,因此大历史每次结算的代价为 O(records) 而非 O(members) 展开([折叠决策](../../.agents/notes/implemented/architecture/2026-09-06-embedded-stream-record-readers.zh.md))。`expandAssistantStream()` 仍是持久边界读取记录与需要每个成员的消费方的校验路径。 +进程本地 `agent/assistant-stream` frame 承载实时呈现。持久回放与恢复校验仍会展开内嵌 settlement;遥测、token 记账与 Host 折叠直接读取紧凑记录。记录级读取器(`assistantStreamFirstTokenTime`、`assistantStreamHasVisibleContent`、`assistantStreamHasVisibleText`、`lastAssistantStreamChunk`、`assistantStreamChunks`、`joinAssistantStreamText`、`assembleAssistantStream` 以及按 run 的 `runFirstTokenTime` 与 `runFirstVisibleTime`)以提前退出在一次扫描内回答消费方问题,因此大历史每次结算的代价为 O(records) 而非 O(members) 展开([折叠参考](../../packages/llm/llm/README.zh.md))。`expandAssistantStream()` 仍是持久边界读取记录与需要每个成员的消费方的校验路径。 @@ -313,7 +313,7 @@ interface LlmImageRequestPricing { ## `AppIdentity`:应用归属 -每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该约定有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包 manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md)。 +每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该约定有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包 manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。 ```ts type-equiv /** @@ -535,7 +535,7 @@ interface LlmModelContext { } ``` -推理强度是另一项针对确切路由的能力。核心为标识符添加品牌类型,但不枚举其值;有序集合、展示名称和可选的部署默认值均由各适配器持有。 +推理强度是另一项确切路由能力。核心为标识符添加品牌类型,但不枚举其值;每个适配器负责按强度递增排列的选项、显示名称与可选部署默认值。该顺序比较可选控制,不预测 token 用量、成本或延迟。 ```ts type-equiv /** Adapter-owned identifier for one model's selectable reasoning effort. */ @@ -557,7 +557,7 @@ interface LlmReasoningEffortInfo { ```ts type-equiv /** Selectable reasoning efforts for one exact provider/model route. */ interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ + /** Supported efforts from least to greatest selectable reasoning effort, not predicted token use or latency. */ efforts: readonly LlmReasoningEffortInfo[] /** * Adapter-configured default materialized into requests when callers omit @@ -640,8 +640,8 @@ interface GenerateOptions { sessionId?: Branded<'SessionId'> /** * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. + * map the purpose to model-hidden transport metadata. Ordinary conversation + * requests leave it unset. */ purpose?: 'compaction' | 'session-title' } @@ -746,10 +746,20 @@ interface LlmDiscoveredModel { `agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall(瀑布式事件)开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置以及由适配器默认值提供的字段。步骤准入时,该 waterfall 与准备过程在组装和 `step/start` 之后、系统提示词与已接纳用户批次提交之前运行;在任一阶段取消都不会提交这两者。已准备调用的能力决定提示词协调,调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。 -在协议中,循环构建的请求只有派生历史:渲染后的提示词作为开头的 `system` 角色消息(surface 第 0 号节点,即一个 `system/message` 事件)传输,并且当已准备调用声明 `systemPromptUpdate: 'in-history'` 时,变化后的非空提示词可以作为后续的 `system` 角色消息跟在已缓存历史之后,由模型读作有效提示词;请求的 `system` 字段不设置——`GenerateOptions.system` 服务于标题提供方等直接单次调用方。空渲染文本使派生历史不包含任何系统消息,即使先前请求保留了多个提示词版本。已记录的请求会以最新的 `user/message`(轮次首步)或上一步的工具结果(后续步骤)结尾。开发不变式针对每个循环构建的请求精确重算此等式,并拒绝携带 `system` 字段的循环请求。 +在协议中,循环构建的请求只有派生历史:渲染后的提示词作为开头的 `system` 角色消息(surface 第 0 号节点,即一个 `system/message` 事件)传输,并且当已准备调用声明 `systemPromptUpdate: 'in-history'` 时,变化后的非空提示词可以作为后续的 `system` 角色消息跟在已缓存历史之后,由模型读作有效提示词;请求的 `system` 字段不设置——`GenerateOptions.system` 服务于标题提供方等直接单次调用方。空渲染文本使派生历史不包含任何系统消息,即使先前请求保留了多个提示词版本。已记录的请求会以最新的 `user/message`(轮次首步)或上一步的工具结果(后续步骤)结尾。 FIXME(call-config-shape):重新审视其余哪些字段出于缓存目的确实属于 epoch 层级(`model` 和模型持有的推理强度已明确属于;采样标量目前出于谨慎保留在此)。 +```ts type-equiv +/** Concrete generation settings; omitted controls use the selected route's defaults. */ +interface LlmCallControls { + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + ```ts type-equiv /** * Provider, model, reasoning effort, and sampling scalars of one conversation's @@ -757,16 +767,28 @@ FIXME(call-config-shape):重新审视其余哪些字段出于缓存目的确 * the loop builds requests from the logged header rather than accepting these * per call. */ -interface LlmCallConfig { +interface LlmCallConfig extends LlmCallControls { provider: string model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] } ``` +```ts type-equiv +/** + * Synchronous, pure configuration of one call before defaults and validation. + * Compose functions in the desired order; later writes replace earlier ones. + * The returned controls cannot change the captured route. Errors reject + * preparation before dispatch; only the resolved configuration is recordable. + * @param controls - detached, deeply frozen proposed controls, without defaults. + * @param model - detached, deeply frozen metadata from the captured adapter generation. + * @returns concrete controls; omitted fields receive the route's defaults. + */ +type ConfigureCall = ( + controls: Readonly, + model: Readonly, +) => LlmCallControls +``` + ```ts type-equiv /** * Effective config fields supplied by exact-model adapter resolution rather @@ -1060,11 +1082,15 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise +async prepareCall( config: LlmCallConfig, signal?: AbortSignal, configure?: ConfigureCall, ): Promise /** * Stream one model call as raw chunks (token-level deltas). Replay state is diff --git a/docs/subsystems/mcp.i18n.yaml b/docs/subsystems/mcp.i18n.yaml index 2510f248ff..74f839078e 100644 --- a/docs/subsystems/mcp.i18n.yaml +++ b/docs/subsystems/mcp.i18n.yaml @@ -30,5 +30,5 @@ en: 27a59706525c88ed zh: 1fb1684bc03bb6ae /mcp/further-reading: - en: f63b120a0f4f9979 - zh: 524c191ba61b8802 + en: d9bb664b4fd11b99 + zh: f1a1d6badffdfb74 diff --git a/docs/subsystems/mcp.md b/docs/subsystems/mcp.md index 809fea9baf..0f68f9d7d0 100644 --- a/docs/subsystems/mcp.md +++ b/docs/subsystems/mcp.md @@ -107,9 +107,9 @@ MCP prompt templates, human-input elicitation, task-based execution, and resourc - [MCP package group](../../packages/mcp/README.md) — package entry points. - [MCP resources](../../packages/mcp/mcp-resources/README.md) — shared tools and resource-provider semantics. -- [Resource visibility decision](../../.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.md) — shared profile mounting and visibility from configured servers. +- [Resource visibility reference](../../packages/mcp/mcp-resources/README.md) — shared profile mounting and visibility from configured servers. - [Third-party memory servers](../user/guide/mcp-memory.md) — product configuration guide. -- [Protocol negotiation decision](../../.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.md) — SDK ownership and compatibility decisions. +- [Protocol negotiation reference](../../packages/mcp/mcp-client/README.md) — SDK ownership and compatibility decisions. diff --git a/docs/subsystems/mcp.zh.md b/docs/subsystems/mcp.zh.md index b1e5fcb0e9..1d62d6b98e 100644 --- a/docs/subsystems/mcp.zh.md +++ b/docs/subsystems/mcp.zh.md @@ -107,9 +107,9 @@ interface McpResourceProvider { - [MCP 包组](../../packages/mcp/README.zh.md) — 包入口。 - [MCP 资源](../../packages/mcp/mcp-resources/README.zh.md) — 共享工具与资源提供方语义。 -- [资源可见性决策](../../.agents/notes/implemented/feature/2026-09-13-mcp-resources-in-profiles.zh.md) — profile 统一挂载及由已配置服务器决定的可见性。 +- [资源可见性参考](../../packages/mcp/mcp-resources/README.zh.md) — profile 统一挂载及由已配置服务器决定的可见性。 - [第三方记忆服务器](../user/guide/mcp-memory.zh.md) — 产品配置指南。 -- [协议协商决策](../../.agents/notes/implemented/feature/2026-09-12-mcp-sdk-protocol-negotiation.zh.md) — SDK 职责与兼容性决策。 +- [协议协商参考](../../packages/mcp/mcp-client/README.zh.md) — SDK 职责与兼容性决策。 diff --git a/docs/subsystems/office-to-pdf.i18n.yaml b/docs/subsystems/office-to-pdf.i18n.yaml index 9bb356202b..309b486103 100644 --- a/docs/subsystems/office-to-pdf.i18n.yaml +++ b/docs/subsystems/office-to-pdf.i18n.yaml @@ -15,5 +15,5 @@ en: 715105e6fff89e7c zh: a7fe63c7dd0be17f /office-to-pdf/engine-selection-and-limits: - en: 3d5104e712d1e7cf - zh: dd060ba4c4e99a45 + en: 3908dab1b7d78890 + zh: 8f2aed6e27c5b5dd diff --git a/docs/subsystems/office-to-pdf.md b/docs/subsystems/office-to-pdf.md index a01478df54..92357607bb 100644 --- a/docs/subsystems/office-to-pdf.md +++ b/docs/subsystems/office-to-pdf.md @@ -37,7 +37,7 @@ The `api/remotes` assembly mounts the conversion service's generated Remote desc ## Engine selection and limits -The external [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit) Node API selects its precompiled engines. The kit has an independent version and release workflow, defined by the [release ownership decision](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md). Application builds install the published npm packages. Application packaging requires the target’s declared native engine, or Node WASM when the kit declares no native engine for that target. The [platform engine decision](../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.md) defines installation and packaging. Invalid metadata, missing required assets, and conversion errors reject without switching engines. Conversion uses disk input and output paths on the Host, with no browser conversion engine or font RPC. +The external [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-ai/dsh-libreoffice-kit) Node API selects its precompiled engines. The kit has an independent version and release workflow, defined by the [release ownership decision](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.md). Application builds install the published npm packages. Application packaging requires the target’s declared native engine, or Node WASM when the kit declares no native engine for that target. The [platform engine reference](../../packages/document/office-to-pdf/README.md) defines installation and packaging. Invalid metadata, missing required assets, and conversion errors reject without switching engines. Conversion uses disk input and output paths on the Host, with no browser conversion engine or font RPC. The [Host provider configuration](../../packages/document/office-to-pdf/README.md#use-this-package) owns concurrency, deadlines, input/output limits, archive limits, image resolution, and font access. Native/WASM implementation and asset distribution belong to the kit workspace. System LibreOffice discovery, runtime engine downloads, persistent PDF caching, and model-facing rendering are outside this provider. diff --git a/docs/subsystems/office-to-pdf.zh.md b/docs/subsystems/office-to-pdf.zh.md index b81468f130..88b0e8116b 100644 --- a/docs/subsystems/office-to-pdf.zh.md +++ b/docs/subsystems/office-to-pdf.zh.md @@ -37,7 +37,7 @@ ## 引擎选择和限制 -外部 [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-harness/libreoffice-kit) Node API 选择其预编译引擎。kit 独立维护版本和发布流程,具体归属由[发布归属决策](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md)定义。应用构建时安装已发布的 npm 包。应用打包要求目标已声明的原生引擎;kit 未为该目标声明原生引擎时使用 Node WASM。[平台引擎决策](../../.agents/notes/implemented/architecture/2026-09-15-platform-office-engines.zh.md)定义安装和打包规则。元数据无效、必需资源缺失和转换错误都会拒绝请求,不切换引擎。转换在 Host 使用磁盘输入输出路径,不使用浏览器转换引擎或字体 RPC。 +外部 [`@deepseek-ai/libreoffice-kit`](https://github.com/deepseek-ai/dsh-libreoffice-kit) Node API 选择其预编译引擎。kit 独立维护版本和发布流程,具体归属由[发布归属决策](../../.agents/notes/implemented/architecture/2026-09-14-independent-libreoffice-kit.zh.md)定义。应用构建时安装已发布的 npm 包。应用打包要求目标已声明的原生引擎;kit 未为该目标声明原生引擎时使用 Node WASM。[平台引擎参考](../../packages/document/office-to-pdf/README.zh.md)定义安装和打包规则。元数据无效、必需资源缺失和转换错误都会拒绝请求,不切换引擎。转换在 Host 使用磁盘输入输出路径,不使用浏览器转换引擎或字体 RPC。 [Host 提供方配置](../../packages/document/office-to-pdf/README.zh.md#use-this-package)负责并发、期限、输入输出上限、归档上限、图像分辨率和字体访问。原生/WASM 实现和资产分发属于 kit 工作区。系统 LibreOffice 探测、运行时引擎下载、持久 PDF 缓存和面向模型的渲染不属于此提供方。 diff --git a/docs/subsystems/permission-presets.i18n.yaml b/docs/subsystems/permission-presets.i18n.yaml index c1e89222ed..d0461f52c8 100644 --- a/docs/subsystems/permission-presets.i18n.yaml +++ b/docs/subsystems/permission-presets.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/permission-presets.md /permission-presets: - en: fa742ed576169157 - zh: 5af7ea95438908d7 + en: 936f306097ae3bcc + zh: 600da49564920881 /permission-presets/the-preset-table: en: a89ee42910e0c4c9 zh: 5207c0de66e6e358 diff --git a/docs/subsystems/permission-presets.md b/docs/subsystems/permission-presets.md index 564b304997..e5fa61c405 100644 --- a/docs/subsystems/permission-presets.md +++ b/docs/subsystems/permission-presets.md @@ -2,7 +2,7 @@ English | [中文](permission-presets.zh.md) -The permission-preset layer of [dsh-permission-presets](../../packages/interaction/permission-presets) (`ctx.permissionPresets`, `PermissionPresetService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. The configured table owns future-session defaults, while the fixed `registerAuto(admit)` hook lets the [Auto review](../../packages/experimental/auto-review/README.md) integration publish its current-session-only option for one effect lifetime. The layer is optional and owns no execution policy: prompt narration and replay keep reading their knob folds, while Auto review owns the additional enforcement. The [package README](../../packages/interaction/permission-presets/README.md) owns composition status and limitations; the [sandbox switching design](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the original knob rationale. +The permission-preset layer of [dsh-permission-presets](../../packages/interaction/permission-presets) (`ctx.permissionPresets`, `PermissionPresetService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. The configured table owns future-session defaults, while the fixed `registerAuto(admit)` hook lets the [Auto review](../../packages/experimental/auto-review/README.md) integration publish its current-session-only option for one effect lifetime. The layer is optional and owns no execution policy: prompt narration and replay keep reading their knob folds, while Auto review owns the additional enforcement. The [package README](../../packages/interaction/permission-presets/README.md) owns composition status and limitations; the [historical sandbox switching design](../../.agents/notes/archived/feature/2026-07-06-sandbox.md) records the original knob rationale. Source: [`packages/interaction/permission-presets/src/index.ts`](../../packages/interaction/permission-presets/src/index.ts) diff --git a/docs/subsystems/permission-presets.zh.md b/docs/subsystems/permission-presets.zh.md index 9cc7bfe611..9353cc8db3 100644 --- a/docs/subsystems/permission-presets.zh.md +++ b/docs/subsystems/permission-presets.zh.md @@ -2,7 +2,7 @@ [English](permission-presets.md) | 中文 -[dsh-permission-presets](../../packages/interaction/permission-presets) 的权限预设层(`ctx.permissionPresets`,`PermissionPresetService`)把两个相互独立的强制执行 knob,即[沙箱模式](sandbox.zh.md)(`sandbox/mode`)与[审批策略](approval.zh.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个 Permissions 选择器提供。配置表拥有未来会话默认值,而固定的 `registerAuto(admit)` 钩子让 [Auto review](../../packages/experimental/auto-review/README.zh.md) integration 在一个 effect 生命周期内发布仅限当前会话的选项。该层是可选能力,且不拥有执行策略:提示词叙述与回放仍读取各自 knob 的折叠结果,额外强制执行由 Auto review 拥有。[包 README](../../packages/interaction/permission-presets/README.zh.md)负责组合状态与限制;[沙箱切换设计](../../.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md)负责原始旋钮依据。 +[dsh-permission-presets](../../packages/interaction/permission-presets) 的权限预设层(`ctx.permissionPresets`,`PermissionPresetService`)把两个相互独立的强制执行 knob,即[沙箱模式](sandbox.zh.md)(`sandbox/mode`)与[审批策略](approval.zh.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个 Permissions 选择器提供。配置表拥有未来会话默认值,而固定的 `registerAuto(admit)` 钩子让 [Auto review](../../packages/experimental/auto-review/README.zh.md) integration 在一个 effect 生命周期内发布仅限当前会话的选项。该层是可选能力,且不拥有执行策略:提示词叙述与回放仍读取各自 knob 的折叠结果,额外强制执行由 Auto review 拥有。[包 README](../../packages/interaction/permission-presets/README.zh.md)负责组合状态与限制;[历史沙箱切换设计](../../.agents/notes/archived/feature/2026-07-06-sandbox.md)负责原始旋钮依据。 源码:[`packages/interaction/permission-presets/src/index.ts`](../../packages/interaction/permission-presets/src/index.ts) diff --git a/docs/subsystems/persistence.i18n.yaml b/docs/subsystems/persistence.i18n.yaml index e5c04a5931..aaf59a0f16 100644 --- a/docs/subsystems/persistence.i18n.yaml +++ b/docs/subsystems/persistence.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/persistence.md /session-persistence: - en: b3d66086d8863a84 - zh: dd532fd7c9947b35 + en: d6ca00339e679318 + zh: 917896dfe65cd710 /session-persistence/sessionhandle-one-open-channel-onto-a-stored-session: en: 283765de86ff454d zh: ef8fcf55a3071243 diff --git a/docs/subsystems/persistence.md b/docs/subsystems/persistence.md index 53a001f438..7c6b47deb1 100644 --- a/docs/subsystems/persistence.md +++ b/docs/subsystems/persistence.md @@ -4,7 +4,7 @@ English | [中文](persistence.zh.md) The **durability seam** for the event log. [session.md](session.md) describes the in-memory `Session` — the append-only `SessionEvent` log that is the source of truth. This page describes how that log is made durable: the abstract `SessionPersistence` service, its provider model and shipped JSONL backend, the flush checkpoint, crash recovery, and the metadata header that travels alongside the log. The event vocabulary the log carries is enumerated, member by member, in the generated [persistence log event catalog](../persistence-catalog.md). -The seam is a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](../../packages/session/session-persistence), `ctx.sessionPersistence`) exposing `create`/`open`/`stat`/`list` over the existing `SessionEvent` — **no parallel persisted event type** — where `create` and `open` return a per-session `SessionHandle` (`read`/`append`/`flush`/`close`) that carries all log access and single-writer ownership. The repository ships [dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl) as its provider; out-of-tree providers may implement the same service contract. See the [handle-based persistence Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) and the [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). +The seam is a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](../../packages/session/session-persistence), `ctx.sessionPersistence`) exposing `create`/`open`/`stat`/`list` over the existing `SessionEvent` — **no parallel persisted event type** — where `create` and `open` return a per-session `SessionHandle` (`read`/`append`/`flush`/`close`) that carries all log access and single-writer ownership. The repository ships [dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl) as its provider; out-of-tree providers may implement the same service contract. See the [handle-based persistence Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.md) and the [session-persistence reference](../../packages/session/session-persistence/README.md). ## `SessionHandle` — one open channel onto a stored session diff --git a/docs/subsystems/persistence.zh.md b/docs/subsystems/persistence.zh.md index 9f3c18a828..2883db7e22 100644 --- a/docs/subsystems/persistence.zh.md +++ b/docs/subsystems/persistence.zh.md @@ -4,7 +4,7 @@ 事件日志的**持久性 seam**。[session.md](session.zh.md) 描述了内存中的 `Session`:仅追加的 `SessionEvent` 日志即为真源。本页描述如何使该日志持久化:抽象的 `SessionPersistence` 服务、它的提供方模型与随产品交付的 JSONL 后端、flush 检查点、崩溃恢复,以及随日志一同存储的元数据头。日志承载的事件词汇在生成的[持久化日志事件目录](../persistence-catalog.zh.md)中逐项列举。 -该 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):一个抽象服务([dsh-session-persistence](../../packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上暴露 `create`/`open`/`stat`/`list`——**没有平行的持久化事件类型**——其中 `create` 与 `open` 返回逐会话的 `SessionHandle`(`read`/`append`/`flush`/`close`),它承载全部日志访问与单写者所有权。仓库随产品交付 [dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl) 作为其 provider;仓库外 provider 可以实现同一服务约定。见[基于句柄的持久化 Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与 [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.zh.md)。 +该 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):一个抽象服务([dsh-session-persistence](../../packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上暴露 `create`/`open`/`stat`/`list`——**没有平行的持久化事件类型**——其中 `create` 与 `open` 返回逐会话的 `SessionHandle`(`read`/`append`/`flush`/`close`),它承载全部日志访问与单写者所有权。仓库随产品交付 [dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl) 作为其 provider;仓库外 provider 可以实现同一服务约定。见[基于句柄的持久化 Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-handle-based-session-persistence.zh.md)与 [session-persistence reference](../../packages/session/session-persistence/README.zh.md)。 ## `SessionHandle`——通向已存储会话的一条打开通道 diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index df58df9e76..6ec7b89a19 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md /plan-mode: - en: d011d9b7480aa201 - zh: 7cb7854e45d486fa + en: 234f6e4c49714997 + zh: add95d36252b572d /plan-mode/logged-state-and-recovery: en: dd1e8ba31e6fd535 zh: a22dbccc60c73bc9 diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index 913a5acb45..1c29a87851 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -2,7 +2,7 @@ English | [中文](plan.zh.md) -Plan mode is logged per-agent collaboration state owned by [dsh-plan-mode](../../packages/plan/plan-mode) (`ctx.planMode`, `PlanModeController`): while active, a deployment-owned guidance section is included in each model request. Plan mode is **soft guidance**. [Sandbox mode](sandbox.md) and [approval policy](approval.md) enforce restrictions independently; neither reads or writes plan state, so deployments configure them separately. The package is optional, and the agent loop does not depend on it. It contributes the `plan:policy` prompt section and registers the `exit_plan_mode` tool and `/plan` command. The [design note](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md) owns the rationale; the [package README](../../packages/plan/plan-mode/README.md) owns the model-experience and limitation detail. +Plan mode is logged per-agent collaboration state owned by [dsh-plan-mode](../../packages/plan/plan-mode) (`ctx.planMode`, `PlanModeController`): while active, a deployment-owned guidance section is included in each model request. Plan mode is **soft guidance**. [Sandbox mode](sandbox.md) and [approval policy](approval.md) enforce restrictions independently; neither reads or writes plan state, so deployments configure them separately. The package is optional, and the agent loop does not depend on it. It contributes the `plan:policy` prompt section and registers the `exit_plan_mode` tool and `/plan` command. The [design reference](../../packages/plan/plan-mode/README.md) owns the rationale; the [package README](../../packages/plan/plan-mode/README.md) owns the model-experience and limitation detail. Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/src/index.ts) diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index 98e926346b..db2a7e1c2b 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -2,7 +2,7 @@ [English](plan.md) | 中文 -计划模式是 [dsh-plan-mode](../../packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeController`):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是**软性指引**。[沙箱模式](sandbox.zh.md)与[审批策略](approval.zh.md)分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包是可选项,agent loop(智能体循环)不依赖它。它贡献 `plan:policy` 提示词段落,并注册 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md)负责决策依据;[包 README](../../packages/plan/plan-mode/README.zh.md)负责模型体验与限制细节。 +计划模式是 [dsh-plan-mode](../../packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeController`):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是**软性指引**。[沙箱模式](sandbox.zh.md)与[审批策略](approval.zh.md)分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包是可选项,agent loop(智能体循环)不依赖它。它贡献 `plan:policy` 提示词段落,并注册 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](../../packages/plan/plan-mode/README.zh.md)负责决策依据;[包 README](../../packages/plan/plan-mode/README.zh.md)负责模型体验与限制细节。 源码:[`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/src/index.ts) diff --git a/docs/subsystems/schedule.i18n.yaml b/docs/subsystems/schedule.i18n.yaml index 07d6b0d545..1c240a9766 100644 --- a/docs/subsystems/schedule.i18n.yaml +++ b/docs/subsystems/schedule.i18n.yaml @@ -21,8 +21,8 @@ en: 1ba9ae84f05d8919 zh: 5b4abfdcddbbcae0 /host-wide-schedule/historical-session-changes: - en: 9cd7f65e0699aa91 - zh: 42f2a6fda33d2ff0 + en: d3bdcf22dd771a7d + zh: 11af3e617a7ef3fd /host-wide-schedule/active-views-and-management: en: 93798ad69f74ddd5 zh: 634126ad8accc356 diff --git a/docs/subsystems/schedule.md b/docs/subsystems/schedule.md index 32d6624e49..c1d842141b 100644 --- a/docs/subsystems/schedule.md +++ b/docs/subsystems/schedule.md @@ -172,7 +172,7 @@ Cron decoding preserves the committed instant and the stored zone spelling, and ## Historical Session changes -Version-1 `schedule/change` events remain decodable as historical Session data. Their create, fold, and invariant types use `LegacyScheduleRecord`, which admits only After, At, and Every; Daily, Weekly, and Cron belong only to the current Host `ScheduleRecord`. The Host record decoder is separate from the frozen historical decoder. A create record written before titles existed carries no `title`, so the historical decoder admits that absent member while the Host decoder still requires it. Historical events do not populate the storage domain or schedule delivery. Existing reminders in these events require explicit recreation through `schedule_create`; no implicit Session migration or conversion of old `at` records occurs. +Version-1 `schedule/change` events remain decodable as historical Session data. Their create and fold types use `LegacyScheduleRecord`, which admits only After, At, and Every; Daily, Weekly, and Cron belong only to the current Host `ScheduleRecord`. The Host record decoder is separate from the frozen historical decoder. A create record written before titles existed carries no `title`, so the historical decoder admits that absent member while the Host decoder still requires it. Historical events do not populate the storage domain or schedule delivery. Existing reminders in these events require explicit recreation through `schedule_create`; no implicit Session migration or conversion of old `at` records occurs. ```ts type-equiv /** diff --git a/docs/subsystems/schedule.zh.md b/docs/subsystems/schedule.zh.md index 0a7ba82a14..0203ad8a85 100644 --- a/docs/subsystems/schedule.zh.md +++ b/docs/subsystems/schedule.zh.md @@ -172,7 +172,7 @@ cron 解码保留已提交时点及已存储的时区拼写,并拒绝非规范 ## 历史 Session 变更 -版本 1 的 `schedule/change` 事件仍可作为历史 Session 数据解码。其 create、fold 和 invariant 类型使用 `LegacyScheduleRecord`,仅接受 After、At 和 Every;Daily、Weekly 与 Cron 仅属于当前宿主的 `ScheduleRecord`。宿主记录解码器独立于冻结的历史解码器。在 title 出现之前写入的 create 记录不含 `title`,因此历史解码器接受该缺失成员,而宿主解码器仍要求它。历史事件不会填充 storage domain 或触发投递。这些事件中的已有提醒需要显式通过 `schedule_create` 重新创建;不会隐式迁移 Session 或转换旧 `at` 记录。 +版本 1 的 `schedule/change` 事件仍可作为历史 Session 数据解码。其 create 和 fold 类型使用 `LegacyScheduleRecord`,仅接受 After、At 和 Every;Daily、Weekly 与 Cron 仅属于当前宿主的 `ScheduleRecord`。宿主记录解码器独立于冻结的历史解码器。在 title 出现之前写入的 create 记录不含 `title`,因此历史解码器接受该缺失成员,而宿主解码器仍要求它。历史事件不会填充 storage domain 或触发投递。这些事件中的已有提醒需要显式通过 `schedule_create` 重新创建;不会隐式迁移 Session 或转换旧 `at` 记录。 ```ts type-equiv /** diff --git a/docs/subsystems/scope.i18n.yaml b/docs/subsystems/scope.i18n.yaml index 0db81be24d..bce762d85d 100644 --- a/docs/subsystems/scope.i18n.yaml +++ b/docs/subsystems/scope.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/scope.md /scoped-registration: - en: ebcefe4dd75f9a9c - zh: e7a4edad80d59002 + en: b786b68190a93d3f + zh: 8ea0fb2fac9a1529 /scoped-registration/identity-and-dispatch-carrier: en: f4e1bdd96c43a845 zh: c74409f7f81bd0e0 diff --git a/docs/subsystems/scope.md b/docs/subsystems/scope.md index 1ed297f7ae..71ffe8a74b 100644 --- a/docs/subsystems/scope.md +++ b/docs/subsystems/scope.md @@ -2,7 +2,7 @@ English | [中文](scope.zh.md) -The [scope package](../../packages/core/scope) supplies the identity, carrier, and scoped-layer vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design Agent Note](../../.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md#scope-routing-one-opaque-key-selects-one-layer) owns the lifecycle rationale, and the package [README](../../packages/core/scope/README.md) owns the callable API and filtering semantics. +The [scope package](../../packages/core/scope) supplies the identity, carrier, and scoped-layer vocabulary that makes one registration context mean both per-agent visibility and shared lifetime ownership. It is a library primitive rather than a Cordis service; the [agent-scope runtime-design reference](../../packages/core/agent-loop/README.md) owns the lifecycle rationale, and the package [README](../../packages/core/scope/README.md) owns the callable API and filtering semantics. Sources: [`packages/core/scope/src/index.ts`](../../packages/core/scope/src/index.ts) and [`packages/core/scope/src/store.ts`](../../packages/core/scope/src/store.ts). diff --git a/docs/subsystems/scope.zh.md b/docs/subsystems/scope.zh.md index 3633c99003..db791a407f 100644 --- a/docs/subsystems/scope.zh.md +++ b/docs/subsystems/scope.zh.md @@ -2,7 +2,7 @@ [English](scope.md) | 中文 -[scope 包](../../packages/core/scope)提供身份、载体与作用域层词汇,使同一注册上下文同时表达每个 agent(智能体)的可见性和共享生命周期所有权。它是库原语,而不是 Cordis 服务;生命周期设计理由由 [agent-scope 运行时设计 Agent Note](../../.agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.zh.md#scope-routing-one-opaque-key-selects-one-layer)规定,可调用 API 与过滤语义则由包 [README](../../packages/core/scope/README.zh.md)规定。 +[scope 包](../../packages/core/scope)提供身份、载体与作用域层词汇,使同一注册上下文同时表达每个 agent(智能体)的可见性和共享生命周期所有权。它是库原语,而不是 Cordis 服务;生命周期设计理由由 [agent-scope 运行时设计 reference](../../packages/core/agent-loop/README.zh.md)规定,可调用 API 与过滤语义则由包 [README](../../packages/core/scope/README.zh.md)规定。 源码:[`packages/core/scope/src/index.ts`](../../packages/core/scope/src/index.ts) 与 [`packages/core/scope/src/store.ts`](../../packages/core/scope/src/store.ts)。 diff --git a/docs/subsystems/session-title.i18n.yaml b/docs/subsystems/session-title.i18n.yaml index 1ca280db5c..a701f0ad9d 100644 --- a/docs/subsystems/session-title.i18n.yaml +++ b/docs/subsystems/session-title.i18n.yaml @@ -9,8 +9,8 @@ en: c6be2d86647daa6f zh: fb259d9fb4738f39 /session-titles/auxiliary-request-record: - en: 4942bec7a7d1d9de - zh: 9edfc1c908577c37 + en: c36916905dad76f9 + zh: c82ff6c3d62e53e5 /session-titles/provider-input-and-output: en: 886e036e442ae06a zh: 49f95782b8aa9bf3 diff --git a/docs/subsystems/session-title.md b/docs/subsystems/session-title.md index 75e17f06d7..699b0148a6 100644 --- a/docs/subsystems/session-title.md +++ b/docs/subsystems/session-title.md @@ -64,7 +64,7 @@ interface SessionTitleSnapshot extends SessionTitleEventData { ## Auxiliary request record -The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, provider ownership, and source-message attribution even when generation later fails. +The shared LLM helper records each validated, dispatchable title request before calling the model. The payload reproduces the model-visible system and message input, routing, output limit, resolved reasoning effort, provider ownership, and source-message attribution even when generation later fails. ```ts type-equiv /** Exact model-visible request recorded before one auxiliary title dispatch. */ @@ -81,6 +81,8 @@ interface SessionTitleLlmRequestEventData { readonly messages: Message[] /** Exact auxiliary output-token cap. */ readonly maxTokens: number + /** Resolved reasoning effort when recorded; older requests can omit it. */ + readonly reasoningEffort?: ReasoningEffortId } ``` diff --git a/docs/subsystems/session-title.zh.md b/docs/subsystems/session-title.zh.md index 187dad8f3a..1b360411a8 100644 --- a/docs/subsystems/session-title.zh.md +++ b/docs/subsystems/session-title.zh.md @@ -64,7 +64,7 @@ interface SessionTitleSnapshot extends SessionTitleEventData { ## 辅助请求记录 -共享 LLM 辅助组件会在调用模型前,记录每一项已经过验证且可分发的标题请求。即使后续生成失败,载荷仍会复现模型可见的系统输入与消息输入、路由、输出上限、提供方归属和源消息归因。 +共享 LLM 辅助组件会在调用模型前,记录每一项已经过验证且可分发的标题请求。即使后续生成失败,载荷仍会复现模型可见的系统输入与消息输入、路由、输出上限、解析后的推理强度、提供方归属和源消息归因。 ```ts type-equiv /** Exact model-visible request recorded before one auxiliary title dispatch. */ @@ -81,6 +81,8 @@ interface SessionTitleLlmRequestEventData { readonly messages: Message[] /** Exact auxiliary output-token cap. */ readonly maxTokens: number + /** Resolved reasoning effort when recorded; older requests can omit it. */ + readonly reasoningEffort?: ReasoningEffortId } ``` diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index e4269bb273..1a778923dc 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -12,8 +12,8 @@ en: 6eb931fcb2f4ab8d zh: d822a83384cc1fee /sessions/sessioneventmap-the-event-vocabulary/the-route-capacity-event-request-context: - en: e6bf2297344b77e3 - zh: f99beedebc892f6a + en: 7e21ea8bcdb16b7a + zh: a377774d3596dbeb /sessions/sessionevent-t-one-log-entry: en: 622d333d9d903795 zh: fdc9ee663330f01e @@ -42,23 +42,26 @@ en: 04e9982948d9560d zh: 221961cadebc6fb1 /sessions/derived-history-derivemessages-and-deriveeventmessage: - en: 6e48ebd63b85f473 - zh: c8ab986e975bbce2 + en: 45d1a9ee6d610ac5 + zh: 79c061252fde4ceb /sessions/live-session-fork-api: - en: e779481af02174c6 - zh: 848a6a0a797c7d29 + en: c72c73ce4b4c697c + zh: 8a34785a92df0ad5 /sessions/why-a-turn-ended-turnendreasonmap: en: 966cc60273fb3911 zh: de5123ea9dbb0df2 /sessions/execution-enclosure-and-standalone-events: - en: 5d33a4901131123a - zh: c774d7d32cb2dcfe + en: 9091c5446e8cb9a2 + zh: 04c8c4fd5d0eaefc /sessions/the-end-seed-boundary-session-end-seed: en: 63b4059793255ca3 zh: 6bc7891c320249f4 /sessions/plugin-contributed-log-only-events: - en: 6b895a381f28a41c - zh: b2fe46ba9b5304c4 + en: 0a014a755b75449f + zh: 9d59d74490a1bcb2 +/sessions/plugin-contributed-log-only-events/plugin-records: + en: 15102fd0b937fbf3 + zh: 37ebf1d715db097c /sessions/durability-contract: en: 62e3e4fbcba292a3 zh: cb6dcaf531621e85 diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 415201d159..1dd08630bb 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -160,8 +160,8 @@ interface SessionEventMap { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -207,7 +207,7 @@ Current event acceptance requires canonical `request/header.header`: any `system ### The route capacity event: `request/context` -The context metadata of the route a request resolved to is separate logged state, appended beside `request/header` inside the same step and only when the provider, model, capacity, or `systemPromptUpdate` mode differs from the previous record. It stays outside `EpochHeader` because that type is the reconstruction contract compared field-wise by `headerEquals`: capacity and the update mode describe a route, not a request input, so folding them in would let a route change register as a request-envelope `change` and would pull adapter metadata into the loop's reconstruction invariant. Like `request/header`, it is not a `SurfaceEventType` and produces no LLM message. `session.requestContext()` folds the latest record incrementally; the agent loop reads that record's `systemPromptUpdate` when it decides whether a changed system prompt replaces the latest system node or is appended after the cached history ([decision rule](../../packages/core/agent-loop/README.md#understand-the-implementation)). A route whose adapter advertises no capacity is recorded with `contextWindow` absent, so the new record clears an older route's capacity; a route without a declared update mode likewise clears an older route's `systemPromptUpdate`. +The context metadata of the route a request resolved to is separate logged state, appended beside `request/header` inside the same step and only when the provider, model, capacity, or `systemPromptUpdate` mode differs from the previous record. It stays outside `EpochHeader` because that type is the reconstruction contract compared field-wise by `headerEquals`: capacity and the update mode describe a route, not a request input, so folding them in would let a route change register as a request-envelope `change` and would pull adapter metadata into the loop's request reconstruction. Like `request/header`, it is not a `SurfaceEventType` and produces no LLM message. `session.requestContext()` folds the latest record incrementally; the agent loop reads that record's `systemPromptUpdate` when it decides whether a changed system prompt replaces the latest system node or is appended after the cached history ([decision rule](../../packages/core/agent-loop/README.md#understand-the-implementation)). A route whose adapter advertises no capacity is recorded with `contextWindow` absent, so the new record clears an older route's capacity; a route without a declared update mode likewise clears an older route's `systemPromptUpdate`. ```ts type-equiv /** Registration-bound metadata for one resolved model route. */ @@ -665,7 +665,7 @@ declare class Session { ## Derived history: `deriveMessages()` and `deriveEventMessage()` -`Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors and the dev invariant project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules: +`Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules: - `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata. - `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Its embedded compact stream is replay, usage, and UI evidence rather than a second message. An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its stream, usage, provider, and model, but a content-less assistant turn must not enter the provider transcript. @@ -680,7 +680,7 @@ Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retr - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events, `parentSession`, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`. -An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. Broader execution-relation sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit. +An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit. ## Why a turn ended: `TurnEndReasonMap` @@ -732,7 +732,7 @@ interface TurnEndReasonMap { A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence admits every contiguous accepted event into a bounded durable batch, while crash repair closes only a genuinely open trailing turn. A producer that needs an immediate durability barrier explicitly awaits `ctx.sessions.flush(session)`. -The optional `dsh-session/invariant` companion enforces the relations owned by core: turn and step numbering, execution-event enclosure, and same-step tool call/result pairing. Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md). +Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md). ## The end-seed boundary: `session/end-seed` @@ -746,12 +746,55 @@ Consumers that order Sessions by human activity exclude this boundary: picking a ## Plugin-contributed log-only events -A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](compaction.md). +A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](compaction.md). When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](conversation.md). The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn. +### Plugin records + +Experimental packages declare plugin-owned state in `PluginRecordMap`: `appendPluginRecord()` uses each declaration to type its name and payload, and appends an event marked `ignorable: true` in the `plugin:` namespace. `pluginRecordOf()` reads stored records with unknown payloads regardless of current declarations. The [experimental persistence catalog](../experimental-persistence-catalog.md) lists current declarations. The [package README](../../packages/core/session/README.md#write-experimental-plugin-records) owns the caller restriction, retention, and read path. + +```ts type-equiv +/** + * Event type of an experimental plugin record: `plugin:` followed by + * slash-separated segments of lowercase letters, digits, `.`, `_`, and `-`, + * each starting with a letter or digit. A record type is never a + * {@link SessionEventMap} member. + */ +type PluginRecordType = `plugin:${string}` +``` + +```ts type-equiv +/** + * Payloads written by experimental packages, keyed by their `plugin:` record + * names. Packages augment this map to type writes and enter the current plugin + * record catalog; these declarations do not enter {@link SessionEventMap} or + * released persistence schemas. Stored records still require owner validation. + */ +interface PluginRecordMap {} +``` + +```ts type-equiv +/** + * One committed experimental plugin record, read from the log by + * `pluginRecordOf`. The record's owner validates `data` before use, because a + * restored record carries whatever JSON an earlier build of its owner wrote, + * or a V3 event that a format migration renamed into the `plugin:` namespace held. + */ +interface PluginRecord { + /** The record type, chosen by the owning plugin. */ + readonly type: PluginRecordType + /** The record's position in the Session log. */ + readonly seq: SessionSeq + /** Unix epoch milliseconds at append. */ + readonly time: number + /** The JSON payload as committed. */ + readonly data: unknown +} +``` + ## Durability contract What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL writes one row per event (see [persistence.md](persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format. @@ -799,7 +842,7 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise @@ -1051,8 +1094,8 @@ announce(session: Session): void * store owns the carrier, so callers (the checkpoint policy's per-request * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner + * and one spelling. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. @@ -1216,7 +1259,6 @@ Creation announcement during session publication. A synchronous throw vetoes and * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners * receive only sessions entered through that agent's context. * @param session - the session just entered and announced. - * @dshScopeScan unsupported * @mode emit */ 'session/created'(this: Scoped, session: Session): void @@ -1239,7 +1281,6 @@ Emitted once when an announced session leaves the store, including publication r * did not begin. Listener failures are logged and contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported * @mode emit */ 'session/disposed'(this: Scoped, session: Session): void @@ -1264,7 +1305,6 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before * receive only events from sessions entered through that agent's context. * @param session - the session whose log grew. * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported * @mode emit */ 'session/event'(this: Scoped, session: Session, event: SessionEvent): void @@ -1286,7 +1326,6 @@ Awaited parallel durability checkpoint: every listener runs and the caller await * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported * @mode parallel */ 'session/flush'(this: Scoped, session: Session): Promise | void diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index e52c08e819..8085cb8067 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -160,8 +160,8 @@ interface SessionEventMap { * keep ordinary restore and replay lifecycle boundaries. * * Only the `Session` constructor and `buildForkSeed` may create this marker. - * The invariant companion deliberately constrains nothing here, so a plugin - * appending one would silently classify every live bracket before it as seed history. + * Session append does not reject other writers, so a plugin appending one + * would silently classify every live bracket before it as seed history. * * An owner of a standalone open/close bracket (`compaction/start` … * `compaction/end`) reads it because seed history and live work are otherwise @@ -207,7 +207,7 @@ interface EpochHeader { ### 路由容量事件:`request/context` -请求所解析到的路由的上下文元数据是独立的已记录状态,在同一步骤内紧随 `request/header` 追加,且仅在提供方、模型、容量或 `systemPromptUpdate` 模式与上一条记录不同时追加。它保持在 `EpochHeader` 之外,因为该类型是 `headerEquals` 逐字段比较的重建约定。容量与更新模式描述的是路由,不是请求输入,把它们折叠进去会让一次路由变化被登记为请求信封的 `change`,也会把适配器元数据拉进 loop 的重建不变式。与 `request/header` 一样,它不是 `SurfaceEventType`,也不产生 LLM 消息。`session.requestContext()` 以增量方式归并最新一条记录;agent loop 在决定变化后的系统提示词是替换最新的系统节点还是追加到已缓存历史之后时,读取该记录的 `systemPromptUpdate`([决策规则](../../packages/core/agent-loop/README.zh.md#understand-the-implementation))。适配器不公布容量的路由会以缺失 `contextWindow` 的形式记录,因此新记录可以清除较早路由的容量;未声明更新模式的路由同样会清除较早路由的 `systemPromptUpdate`。 +请求所解析到的路由的上下文元数据是独立的已记录状态,在同一步骤内紧随 `request/header` 追加,且仅在提供方、模型、容量或 `systemPromptUpdate` 模式与上一条记录不同时追加。它保持在 `EpochHeader` 之外,因为该类型是 `headerEquals` 逐字段比较的重建约定。容量与更新模式描述的是路由,不是请求输入,把它们折叠进去会让一次路由变化被登记为请求信封的 `change`,也会把适配器元数据拉进 loop 的请求重建。与 `request/header` 一样,它不是 `SurfaceEventType`,也不产生 LLM 消息。`session.requestContext()` 以增量方式归并最新一条记录;agent loop 在决定变化后的系统提示词是替换最新的系统节点还是追加到已缓存历史之后时,读取该记录的 `systemPromptUpdate`([决策规则](../../packages/core/agent-loop/README.zh.md#understand-the-implementation))。适配器不公布容量的路由会以缺失 `contextWindow` 的形式记录,因此新记录可以清除较早路由的容量;未声明更新模式的路由同样会清除较早路由的 `systemPromptUpdate`。 ```ts type-equiv /** Registration-bound metadata for one resolved model route. */ @@ -667,7 +667,7 @@ declare class Session { ## 派生历史:`deriveMessages()` 与 `deriveEventMessage()` -`Session.deriveMessages()` 将事件日志投影为模型看到的 `Message[]`。它是缓存的(每个 surface 节点在首次出现时投影一次;surface 重写触发重建)且冻结的(每次调用返回一个新数组,引用共享的深冻结消息,因此通过投影修改已记录的历史在类型上不可表达)。`deriveEventMessage(event)` 是折叠所应用的逐节点纯函数,公开暴露以便外部重建器和开发不变式检查能以完全相同的规则投影日志前缀,不会与缓存产生分歧。投影规则: +`Session.deriveMessages()` 将事件日志投影为模型看到的 `Message[]`。它是缓存的(每个 surface 节点在首次出现时投影一次;surface 重写触发重建)且冻结的(每次调用返回一个新数组,引用共享的深冻结消息,因此通过投影修改已记录的历史在类型上不可表达)。`deriveEventMessage(event)` 是折叠所应用的逐节点纯函数,公开暴露以便外部重建器能以完全相同的规则投影日志前缀,不会与缓存产生分歧。投影规则: - `user/message` → 一条携带确切 `content` 的 user 消息;可选 envelope 仅作为日志中的展示元数据保留。 - `assistant/message` → 一条 assistant 消息,包含生成它的提供方和模型,以及可选的适配器私有回放状态。其嵌入式紧凑 stream 是回放、usage 与 UI 证据,而不是第二条 message。**内容为空的** `assistant/message` 也会跳过:因 max-tokens 而截断且无内容的步骤仍会记录一条 `assistant/message` 来保存 stream、usage、提供方和模型,但无内容的 assistant 轮次不得进入提供方 transcript(文本记录)。 @@ -682,7 +682,7 @@ declare class Session { - `fork(source, boundary?, childSessionId?)` 接受一个活跃的 `Session` 对象或活跃的 `SessionId`,选取到 `SessionSeq` boundary(含)为止的源事件(默认为当前最后一个事件),要求所选前缀结束时没有开放轮次,然后创建一个活跃的子会话,包含深克隆的 seed event、`parentSession`、`isSeeded: true`、精确 `inheritedEventCount` 及继承的 `cwd`。 -显式 `boundary` 允许调用者从任意稳定的轮次间位置 fork,包括之前的 `turn/end` 或更晚的独立纯日志事件,即使源会话有更新的事件或正在进行的轮次。API 拒绝结束于开放轮次内的前缀,而不是静默截断。更广泛的执行关系健全性检查留在既有的 `dsh-invariants` 插件和持久化修复路径中,不在 `fork()` 中重复。`dsh-subagent-fork-in-process` 保留其已完成前缀截断逻辑,因为工具调用时的委托通常在父轮次仍然打开时启动;普通的会话分支应显式指定请求的 boundary。 +显式 `boundary` 允许调用者从任意稳定的轮次间位置 fork,包括之前的 `turn/end` 或更晚的独立纯日志事件,即使源会话有更新的事件或正在进行的轮次。API 拒绝结束于开放轮次内的前缀,而不是静默截断。`dsh-subagent-fork-in-process` 保留其已完成前缀截断逻辑,因为工具调用时的委托通常在父轮次仍然打开时启动;普通的会话分支应显式指定请求的 boundary。 @@ -736,7 +736,7 @@ interface TurnEndReasonMap { 一个轮次包围一次模型循环执行,而不是整个会话日志。AgentLoop 只会在轮次内进入 pre-step 批次时记录注入的 `user/message` 事件;插件所属的纯日志事件仍可出现在 `turn/end` 与下一个 `turn/start` 之间,占用事件 seq 但不递增轮次编号。持久化会将每个连续且已接受的事件纳入有界持久化批次,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要即时持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。 -可选的 `dsh-session/invariant` 配套插件会强制核心拥有的关系:轮次与步骤编号、执行事件封闭,以及同一步骤内的工具调用/结果配对。可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)。 +可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)。 ## 种子结束边界:`session/end-seed` @@ -750,12 +750,55 @@ interface TurnEndReasonMap { ## 插件贡献的仅日志事件 -插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。生成的[持久化日志事件目录](../persistence-catalog.zh.md)会列出每个核心或插件贡献的事件;压缩 seam 的 `compaction/*` 语义在 [compaction.md](compaction.zh.md) 中讨论。 +插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间。生成的[持久化日志事件目录](../persistence-catalog.zh.md)会列出每个核心或插件贡献的事件;压缩 seam 的 `compaction/*` 语义在 [compaction.md](compaction.zh.md) 中讨论。 如果同一个插件事件族中的多条事件要组装成一个 Web Client Conversation Node,该事件族中的每条 start、update、result、resource 或 interruption 事件都必须携带或独立推导出同一个稳定业务 id。此要求只约束需要关联的 Node 事件族,并不要求每条 Session 事件都有业务 id;Client 因此无须根据相邻关系猜测归属,也无须扫描历史。参见 [Conversation 子系统](conversation.zh.md)。 钩子桥接层的 `hook/invoked` / `hook/result` 对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 不生成 `hook/*` 记录,因为它在轮次 1 之前运行;其上下文会在 inbox 中保持待处理,直到唤醒交付打开一个轮次。 +### 插件记录 + +实验性包在 `PluginRecordMap` 中声明插件自有的状态:`appendPluginRecord()` 根据每项声明约束名称与 payload 类型,并在 `plugin:` 命名空间中追加一条带 `ignorable: true` 标记的事件。`pluginRecordOf()` 读取已存记录,其 payload 为未知类型,不受当前声明约束。[实验性持久化目录](../experimental-persistence-catalog.zh.md)列出当前声明。调用方限制、保留方式与读取路径见[包 README](../../packages/core/session/README.zh.md#write-experimental-plugin-records)。 + +```ts type-equiv +/** + * Event type of an experimental plugin record: `plugin:` followed by + * slash-separated segments of lowercase letters, digits, `.`, `_`, and `-`, + * each starting with a letter or digit. A record type is never a + * {@link SessionEventMap} member. + */ +type PluginRecordType = `plugin:${string}` +``` + +```ts type-equiv +/** + * Payloads written by experimental packages, keyed by their `plugin:` record + * names. Packages augment this map to type writes and enter the current plugin + * record catalog; these declarations do not enter {@link SessionEventMap} or + * released persistence schemas. Stored records still require owner validation. + */ +interface PluginRecordMap {} +``` + +```ts type-equiv +/** + * One committed experimental plugin record, read from the log by + * `pluginRecordOf`. The record's owner validates `data` before use, because a + * restored record carries whatever JSON an earlier build of its owner wrote, + * or a V3 event that a format migration renamed into the `plugin:` namespace held. + */ +interface PluginRecord { + /** The record type, chosen by the owning plugin. */ + readonly type: PluginRecordType + /** The record's position in the Session log. */ + readonly seq: SessionSeq + /** Unix epoch milliseconds at append. */ + readonly time: number + /** The JSON payload as committed. */ + readonly data: unknown +} +``` + ## 持久性约定 持久化后端依赖的约定如下:持久日志无损保存每个事件,每个 Assistant attempt 都是一个 `assistant/message` 或 `assistant/attempt`,其嵌入式紧凑 stream 会保留原始带时间 chunk。`seq` 在这些 settlement 与所有交错事件之间保持连续。后端可以为事件批次选择自己的存储 framing,只要句柄的 `read()` 返回与追加时完全一致的事件即可;当前 JSONL 每个事件写一行(见 [persistence.md](persistence.zh.md))。所有 `event.data` 都必须可序列化为 JSON;`Session.append` 会从源头强制这一要求(遇到不可序列化数据时抛出),因此错误事件绝不会进入日志,`session.snapshotEvents()` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。 @@ -803,7 +846,7 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise @@ -1055,8 +1098,8 @@ announce(session: Session): void * store owns the carrier, so callers (the checkpoint policy's per-request * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner + * and one spelling. * @param session - the session whose buffered events must reach durable storage. * @returns whether at least one durability listener participated, after every * listener has settled successfully. @@ -1220,7 +1263,6 @@ Creation announcement during session publication. A synchronous throw vetoes and * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners * receive only sessions entered through that agent's context. * @param session - the session just entered and announced. - * @dshScopeScan unsupported * @mode emit */ 'session/created'(this: Scoped, session: Session): void @@ -1243,7 +1285,6 @@ Emitted once when an announced session leaves the store, including publication r * did not begin. Listener failures are logged and contained. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported * @mode emit */ 'session/disposed'(this: Scoped, session: Session): void @@ -1268,7 +1309,6 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before * receive only events from sessions entered through that agent's context. * @param session - the session whose log grew. * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported * @mode emit */ 'session/event'(this: Scoped, session: Session, event: SessionEvent): void @@ -1290,7 +1330,6 @@ Awaited parallel durability checkpoint: every listener runs and the caller await * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported * @mode parallel */ 'session/flush'(this: Scoped, session: Session): Promise | void diff --git a/docs/subsystems/shell.i18n.yaml b/docs/subsystems/shell.i18n.yaml index 207792b8e2..c022715938 100644 --- a/docs/subsystems/shell.i18n.yaml +++ b/docs/subsystems/shell.i18n.yaml @@ -15,8 +15,8 @@ en: 471d328907ef0761 zh: d6b0b8e66e427394 /shell-executor/file-sandbox-shellsandboxinfo: - en: d09036a8749423ce - zh: bba45a62001754ae + en: 7f84b75c4128768c + zh: cf389bfa6c8d24f3 /shell-executor/background-processes-shellprocess: en: e5530a4d2b8f3aa3 zh: 72e4fd736c1a6d9d diff --git a/docs/subsystems/shell.md b/docs/subsystems/shell.md index ad7e7f12ac..924e302b24 100644 --- a/docs/subsystems/shell.md +++ b/docs/subsystems/shell.md @@ -166,7 +166,7 @@ interface ShellSandboxInfo { } ``` -The `SANDBOX_UNAVAILABLE` error code (owned by the [sandbox seam](sandbox.md)) is what the `ctx.sandbox` provider throws — and the executor propagates — when a confined mode has no usable backend. A selected runner refusing its profile reaches the same fail-closed foreground error; a settled background job records `runnerFailed`. The model receives denial/runner facts in results, learns the effective mode only when a denial marker names it, and can request a one-shot strictly wider retry through `sandbox_permissions` plus `justification`; `ctx.approval` must grant that exact call before anything executes. The complete policy and switching design is the [sandbox Agent Note](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md). +The `SANDBOX_UNAVAILABLE` error code (owned by the [sandbox seam](sandbox.md)) is what the `ctx.sandbox` provider throws — and the executor propagates — when a confined mode has no usable backend. A selected runner refusing its profile reaches the same fail-closed foreground error; a settled background job records `runnerFailed`. The model receives denial/runner facts in results, learns the effective mode only when a denial marker names it, and can request a one-shot strictly wider retry through `sandbox_permissions` plus `justification`; `ctx.approval` must grant that exact call before anything executes. The complete policy and switching design is the [sandbox reference](../../packages/sandbox/sandbox-policy/README.md). ## Background processes: `ShellProcess` diff --git a/docs/subsystems/shell.zh.md b/docs/subsystems/shell.zh.md index f0e9bd6ea0..7c6d41fc8c 100644 --- a/docs/subsystems/shell.zh.md +++ b/docs/subsystems/shell.zh.md @@ -166,7 +166,7 @@ interface ShellSandboxInfo { } ``` -当受限模式没有可用后端时,`ctx.sandbox` 提供方会抛出、执行器会传播由[沙箱 seam](sandbox.zh.md)所有的 `SANDBOX_UNAVAILABLE` 错误码。选定的 runner 拒绝其 profile 时会触达同一个故障关闭的前台错误;已结束的后台任务则记录 `runnerFailed`。模型会在结果中收到拒绝/runner 事实,仅当拒绝标记指出生效模式时才得知该模式,并可通过 `sandbox_permissions` 加 `justification` 请求一次性、严格更宽松的重试;执行任何操作前,`ctx.approval` 必须批准该次确切调用。完整的策略与切换设计见[沙箱 Agent Note](../../.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md)。 +当受限模式没有可用后端时,`ctx.sandbox` 提供方会抛出、执行器会传播由[沙箱 seam](sandbox.zh.md)所有的 `SANDBOX_UNAVAILABLE` 错误码。选定的 runner 拒绝其 profile 时会触达同一个故障关闭的前台错误;已结束的后台任务则记录 `runnerFailed`。模型会在结果中收到拒绝/runner 事实,仅当拒绝标记指出生效模式时才得知该模式,并可通过 `sandbox_permissions` 加 `justification` 请求一次性、严格更宽松的重试;执行任何操作前,`ctx.approval` 必须批准该次确切调用。完整的策略与切换设计见[沙箱 参考](../../packages/sandbox/sandbox-policy/README.zh.md)。 ## 后台进程:`ShellProcess` diff --git a/docs/subsystems/skills.i18n.yaml b/docs/subsystems/skills.i18n.yaml index d95e69dd63..c4fbc992b6 100644 --- a/docs/subsystems/skills.i18n.yaml +++ b/docs/subsystems/skills.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/skills.md /skills: - en: 047c73a023dcdcd1 - zh: 052f42fbfd202f32 + en: ad77015352a17ed6 + zh: 5c84933161845393 /skills/provider-registry: en: abe338b6c6d1ebd1 zh: c604db2de10a23c4 diff --git a/docs/subsystems/skills.md b/docs/subsystems/skills.md index e330edd2c5..3bfdbff402 100644 --- a/docs/subsystems/skills.md +++ b/docs/subsystems/skills.md @@ -2,9 +2,9 @@ English | [中文](skills.zh.md) -The [skill capability family](../../packages/skill) includes the Service Definition ([dsh-skill](../../packages/skill/skill), `ctx.skills`), the local Service Provider ([dsh-skill-filesystem](../../packages/skill/skill-filesystem)), optional packaged providers ([dsh-skill-badge](../../packages/skill/skill-badge), [dsh-skill-office](../../packages/skill/skill-office), and the Windows ACL diagnosis provider in [dsh-sandbox-windows-acl](../../packages/sandbox/sandbox-windows-acl)), and the Consumer ([dsh-tool-skill](../../packages/skill/tool-skill)). The registry merges provider catalogs across its host and per-scope layers; providers contribute local or packaged skills; the Consumer owns the initial and replacement catalogs plus the model-facing `skill` tool. Skills are optional instructions, not session events, so their vocabulary lives here rather than in [core.md](core.md). +The [skill capability family](../../packages/skill) includes the Service Definition ([dsh-skill](../../packages/skill/skill), `ctx.skills`), the local Service Provider ([dsh-skill-filesystem](../../packages/skill/skill-filesystem)), optional packaged providers ([dsh-skill-badge](../../packages/experimental/skill-badge), [dsh-skill-office](../../packages/skill/skill-office), and the Windows ACL diagnosis provider in [dsh-sandbox-windows-acl](../../packages/sandbox/sandbox-windows-acl)), and the Consumer ([dsh-tool-skill](../../packages/skill/tool-skill)). The registry merges provider catalogs across its host and per-scope layers; providers contribute local or packaged skills; the Consumer owns the initial and replacement catalogs plus the model-facing `skill` tool. Skills are optional instructions, not session events, so their vocabulary lives here rather than in [core.md](core.md). -Source: [`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts), [`packages/skill/skill-filesystem/src/index.ts`](../../packages/skill/skill-filesystem/src/index.ts), [`packages/skill/skill-badge/src/index.ts`](../../packages/skill/skill-badge/src/index.ts), [`packages/skill/skill-office/src/index.ts`](../../packages/skill/skill-office/src/index.ts), [`packages/sandbox/sandbox-windows-acl/src/acl-skill.ts`](../../packages/sandbox/sandbox-windows-acl/src/acl-skill.ts), and [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts). +Source: [`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts), [`packages/skill/skill-filesystem/src/index.ts`](../../packages/skill/skill-filesystem/src/index.ts), [`packages/experimental/skill-badge/src/index.ts`](../../packages/experimental/skill-badge/src/index.ts), [`packages/skill/skill-office/src/index.ts`](../../packages/skill/skill-office/src/index.ts), [`packages/sandbox/sandbox-windows-acl/src/acl-skill.ts`](../../packages/sandbox/sandbox-windows-acl/src/acl-skill.ts), and [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts). ## Provider registry diff --git a/docs/subsystems/skills.zh.md b/docs/subsystems/skills.zh.md index 03a6a06f4e..d93f8416fb 100644 --- a/docs/subsystems/skills.zh.md +++ b/docs/subsystems/skills.zh.md @@ -2,9 +2,9 @@ [English](skills.md) | 中文 -[skill(技能)能力族](../../packages/skill) 包含 Service Definition([dsh-skill](../../packages/skill/skill),`ctx.skills`)、本地 Service Provider([dsh-skill-filesystem](../../packages/skill/skill-filesystem))、可选的随包提供方([dsh-skill-badge](../../packages/skill/skill-badge)、[dsh-skill-office](../../packages/skill/skill-office) 及 [dsh-sandbox-windows-acl](../../packages/sandbox/sandbox-windows-acl) 中的 Windows ACL 诊断提供方)和 Consumer([dsh-tool-skill](../../packages/skill/tool-skill))。注册表在其宿主层与各 scope 层之间合并各提供方的目录;提供方贡献本地或随包 skill;Consumer 拥有初始目录和替换目录,以及面向模型的 `skill` 工具。skill 是可选的指令而非会话事件,因此其词汇定义在此处而非 [core.md](core.zh.md)。 +[skill(技能)能力族](../../packages/skill) 包含 Service Definition([dsh-skill](../../packages/skill/skill),`ctx.skills`)、本地 Service Provider([dsh-skill-filesystem](../../packages/skill/skill-filesystem))、可选的随包提供方([dsh-skill-badge](../../packages/experimental/skill-badge)、[dsh-skill-office](../../packages/skill/skill-office) 及 [dsh-sandbox-windows-acl](../../packages/sandbox/sandbox-windows-acl) 中的 Windows ACL 诊断提供方)和 Consumer([dsh-tool-skill](../../packages/skill/tool-skill))。注册表在其宿主层与各 scope 层之间合并各提供方的目录;提供方贡献本地或随包 skill;Consumer 拥有初始目录和替换目录,以及面向模型的 `skill` 工具。skill 是可选的指令而非会话事件,因此其词汇定义在此处而非 [core.md](core.zh.md)。 -源码:[`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts)、[`packages/skill/skill-filesystem/src/index.ts`](../../packages/skill/skill-filesystem/src/index.ts)、[`packages/skill/skill-badge/src/index.ts`](../../packages/skill/skill-badge/src/index.ts)、[`packages/skill/skill-office/src/index.ts`](../../packages/skill/skill-office/src/index.ts)、[`packages/sandbox/sandbox-windows-acl/src/acl-skill.ts`](../../packages/sandbox/sandbox-windows-acl/src/acl-skill.ts) 与 [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts)。 +源码:[`packages/skill/skill/src/index.ts`](../../packages/skill/skill/src/index.ts)、[`packages/skill/skill-filesystem/src/index.ts`](../../packages/skill/skill-filesystem/src/index.ts)、[`packages/experimental/skill-badge/src/index.ts`](../../packages/experimental/skill-badge/src/index.ts)、[`packages/skill/skill-office/src/index.ts`](../../packages/skill/skill-office/src/index.ts)、[`packages/sandbox/sandbox-windows-acl/src/acl-skill.ts`](../../packages/sandbox/sandbox-windows-acl/src/acl-skill.ts) 与 [`packages/skill/tool-skill/src/index.ts`](../../packages/skill/tool-skill/src/index.ts)。 ## 提供方注册表 diff --git a/docs/subsystems/slots.i18n.yaml b/docs/subsystems/slots.i18n.yaml index aec206af8c..67850a5a7d 100644 --- a/docs/subsystems/slots.i18n.yaml +++ b/docs/subsystems/slots.i18n.yaml @@ -18,8 +18,8 @@ en: 2fcfc55fbac80f72 zh: ea3016f394c56b07 /web-client-slots/developer-provided-injection: - en: 4fb5222bb2b064e7 - zh: b961f792052b4ce5 + en: 7081335b356bf46b + zh: 631e60d29daf7196 /web-client-slots/current-hierarchy: en: 8a4cc4b8608ce0f6 zh: 21281dcf047503b2 diff --git a/docs/subsystems/slots.md b/docs/subsystems/slots.md index 42da307ddd..8a9448a4f8 100644 --- a/docs/subsystems/slots.md +++ b/docs/subsystems/slots.md @@ -106,6 +106,8 @@ A reserved `hooks` object in that return value accepts bare `getSnapshot`/`subsc The owner of a slot may put an `inject` face in the child declaration when every occupant needs the same capability. Plain members reach all occupants unchanged. Function-valued members inside its `hooks` object are hook factories; they receive the slot's standard props and optional per-render `hookContext`, then return the constrained hook exposed to the occupant. `conversation.chat.node` uses this mechanism to provide `useTurnData(key)` for the node currently being rendered. +The session-scoped `conversation.chat.reasoning-body` chain receives unchanged `text` and `running` values for an expanded reasoning body. Its owner renders original compact Markdown when all entries decline; display contributions retain access to the original reasoning. + Use owner props for values already known at one render occurrence, registration `inject` for one entry's callbacks and private observables, slot-level `inject` for a capability controlled by the slot owner, and a declared store for mutable view state shared across entries or preserved across remounts. React nodes compose through child slots, not through injected values. ## Current hierarchy @@ -147,6 +149,7 @@ root │ │ └─ conversation.view │ │ ├─ conversation.chat.node │ │ │ ├─ conversation.chat.assistant-actions +│ │ │ ├─ conversation.chat.reasoning-body │ │ │ ├─ conversation.chat.commandview │ │ │ ├─ conversation.chat.turnTail │ │ │ └─ tool.call.toolview @@ -185,6 +188,7 @@ root │ │ └─ sidebar.right.tab.guide.entry │ ├─ sidebar.right.pane.tab.title │ └─ sidebar.right.tab.menu.item +├─ shell.bottom ├─ shell.leading └─ shell.overlay └─ shell.quota-notice diff --git a/docs/subsystems/slots.zh.md b/docs/subsystems/slots.zh.md index 10e6338d0f..bf8bc0cac7 100644 --- a/docs/subsystems/slots.zh.md +++ b/docs/subsystems/slots.zh.md @@ -106,6 +106,8 @@ Renderer 还会根据声明的 store 创建 `useStore`,并根据声明的 loca 当每个 occupant 都需要同一种能力时,slot owner 可以在 child 声明里放置 `inject` face。普通成员会原样交给所有 occupant;其 `hooks` 对象中的函数成员是 hook factory,它会收到 slot 的标准 props 与可选的逐次渲染 `hookContext`,再返回提供给 occupant 的受限 hook。`conversation.chat.node` 正是通过这种机制,为当前渲染的 node 提供 `useTurnData(key)`。 +Session 作用域的 `conversation.chat.reasoning-body` 链为展开的思考正文提供未修改的 `text` 和 `running`。所有条目都拒绝认领时,owner 渲染原始紧凑 Markdown;显示贡献保留查看原始思考内容的能力。 + 一次渲染时 owner 已知的值走 owner props;单个 entry 的 callback 与私有 observable 走注册项 `inject`;由 slot owner 控制、所有 occupant 共享的能力走 slot 级 `inject`;需要跨 entry 共享或跨重新挂载保留的可变视图状态走声明的 store。React node 通过 child slot 组合,不通过注入值传递。 ## 当前层级 @@ -147,6 +149,7 @@ root │ │ └─ conversation.view │ │ ├─ conversation.chat.node │ │ │ ├─ conversation.chat.assistant-actions +│ │ │ ├─ conversation.chat.reasoning-body │ │ │ ├─ conversation.chat.commandview │ │ │ ├─ conversation.chat.turnTail │ │ │ └─ tool.call.toolview @@ -185,6 +188,7 @@ root │ │ └─ sidebar.right.tab.guide.entry │ ├─ sidebar.right.pane.tab.title │ └─ sidebar.right.tab.menu.item +├─ shell.bottom ├─ shell.leading └─ shell.overlay └─ shell.quota-notice diff --git a/docs/subsystems/storage.md b/docs/subsystems/storage.md index 03b0fa1c67..674221a263 100644 --- a/docs/subsystems/storage.md +++ b/docs/subsystems/storage.md @@ -215,8 +215,7 @@ The mounted domain facility. Opens declared domains over routed backends; one fa async open(spec: S): Promise> /** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed + * Look up an open domain by name, untyped. Diagnostic surface; typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. diff --git a/docs/subsystems/storage.zh.md b/docs/subsystems/storage.zh.md index ded8783fbf..2a4366c84e 100644 --- a/docs/subsystems/storage.zh.md +++ b/docs/subsystems/storage.zh.md @@ -215,8 +215,7 @@ The mounted domain facility. Opens declared domains over routed backends; one fa async open(spec: S): Promise> /** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed + * Look up an open domain by name, untyped. Diagnostic surface; typed * consumers hold the handle returned by {@link open}. * @param name - Domain name. * @returns the open domain runtime, or `undefined` when not open. diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index 5b6c01d39a..104548c425 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -2,30 +2,27 @@ # section, a hash of its English and Chinese blocks outside code blocks and generated regions. # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/subagent.md -/subagent: - en: 7ef8cafc02c317b5 - zh: 1ccf8273c706b436 -/subagent/two-kinds-of-capability-discovered-two-ways: - en: 7be7cbc224948ed1 - zh: 6da4f61394ed900e -/subagent/the-one-shot-start-request: - en: 37b7876d6f186642 - zh: 196a9df4fca5a4fd -/subagent/continuable-children-and-activations: - en: 6cc3f373d6ad49b2 - zh: dd2cf41fbbc5fbe9 -/subagent/durable-enumeration-listchildren-listdescendants-and-their-entries: - en: 5ff018408210d739 - zh: 909d4a1788d65629 -/subagent/the-terminal-result-subagentresult: - en: 6070effb5f84f701 - zh: 2b8a6980ceb0b7b2 -/subagent/a-one-shot-run-subagentrun: - en: 39df94dd1343ffde - zh: 047605d7f5ba91ae -/subagent/the-provider-contract-subagentprovider: - en: cb05cffdb837b7bc - zh: a53133845d673f0a -/subagent/in-process-backends-permission-depth-and-seed: - en: 3290c070c97c284d - zh: ebced35b081ea1c2 +/subagents: + en: 9d48a27a49585a3d + zh: 89da32c8a16c290a +/subagents/capabilities-and-activation-requests: + en: 8c92b131f67997f6 + zh: c2dc606a0859420c +/subagents/local-children-and-activations: + en: d926d4777ec0f11e + zh: 0ae6076e24b66401 +/subagents/messages-and-interruption: + en: 623b06b3c12540bc + zh: f56c6341e1c62768 +/subagents/durable-enumeration: + en: 30c3607a969696ce + zh: e6359cdf22b1e235 +/subagents/results-and-backend-handles: + en: 69ea5bed7d9fb64a + zh: b187a822b813e80a +/subagents/the-provider-contract-subagentprovider: + en: 226ba8094a8cf96c + zh: 719678662905d1eb +/subagents/durable-enumeration-listchildren-listdescendants-and-their-entries: + en: d8ce7969b65ad86e + zh: d39dac6592681bf2 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index de96d75509..8ad39194a1 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -1,30 +1,21 @@ -# Subagent +# Subagents English | [中文](subagent.zh.md) -The subagent seam lets an agent delegate work to a child agent. Like [bash](shell.md), it is **one optional capability**, not part of the agent loop, so its types live here rather than in [core.md](core.md). It differs from the other capability seams because **multiple provider implementations coexist** in one context, registered by name (`ctx.subagents`), while bash allows only one executor. Its registry follows the [LLM adapter registry](llm-streaming.md), not the single-service bash executor. +The unified activation API manages local conversations and external executions. -Service Definition: [dsh-subagent](../../packages/subagent/subagent) (`ctx.subagents` + the vocabulary below). Service Providers are sibling packages (`dsh-subagent-spawn-in-process`, `dsh-subagent-fork-in-process`, `dsh-subagent-acp`, `dsh-subagent-codex`, `dsh-subagent-claude-code`, `dsh-subagent-dsh-sdk`); the model-facing Consumers are [dsh-tool-subagent](../../packages/subagent/tool-subagent) (per-provider delegation) and [dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control) (the optional global `send_message`, `interrupt_agent`, and `list_agents` controls). The same `ctx.subagents` service owns continuable-child orchestration through an internal activation manager, direct-child discovery through the parent catalog, and recursive descendant discovery through parent catalogs. Product-provider rationale lives in [the Codex and Claude Code Agent Note](../../.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md); common-seam rationale lives in [the subagent Agent Note](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [the continuable subagents Agent Note](../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.md), and [the adjacent-Agent messaging Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.md); [the archived list-identity-projection record](../../.agents/notes/archived/architecture/2026-08-06-subagent-list-identity-projection.md) documents the original list-identity decision. +## Capabilities and activation requests -Sources: [`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts), [`packages/subagent/subagent/src/index.ts`](../../packages/subagent/subagent/src/index.ts), and [`packages/subagent/subagent/src/continuation.ts`](../../packages/subagent/subagent/src/continuation.ts) +Every backend uses startActivation. Capability flags validate request options; prepareContinuable selects local Agent creation, while start selects one external execution. The model-facing tool uses parent delivery. Workflow and code mode consumers can await caller results without injecting another parent message. Historical children with unavailable descriptors remain discoverable as `mode: 'unknown'` without granting continuation capabilities; the [package README](../../packages/subagent/subagent/README.md) defines catalog persistence. -The `subagentCatalog` projection exposes `SubagentCatalogEntry[]` in parent event order through Session observations and client snapshots. Each entry contains the child id, creation time, mode, and mode-dependent label; fork-inherited catalog facts are excluded. [The subagent package](../../packages/subagent/subagent/README.md) owns catalog creation and persistence semantics. Historical children with unavailable descriptors have `mode: 'unknown'`; their header identity remains discoverable without granting continuation capabilities. - -## Two kinds of capability, discovered two ways - -A provider advertises its **start-time** features on a static descriptor the service checks BEFORE a one-shot run exists; a request that needs one the provider lacks is rejected loud (`SubagentError('UNSUPPORTED_CAPABILITY')`), never accepted-then-ignored. Those flags describe only the one-shot [`start()`](#the-provider-contract-subagentprovider) path, where the provider composes the child. **Continuable** children are composed by the continuation manager itself, so they are gated by one optional method whose presence IS the capability, with TS narrowing as the discovery mechanism: [`SubagentProvider.prepareContinuable`](#the-provider-contract-subagentprovider). +Optional `request.cwd` selects the child's initial directory; relative paths resolve against the parent's current effective directory. Omitting it captures that directory at admission. Local child Sessions retain the parent's original project, and cold resume retains their logged directory state. ```ts type-equiv /** - * Which START-TIME features a provider supports. Checked by the service before delegating to - * {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks - * is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent - * degradation" rule). These flags describe the ONE-SHOT - * {@link SubagentProvider.start} path, where the provider composes the child; - * continuable children are composed by the continuation manager itself and are - * gated by {@link SubagentProvider.prepareContinuable} instead. Each flag - * corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit` - * to `maxDepth`; the other names match. + * Start-time options supported by a registered backend. The manager checks + * every requested option before preparing a local child or starting an external + * execution. depthLimit controls maxDepth; the remaining flag names match + * SubagentStartRequest fields. */ interface SubagentCapabilities { readonly agentOptions: boolean @@ -35,27 +26,57 @@ interface SubagentCapabilities { } ``` -## The one-shot start request +```ts type-equiv +/** One managed child execution and its result delivery policy. */ +interface SubagentActivationSpec { + /** Registered backend to use. */ + readonly provider: string + /** Short task label retained in cataloged child membership. */ + readonly label: string + /** Optional reserved identity for a local child; external backends allocate their own ids. */ + readonly childId?: SessionId + /** Task, parent, and backend-supported execution options. */ + readonly request: Omit + /** Cancellation before publication; callers own later cancellation through dispose. */ + readonly signal: AbortSignal + /** Parent delivery notifies the model; caller delivery only returns the result. Local children always enter the parent catalog. */ + readonly delivery: 'parent' | 'caller' +} +``` -The tool layer builds this request from the model input and its own config; the service validates it against the named provider before `start`. Required `parent` supplies the session cwd, lineage, and delegation depth. Optional Agent provider, model, reasoning-effort, and token overrides, output schema, depth, tool filter, and persona require matching capability flags. In-process backends merge `agentOptions` over the parent Agent's options, scope filters and personas to child creation, and implement the supported object-rooted schema with a forced capture tool. The DSH SDK backend merges the four Agent route fields over its instance defaults and validates them in the child runtime's initialization; ACP, Codex, and Claude Code reject `agentOptions` before starting their transports. +```ts type-equiv +/** A managed execution; disposal addresses this exact activation, never a later resume. */ +interface SubagentActivation { + /** Stable identity of the child. */ + readonly childId: SessionId + /** Accepted inbox message, when the backend has a local inbox. */ + readonly messageId?: MessageId + /** + * Execution result after teardown settles and notifications are sent; capture failures reject. + * Teardown failures are reported by dispose() without replacing a captured result. + */ + readonly result: Promise + /** Stop and release this activation and its owned descendants; rejects on teardown failure. */ + dispose(): Promise +} +``` ```ts type-equiv /** - * What a caller asks for when starting a ONE-SHOT subagent. The tool layer - * builds this from the model's `{ description, prompt }` plus its own config; - * the service validates {@link SubagentCapabilities} against the named provider - * and resolves the durable descriptor before dispatching to - * {@link SubagentProvider.start}. + * Task and optional capabilities supplied to a backend. startActivation carries + * these fields in request while owning its label, cancellation, and delivery + * policy separately. */ interface SubagentStartRequest { + /** Initial child directory; relative paths resolve against the parent's current directory. Omitted inherits that directory at start. */ + readonly cwd?: string /** Optional short display label persisted with a session-backed child. */ readonly label?: string /** Content delivered as the child's user message. */ readonly prompt: ContentBlock[] /** - * The spawning agent. In-process providers derive workspace, lineage, and - * delegation depth from its durable session state. ACP reads only its cwd, - * and only when no deployment `cwd` override is configured. + * The spawning agent. Its effective directory supplies the default cwd; + * in-process children retain its origin, lineage, and delegation depth. */ readonly parent: Agent /** @@ -106,121 +127,17 @@ interface SubagentStartRequest { } ``` -`signal` is the single cancellation channel before and after readiness. The [subagent composition-controls Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md) owns the persona, live global-tool filter, absolute-depth, and visibility-not-authority rationale. - -The caller-facing request does not carry catalog format details or continuation state. `SubagentRuntime.start()` resolves the detached one-shot descriptor after capability checks, then passes this provider-facing request to the selected transport; a continuable child never reaches `SubagentProvider.start()`: - ```ts type-equiv -/** - * Provider-facing one-shot request after {@link SubagentRuntime.start} resolves - * the durable child descriptor. - */ +/** Provider-facing request with an absolute directory selected before startup. */ interface ResolvedSubagentStartRequest extends SubagentStartRequest { - /** Detached descriptor a session-backed provider persists in the child log. */ - readonly descriptor: SubagentDescriptorData + /** Absolute child directory captured from the parent or explicit request. */ + readonly cwd: string } ``` -## Continuable children and activations +## Local children and activations -A **continuable background subagent** is one durable child Session with at most one process-local **Activation**, the period when a reconstructed child Agent is resident. An Activation is not a request, result, cancellation, or Task: it may execute many FIFO turns and stays resident while descendants it created are still running. The continuation manager owns activation admission, direct-parent authorization, the live ownership graph, cold resume, and child-first disposal; the Agent loop owns all turn ordering and execution. No continuable path creates a Task or an intermediate result-bearing wrapper. - -```text -persisted Session - -> optional live Activation - -> one retained AgentHandle - -> Agent inbox as the only turn FIFO - -> zero or more owned child Activations -``` - -`SubagentRuntime.startContinuable()` reserves the stable child id, snapshots the versioned `subagent/descriptor` payload, asks the named provider for its detached `ContinuableCreateSpec`, creates the child Agent through a private activation-owner scope, establishes any continuable-parent ownership, and submits the initial prompt. It resolves with `{ childId, messageId }` when inbox acceptance yields the message id — without waiting for the turn to start or for the message to enter the Session log. Every failure before that acceptance rejects with neither id, disposing any created handle and rolling back the Activation and parent ownership. - -`SubagentRuntime.sendMessage()` is the sole model-authored message operation. It accepts the exact live sender plus a target id, permits only a direct parent or direct continuable child, derives sender attribution itself, and routes a direct-child target by Activation residency: - -| Target Activation state | `sendMessage` | -|---|---| -| `running` | steer the nearest step in the same Activation | -| `waiting` | wake and steer the same Activation | -| no Activation | cold-resume a new Activation, then steer it | - -`running` means the Agent has an active driver or maintenance task; `waiting` means no Agent activity is active but its Inbox is nonempty or it owns at least one child Activation that has not completed disposal; `settled` means no Agent activity is active, the Inbox is empty, and every owned child is disposed, at which point the manager disposes the [`AgentHandle`](core.md#creation-and-ownership) and removes the Activation. The manager derives these internal conditions from `Agent.whenIdle()`, `Agent.inbox.hasPending`, the owned-child set, and an Activation generation that invalidates stale observations, rather than maintaining a second execution state machine. After the final Session flush, the child-lock decision uses the synchronous task entry of `Agent.runMaintenance()` to claim the idle phase and close admission in the same JavaScript turn. This conservative rule does not distinguish delivery modes: context parked by `Agent.inject()` can keep an idle Activation and its live ancestors resident until a waking delivery claims it, a queue mutation removes it, or manager teardown discards it. - -The Agent inbox is the only queue. Every Agent message uses `Agent.steer()`: an idle target starts a turn, while a running target claims it at the nearest step boundary. The browser `subagent.prompt` Remote separately carries `delivery: 'queue' | 'steer'` through the same internal admission path; Queue opens a later FIFO turn, while Steer retains the Agent loop's best-effort nearest-step behavior and the message's human source. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/inserted`, `agent/inbox/claimed`, and `agent/inbox/discarded` events remain the message-lifecycle observations, and the continuation layer defines no second queue. - -Authority comes from the exact live sender. Parent-to-child delivery requires the target's `SessionHeader.parentSession` to name the sender; child-to-parent delivery requires the sender's resident Activation to name the target. Siblings, ancestors beyond one edge, self-targets, stale Agent objects, and one-shot children are rejected. Each accepted message is framed as `Agent sent a message:` and records `AgentMessageSource`; the source records the sender but grants no authority. - -For `startContinuable()`, `sendMessage()`, and browser prompt delivery, the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child. The public subagent service exposes no caller-selected Agent-message scheduling; browser human Queue and Steer remain internal adapter choices. - -Live queue occurrence mutation remains in the Session domain. `session.updateQueue` admits ordinary Edit, Remove, and QueueDock Steer for a live subagent-owned Agent only when its current projected identity is continuable and its descriptor sequence is in that child's own non-seed suffix. The identity projection folds descriptors last-wins so a child descriptor supersedes descriptors retained from fork lineage; the own-suffix sequence check prevents a seed-only ancestor identity from authorizing mutation. One-shot, missing, unknown, corrupt, or cold children remain rejected, and queue mutation never cold-resumes a child. The target Session id is the human authority for these mutations, including pending `nextStep` steering or injected context. Steer requires a queued `MessageId` and an Agent that reports running when the command begins; cancellation after admission uses the Agent's accepted waking `nextTurn` fallback. Edit rewrites content under the same `MessageId`, and both Edit and Steer complete their Inbox work synchronously, so settlement observes only the final state. `agent/inbox/claimed` and `agent/inbox/discarded` wake the watcher to re-read whether any pending occurrence remains; this lets direct Agent delivery resume parked work and lets removing the last parked occurrence settle an idle child. The [human inbox-control Agent Note](../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md) owns these semantics. - -`SubagentRuntime.interrupt(targetSessionId, authority)` is the one public stop: it authorizes synchronously, issues `Agent.cancel(cause, { keepInbox: true })` on the live target, and returns without awaiting quiescence. The Activation, its unclaimed pending inbox work, and published descendants are untouched; work already claimed into the interrupted turn is not requeued. Once the interrupted driver is idle, a waking send resumes the parked FIFO queue. An absent target — unknown, one-shot, or already settled — and a manager-less composition are accepted no-ops. For a live target, a mismatched parent address or caller outside its live ancestry rejects with `UNAUTHORIZED`; stale ancestor objects and self-targeting ancestor requests reject before target lookup. - -```ts type-equiv -/** - * Authority under which one interrupt request is admitted. `user` carries the - * durable direct-parent address a human client presented; `ancestor` carries - * the exact live Agent object whose recorded lineage must contain the caller. - */ -type SubagentInterruptAuthority = - | { readonly kind: 'user'; readonly parentSessionId: SessionId } - | { readonly kind: 'ancestor'; readonly agent: Agent } -``` - -Every Activation owns its `AgentHandle` and an `ownedChildren: Set`; because one Session has at most one live Activation, the child Session id identifies the live child without another runtime-incarnation reference. Starting a child or submitting parent-originated work registers the child in a continuation-managed parent's set before the child can run, and that parent cannot settle while the set is non-empty. A top-level or other non-continuation Agent has no Activation and stays outside the waiting graph. Child release happens only after the child has no active Agent work, its Inbox is empty, every child of that child is disposed, the best-effort final session flush settles, and the child's `AgentHandle` completes disposal. - -Final settlement awaits `ctx.sessions.flush(session)` but ignores its participation boolean because an arbitrary listener cannot prove that a persistence backend stored the state. Rejection is logged without failing the Activation, and the manager still disposes the handle and releases ownership; the persisted child state may then be missing or stale on a later resume. Manager unload invokes an internal manager-wide drain that closes admission and disposes every live forest; `drainContinuableDescendants(parents)` closes admission only below exact live host-owned Agents and disposes their continuable descendants while unrelated forests remain live. Both await already-admitted materializations in their scope, propagate cancellation top-down, release handles child-first, and await every selected branch despite individual failures. Durable child Sessions survive that process-local teardown. - -```ts type-equiv -/** Durable attribution for one model-authored message between adjacent Agents. */ -interface AgentMessageSource { - readonly kind: 'agent-message' - /** A message another agent addressed to this one (`relay` context form). */ - readonly form: 'relay' - /** Session id of the Agent whose tool call produced the message. */ - readonly senderSessionId: SessionId -} -``` - -```ts type-equiv -/** Options for one model-authored message between adjacent Agents. */ -interface SubagentSendMessageOptions { - /** Caller cancellation, owning the operation only until inbox acceptance. */ - readonly signal: AbortSignal -} -``` - -```ts type-equiv -/** Identities returned once a continuable child accepted its initial prompt. */ -interface ContinuableStart { - /** The durable child session id, stable across activations. */ - readonly childId: SessionId - /** The accepted initial prompt's inbox message id. */ - readonly messageId: MessageId -} -``` - -When a resident Activation settles, the manager delivers one notice to the child's durable direct parent describing how that epoch ended and carrying the nonempty text blocks from its final assistant output, or `It left no closing message.` when none remain. That delivery is unconditional for every child whose id a caller received, happens before the ownership release that would let the parent be judged settled, and reaches a resident parent through the same waking Agent delivery as an Agent message. A parent whose own lineage is already tearing down receives it without a wake, because waking an idle Agent starts a turn rather than queueing work. Its source has a distinct kind so a transcript never presents a runtime account as something the child wrote. - -```ts type-equiv -/** - * Durable attribution for the runtime's own account of a continuable child - * settling. Deliberately a different kind from - * {@link AgentMessageSource}: an Agent message is content the sender chose, - * while this message is the manager stating what became of the child, and a - * transcript that merged them would credit the child with words it never wrote. - */ -interface SubagentSettledMessageSource { - readonly kind: 'subagent-settled' - /** A runtime account shown without expanding the row (`notice` context form). */ - readonly form: 'notice' - /** One-line account of how the child ended. */ - readonly summary: string - /** Session id of the child that settled. */ - readonly senderSessionId: SessionId -} -``` - -The provider participates only in preparing the initial creation spec, where `spawn` and `fork` differ. Its returned spec carries only detached provider-specific creation inputs — the optional parent-history seed — and no Agent, `AgentHandle`, prompt delivery, result, disposal, or resume operation. Cold resume does not dispatch through a provider at all: the manager folds the generic descriptor, calls `ctx.agents.resume()` through the same activation-owner scope, and submits the waiting turn. +A local child has a durable Session and at most one live activation. The manager reserves identity and capacity, prepares the child through its backend, and owns input admission, cold resume, and child-first disposal. The activation result settles after resource release and completion notification. Disposal reports cleanup failure separately without replacing a captured result. Structured capture closes input for that activation; a later cold resume does not inherit the output schema. ```ts type-equiv /** @@ -231,6 +148,8 @@ The provider participates only in preparing the initial creation spec, where `sp * history. */ interface ContinuableCreateRequest { + /** Absolute initial directory captured before provider preparation. */ + readonly cwd: string /** The reserved durable child session id, for provider diagnostics. */ readonly sessionId: SessionId /** The delegating parent agent whose history a seeding provider reads. */ @@ -260,32 +179,66 @@ interface ContinuableCreateSpec { } ``` -The descriptor (`SubagentDescriptorData` in [descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts)) is a mode-discriminated durable identity for every session-backed subagent. Both modes carry the provider name. A `one-shot` descriptor optionally carries a caller-owned display `label`; a `continuable` descriptor requires the delegation `description` as its durable creation label and additionally snapshots resolved child `agentOptions.provider`/`model`/`reasoningEffort` and optional `persona`/`toolFilter` for cold resume. It never snapshots the merge-extensible `AgentOptions` object, so an unrelated extension value cannot break continuation and a later composition input is a deliberate version change. It omits `subagentDepth` (cold resume trusts the persisted header's `delegationDepth` as the monotone floor) and `outputSchema` (one run or Activation's result contract, not durable identity). +## Messages and interruption -A local one-shot provider appends the descriptor inside the child's initial turn before its first request. The continuation manager appends the descriptor after any provider-supplied lineage and before the initial prompt is admitted; `Session.inheritedEventCount` remains the fork-lineage boundary: resume-time descriptor authority reads the child's own suffix, while the identity projection folds `subagent/descriptor` last-wins so the child's own descriptor overrides a fork-seeded ancestor's. The event is log-only: no `surfaceOp`, never in model history, and retained across compaction by the append-only log. Malformed current-version descriptors are corrupt; unsupported versions cannot be classified by this runtime. - -## Durable enumeration: `listChildren()`, `listDescendants()`, and their entries - -The model-facing `list_agents` adapter reports current activity as `running` or `inactive`. These values do not describe task completion or guarantee that `send_message` will succeed. - -`SubagentRuntime.listChildren(parentSessionId, signal?)` reads the parent's `subagentCatalog` view through a live-preferred Session observation and releases that observation on success or failure. It returns direct-child entries in parent event order without reading child logs or enumerating the Session corpus. Query failures propagate; a missing catalog projection fails explicitly. Browser rows derive membership from the shared projection store and add activity from Session status; the control stream pushes complete catalog updates. `listDescendants()` recursively reads those catalogs and derives `hasChildren` from each child catalog. [The parent-catalog Agent Note](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md) owns creation, fork isolation, ordering, and persistence costs. - -`SubagentRuntime.listDescendants(rootSessionId)` recursively calls the same catalog reader in stable pre-order, preserving each parent's event order. One-shot and unknown-mode entries remain traversal nodes; unknown modes produce `unsupported` diagnostics. An unreadable child catalog produces `corrupt` or `unavailable` and stops only that branch. Root read failures, missing services or projections, and cancellation reject the listing. Each reachable catalog is observed once and released before the next read; repeated ids and cycles are skipped. Sessions absent from reachable catalogs are not discovered, including ordinary Session forks and any subagents below those forks. Each row carries its catalog parent and root-relative depth: +sendMessage authorizes the exact live sender and permits only adjacent local agents. Running children receive steering input; absent continuable children resume from persistence. External executions reject follow-ups. Both local and external executions automatically report their final text, structured result, diagnostic, and status in the parent settlement notice. Local children can also send messages through send_message. caller delivery suppresses settlement notices. drainDescendants and drainChildren await owned work teardown for local and external activations. ```ts type-equiv -/** One catalog descendant with its direct parent and edge distance from the requested root. */ -type SubagentDescendantListEntry = SubagentListEntry & { - /** Parent whose catalog contains this child. */ - readonly parentId: SessionId - /** Edge distance from the requested root; direct children are `1`. */ - readonly depth: number +/** Durable attribution for one model-authored message between adjacent Agents. */ +interface AgentMessageSource { + readonly kind: 'agent-message' + /** A message another agent addressed to this one (`relay` context form). */ + readonly form: 'relay' + /** Session id of the Agent whose tool call produced the message. */ + readonly senderSessionId: SessionId } ``` +```ts type-equiv +/** + * Durable attribution for the runtime's own account of a continuable child + * settling. Deliberately a different kind from + * {@link AgentMessageSource}: an Agent message is content the sender chose, + * while this message is the manager stating what became of the child, and a + * transcript that merged them would credit the child with words it never wrote. + */ +interface SubagentSettledMessageSource { + readonly kind: 'subagent-settled' + /** A runtime account shown without expanding the row (`notice` context form). */ + readonly form: 'notice' + /** One-line account of how the child ended. */ + readonly summary: string + /** Session id of the child that settled. */ + readonly senderSessionId: SessionId +} +``` -## The terminal result: `SubagentResult` +```ts type-equiv +/** Options for one model-authored message between adjacent Agents. */ +interface SubagentSendMessageOptions { + /** Caller cancellation, owning the operation only until inbox acceptance. */ + readonly signal: AbortSignal +} +``` -The outcome of a one-shot run, resolved by `SubagentRun.result`. `structured` is present only after a requested `outputSchema` was successfully satisfied; requesting a schema does not guarantee it, and a provider may return `stopReason: 'error'` when the child fails or finishes without a valid capture. A provider may attach a safe, non-assistant `diagnostic` to a non-`completed` result; the provider removes tool inputs, file contents, environment values, credentials, and raw protocol payloads and limits the complete value to 4096 UTF-8 bytes before consumers present it separately from `output`. A non-`completed` `stopReason` means `output` may be partial — the consumer maps it to an `isError` tool result rather than reporting partial output as success. +```ts type-equiv +/** + * Authority under which one interrupt request is admitted. `user` carries the + * durable direct-parent address a human client presented; `ancestor` carries + * the exact live Agent object whose recorded lineage must contain the caller. + */ +type SubagentInterruptAuthority = + | { readonly kind: 'user'; readonly parentSessionId: SessionId } + | { readonly kind: 'ancestor'; readonly agent: Agent } +``` + +## Durable enumeration + +`listChildren` reads the parent-owned catalog of all local children and parent-delivery external children. Caller-delivery external executions leave membership and result collection to their caller; local workflow children remain discoverable after completion. External entries carry `mode: 'external'` and cannot open a child Session or accept follow-ups. Catalog entries record membership at creation without execution status. `list_agents` exposes direct continuable children with `running` or `inactive` activity; see the [control tool](../../packages/subagent/tool-subagent-control/README.md#list_agents). + +## Results and backend handles + +SubagentResult is available to program consumers. The activation registry retains the external backend handle until disposal completes. A cleanup failure does not replace a captured execution result: the activation result and parent notice retain it, while dispose rejects and the live subagent/end event reports error. Backend-owned diagnostics must satisfy the safe-detail requirements below. ```ts type-equiv /** @@ -320,8 +273,6 @@ interface SubagentResult { } ``` -`SubagentStopReason` is a [merge-extensible derived union](core.md#the-map--derived-union-pattern) — a backend may add variants, so consumers branch on the known cases and treat an unknown terminal reason as a failure: - ```ts type-equiv /** * Why a subagent run ended. Merge-extensible (a backend may add variants); @@ -343,33 +294,17 @@ interface SubagentStopReasonMap { } ``` -## A one-shot run: `SubagentRun` - -`SubagentRun` is the consumer-owned handle for a published one-shot child — one disposable foreground delegation with one result, never a durable child handle. Prompt submission, turn work, and infrastructure faults after publication belong to `result`. Consumers await that result and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. A run has no steering and no resume: continuable conversations have no run at all, because the continuation manager holds their `AgentHandle` directly and orders every turn through the child's own inbox. - ```ts type-equiv /** - * ONE-SHOT child handle returned after publication. Prompt submission, turn - * work, and infrastructure faults after that boundary belong to {@link result}. - * Consumers await that result and must always {@link dispose} to cancel - * remaining work and reach quiescence. A run is one disposable foreground - * delegation with one result; continuable conversations have no run — the - * continuation manager holds their `AgentHandle` directly and orders every - * turn through the child's own inbox. + * Backend execution handle owned directly by the activation registry. + * A result may become available before resource release; the manager always + * disposes the handle and awaits cleanup. Backend startup failures clean up + * partial resources before rejecting, while accepted execution failures settle + * through result. */ interface SubagentRun { - /** - * Parent-scoped run id. For a local run, this MUST equal the published child - * session id, whose `parentSession` records `request.parent.session.id`; a - * remote provider mints an id unique in the parent namespace. - */ + /** Provider-minted id, unique across all parents, providers, and local Sessions in this runtime. */ readonly id: SessionId - /** - * The exact published in-process child, or `undefined` for a remote run. - * When present, its id is {@link id}; the provider retains no ownership - * implication beyond the run's ordinary {@link dispose} contract. - */ - readonly localAgent: Agent | undefined /** * Resolves with the child's terminal {@link SubagentResult} when the run * settles. Does NOT reject on a child-level failure — a model/transport @@ -386,11 +321,9 @@ interface SubagentRun { } ``` -A local one-shot run MUST publish an ordinary child agent/session before `start()` fulfills, return that child session id as `SubagentRun.id`, expose the exact child as `localAgent`, record `request.parent.session.id` in the child's `parentSession` header, and append the resolved descriptor inside the child's initial turn before its first request. Runtime ownership may place the child under the parent, provider, or root scope. A remote provider instead returns a parent-scoped lifecycle id and `localAgent: undefined`; without a local child Session, it is absent from durable enumeration. +## The provider contract: SubagentProvider -## The provider contract: `SubagentProvider` - -Each provider is a named child-agent transport, and multiple providers may coexist. The service validates requested start-time capabilities before `start()`, and rejects a continuable start on a provider without `prepareContinuable`. `inheritsParentContext` describes only conversation seeding (`fork`: true; `spawn` and `acp`: false), allowing consumers to generate accurate model-facing wording without implying inherited tools, services, or authority. A provider whose one-shot route has static provider-owned defaults publishes optional immutable `agentRouteDefaults`, allowing a Consumer to merge model/tool overrides against the correct baseline before preflight. +`SubagentManager` owns startup, message admission, and execution lifetimes. Spawn and Fork contribute detached creation inputs for local Agents. Codex, Claude Code, ACP, and DSH SDK contribute one execution handle; they do not gain multiple turns. Each activation is an execution record that directly retains its AgentHandle or SubagentRun. External completion follows the handle’s result promise; inbox and idle admission belong only to local execution. Both paths share admission, capacity, parent ownership, cancellation, and disposal. The [package reference](../../packages/subagent/subagent/README.md) describes composition and deployment requirements. ```ts type-equiv /** @@ -413,31 +346,28 @@ interface SubagentProvider { */ readonly inheritsParentContext: boolean /** - * Optional static provider-owned provider/model route for one-shot Agent + * Optional static provider-owned provider/model route for child Agent * options. Consumers merge tool/model overrides over these values before * preflight; providers whose route derives from the parent omit it. The value * is detached immutable data and requires `agentOptions` support. */ readonly agentRouteDefaults?: Readonly<{ provider: string; model: string }> /** - * Establish a ONE-SHOT child and return its handle after publication. + * Establish one external execution and return its owned handle. * The service has already validated that every requested start-time - * capability is supported and resolved `request.descriptor`, so a - * session-backed implementation appends that descriptor inside the child's - * initial turn. Before fulfillment, the provider owns setup and cleans any - * unpublished partial resources before rejecting. Ownership transfers on + * capability is supported. Before fulfillment, the provider owns setup and + * cleans any unpublished partial resources before rejecting. Ownership transfers on * fulfillment; subsequent turn or infrastructure failure settles through * the returned run. Distinct starts may overlap; cancellation, failure, * result settlement, and disposal remain independent for each run. */ - start(request: ResolvedSubagentStartRequest): Promise + start?(request: ResolvedSubagentStartRequest): Promise /** * OPTIONAL (continuable-creation capability): contribute the detached * creation inputs that distinguish this provider's continuable children — * only whether the child session is seeded with parent history. Method - * presence IS the capability: the service rejects continuable starts on - * providers without it, while a provider that has it may still serve - * ordinary one-shot delegations. + * presence selects local activation execution. Providers without it execute + * through start and do not accept subsequent messages. * * This is the provider's ONLY participation in a continuable child. The * continuation manager owns identity reservation, composition, Agent @@ -450,18 +380,25 @@ interface SubagentProvider { } ``` -Provider `start()` fulfills with a published run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; a `start()` rejection implies cleanup of unpublished resources and emits no lifecycle pair, while a post-publication result rejection closes the emitted pair. Each continuable Activation emits the same observe-only pair for its residency epoch, so a cold resume is a new epoch with its own `runId`. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions. Their `provider` field names the provider that started the run or Activation epoch; it does not claim that the provider remains registered when the edge is emitted. +## Durable enumeration: `listChildren()`, `listDescendants()`, and their entries -## In-process backends: permission, depth, and seed +The model-facing `list_agents` adapter reports current activity as `running` or `inactive`. These values do not describe task completion or guarantee that `send_message` will succeed. -The spawn and fork backends create an ordinary one-shot agent through `parent.ctx`, pass cancellation into core creation, and dispose through `AgentHandle`; a continuable child is instead created by the continuation manager through its own activation-owner scope. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Permission, depth, and fork seeding reuse existing Session vocabulary: +`SubagentRuntime.listChildren(parentSessionId, signal?)` reads the parent's `subagentCatalog` view through a live-preferred Session observation and releases that observation on success or failure. It returns direct-child entries in parent event order without reading child logs or enumerating the Session corpus. Query failures propagate; a missing catalog projection fails explicitly. Browser rows derive membership from the shared projection store and add activity from Session status; the control stream pushes complete catalog updates. `listDescendants()` recursively reads those catalogs and derives `hasChildren` from each child catalog. [The parent-catalog Agent Note](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md) owns creation, fork isolation, ordering, and persistence costs. -- **Delegated permission** is captured before the first await. Auto and Full access parents append their captured `permission/preset` identity to the fresh child after fork seeding and sandbox/approval overrides. One-shot and continuable children share this path; cold resume reads only the child log. Read Only and Workspace Write retain the inherited sandbox override plus `approval: never`, so unmatched bundles remain `custom`. Each Auto child call is reviewed independently using existing `parentSession`, creation prompt, and authenticated human/direct-parent messages. The [Auto review decision](../../.agents/notes/implemented/feature/2026-08-28-auto-review.md) defines low/medium/high semantics; no delegation records, receipt, Header field, descriptor field, or Session format is added. +`SubagentRuntime.listDescendants(rootSessionId)` recursively calls the same catalog reader in stable pre-order, preserving each parent's event order. External entries are leaves without local Sessions. One-shot and unknown-mode entries remain traversal nodes; unknown modes produce `unsupported` diagnostics. An unreadable child catalog produces `corrupt` or `unavailable` and stops only that branch. Root read failures, missing services or projections, and cancellation reject the listing. Each reachable catalog is observed once and released before the next read; repeated ids and cycles are skipped. Sessions absent from reachable catalogs are not discovered, including ordinary Session forks and any subagents below those forks. Each row carries its catalog parent and root-relative depth: + +```ts type-equiv +/** One catalog descendant with its direct parent and edge distance from the requested root. */ +type SubagentDescendantListEntry = SubagentListEntry & { + /** Parent whose catalog contains this child. */ + readonly parentId: SessionId + /** Edge distance from the requested root; direct children are `1`. */ + readonly depth: number +} +``` -- **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap. -- **Fork seeding** uses [`CreateAgentOptions.seed`](core.md#creation-and-ownership) (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/runtime-diagnostics/invariants) replay accepts it (the in-flight, unbalanced turn is excluded). -`SubagentCatalogEntry` describes a direct child with complete or unknown-mode discovery information; `SubagentCatalogState` is the host-only projection state. `listChildren()` owns a live-preferred parent observation without opening child logs. Browser consumers read `subagentCatalog` through the shared Session projection store and combine membership with Session-list activity. `SubagentCatalogRow` belongs to recursive catalog listing. [The parent-catalog decision](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.md) owns the persistent facts and read semantics. @@ -491,7 +428,7 @@ Source: [`packages/subagent/tool-subagent/src/model-selection-settings.ts`](../. ### `ctx.subagents` — `SubagentRuntime` -Named provider registry with one-shot runs, durable discovery, and continuable-child operations. +Named provider registry with managed activations, durable discovery, and local child messaging. ```ts cordis-catalog /** @@ -502,15 +439,26 @@ Named provider registry with one-shot runs, durable discovery, and continuable-c resolveMaxDepth(configured?: number | 'provider-managed'): number | undefined /** - * Establish one durable continuable child and deliver its initial prompt. - * Resolves when the child's inbox accepts that prompt, without waiting for the - * turn to start or for the message to reach the Session log; any earlier - * failure rejects with no ids and rolls back the child entirely. - * @param spec - provider, delegation request, and caller cancellation. - * @returns the durable child id and the accepted prompt's message id. - * @throws when continuation services are unavailable or materialization fails. + * Start a local child under its reserved identity. + * @param spec - local task, reserved child id, and result recipient. + * @returns activation with its accepted initial message id. */ -async startContinuable(spec: ContinuableStartSpec): Promise +startActivation(spec: SubagentActivationSpec & { readonly childId: SessionId }): Promise + +/** + * Start a local or external child. + * @param spec - task, backend, and result recipient. + * @returns activation with a message id only for local children. + */ +startActivation(spec: SubagentActivationSpec): Promise + +/** + * Join progressing descendants without cancelling them. Idle descendants whose + * inboxes require a later wake stay resident and do not delay host completion. + * @param parent - the exact parent whose descendant work is observed. + * @returns whether work was joined; hosts recheck parent idle after true. + */ +async waitForChildren(parent: Agent): Promise /** * Steer one model-authored message to the sender's direct parent or direct @@ -529,13 +477,13 @@ async startContinuable(spec: ContinuableStartSpec): Promise async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions, ): Promise /** - * Interrupt one live continuable child's current turn under a human parent + * Interrupt one live child's current execution under a human parent * address or an exact live ancestor Agent. Fire-and-return: the cancel * signal is issued before this returns, but the target may keep running * until it observes the signal. Unclaimed pending inbox work, the Activation, * and published descendants are preserved; claimed work is not requeued. - * Once the interrupted driver is idle, a waking send resumes the parked FIFO - * queue. An absent target — including a one-shot or unknown id — + * Once the interrupted Agent is idle, a waking send resumes the parked FIFO + * queue. External backends stop their single execution. An absent target * is an accepted no-op, as is a manager-less composition, which cannot own a * live Activation. * @param targetSessionId - the durable child session id to interrupt. @@ -546,28 +494,28 @@ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void /** - * Close continuable admission below exact live parent Agents, stop only their + * Close subagent admission below exact live parent Agents, stop only their * visible descendant Activations synchronously, then await admitted scoped * materializations and release those forests child-first. The scoped cutoff * lasts until each exact parent leaves the registry; unrelated parent trees * remain live. * @param parents - exact host-owned parent Agents entering teardown. - * @returns once every retained descendant Activation released its `AgentHandle`. + * @returns once every retained descendant activation released its execution handle. * @throws an aggregate error after all branches settle when any failed. */ -async drainContinuableDescendants(parents: readonly Agent[]): Promise +async drainDescendants(parents: readonly Agent[]): Promise /** - * Release selected resident continuable direct children of one exact live + * Release selected resident direct children of one exact live * parent. Other children of the same parent remain admitted and resident. * Absent targets and a manager-less composition are accepted no-ops. * @param parent - exact live direct parent authorizing the selected release. * @param childIds - durable direct-child ids to release when resident. - * @returns once every selected Activation released its `AgentHandle`. + * @returns once every selected activation released its execution handle. * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a * different parent or the supplied parent identity is stale. */ -async drainContinuableChildren(parent: Agent, childIds: readonly SessionId[]): Promise +async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise /** * Read the parent's durable direct-child catalog without loading or resuming an Agent. @@ -583,7 +531,8 @@ listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise ``` Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md) @@ -690,7 +626,6 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare * parent carrier as `subagent/start`, so the lifecycle pair reaches the * same scoped audience. * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported * @mode emit */ 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void @@ -748,7 +683,6 @@ A provider established a published child. For in-process providers, `ctx.agents. * parent-scoped listener observes only its own delegations. Paired with * `subagent/end`. * @param info - the provider and published child identity. - * @dshScopeScan unsupported * @mode emit */ 'subagent/start'(this: Scoped, info: SubagentRunInfo): void diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index 843f0e0cd7..220ffe5d42 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -1,30 +1,21 @@ -# Subagent +# 子代理 [English](subagent.md) | 中文 -subagent seam 让一个 agent(智能体)将工作委派给子 agent。与 [bash](shell.zh.md) 一样,它是**一项可选能力**,不属于 agent loop(智能体循环),因此其类型定义在此而非 [core.md](core.zh.md) 中。它不同于其他能力 seam,因为**同一上下文中可共存多个提供方实现**,并按名称注册(`ctx.subagents`),而 bash 只允许一个执行器。该注册表遵循 [LLM(大语言模型)适配器注册表](llm-streaming.zh.md),而非单服务的 bash 执行器。 +统一的 activation API 管理本地会话和外部执行。 -Service Definition:[dsh-subagent](../../packages/subagent/subagent)(`ctx.subagents` + 下文词汇)。Service Provider 是六个兄弟包:`dsh-subagent-spawn-in-process`、`dsh-subagent-fork-in-process`、`dsh-subagent-acp`、`dsh-subagent-codex`、`dsh-subagent-claude-code`、`dsh-subagent-dsh-sdk`;面向模型的 Consumer 包括 [dsh-tool-subagent](../../packages/subagent/tool-subagent)(按提供方委派)和 [dsh-tool-subagent-control](../../packages/subagent/tool-subagent-control)(可选的全局 `send_message`、`interrupt_agent` 与 `list_agents` 控制工具)。同一个 `ctx.subagents` 服务通过内部激活管理器负责可继续子 agent 编排,通过 parent 目录发现直接 child,并通过父目录递归发现后代。产品提供方设计理由见 [Codex 与 Claude Code Agent Note](../../.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md);通用 seam 的设计理由见 [subagent Agent Note](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md)、[可继续 subagent Agent Note](../../.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md)和[相邻 Agent 消息 Agent Note](../../.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md);[已归档的列表身份投影记录](../../.agents/notes/archived/architecture/2026-08-06-subagent-list-identity-projection.md)记录了最初的列表身份决策。 +## 能力与 activation 请求 -源码:[`packages/subagent/subagent/src/types.ts`](../../packages/subagent/subagent/src/types.ts)、[`packages/subagent/subagent/src/index.ts`](../../packages/subagent/subagent/src/index.ts)和 [`packages/subagent/subagent/src/continuation.ts`](../../packages/subagent/subagent/src/continuation.ts) +所有后端都使用 startActivation。能力标记校验请求选项;prepareContinuable 选择本地 Agent 创建,start 选择一次外部执行。面向模型的工具采用 parent 回传。Workflow 与 code mode 消费者可以等待 caller 结果,不额外向父代理注入消息。历史子会话的 descriptor 不可用时,以 `mode: 'unknown'` 保持可发现性,但不授予继续执行能力;[包 README](../../packages/subagent/subagent/README.zh.md) 定义目录持久化语义。 -`subagentCatalog` projection 通过 Session 观察和客户端快照暴露按父会话事件排序的 `SubagentCatalogEntry[]`。每个条目包含子级 id、创建时间、模式和依模式确定的标签;fork 继承的目录事实不在其中。[subagent 包](../../packages/subagent/subagent/README.zh.md) 定义目录创建和持久化语义。历史子会话的 descriptor 不可用时使用 `mode: 'unknown'`,保留 header 身份供发现,但不授予继续执行能力。 - -## 两类能力,两种发现方式 - -提供方通过一个静态描述符公布其**启动时**功能,服务会在单次 run 存在之前即行检查;如果请求依赖提供方不具备的功能,会被明确拒绝(`SubagentError('UNSUPPORTED_CAPABILITY')`),绝不会被接受后静默忽略。这些 flag 仅描述单次 [`start()`](#the-provider-contract-subagentprovider) 路径,即由提供方组合子 agent 的路径。**可继续**子 agent 由继续执行管理器自行组合,因此它们由唯一一个可选方法把关,方法存在即为能力,并以 TypeScript 的类型收窄作为发现机制:[`SubagentProvider.prepareContinuable`](#the-provider-contract-subagentprovider)。 +可选的 `request.cwd` 指定子级的初始目录;相对路径以父级当前有效目录为基准解析。省略时,在准入阶段捕获该目录。本地子 Session 保留父级的原始项目,冷恢复保留其日志中的目录状态。 ```ts type-equiv /** - * Which START-TIME features a provider supports. Checked by the service before delegating to - * {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks - * is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent - * degradation" rule). These flags describe the ONE-SHOT - * {@link SubagentProvider.start} path, where the provider composes the child; - * continuable children are composed by the continuation manager itself and are - * gated by {@link SubagentProvider.prepareContinuable} instead. Each flag - * corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit` - * to `maxDepth`; the other names match. + * Start-time options supported by a registered backend. The manager checks + * every requested option before preparing a local child or starting an external + * execution. depthLimit controls maxDepth; the remaining flag names match + * SubagentStartRequest fields. */ interface SubagentCapabilities { readonly agentOptions: boolean @@ -35,27 +26,57 @@ interface SubagentCapabilities { } ``` -## 单次启动请求 +```ts type-equiv +/** One managed child execution and its result delivery policy. */ +interface SubagentActivationSpec { + /** Registered backend to use. */ + readonly provider: string + /** Short task label retained in cataloged child membership. */ + readonly label: string + /** Optional reserved identity for a local child; external backends allocate their own ids. */ + readonly childId?: SessionId + /** Task, parent, and backend-supported execution options. */ + readonly request: Omit + /** Cancellation before publication; callers own later cancellation through dispose. */ + readonly signal: AbortSignal + /** Parent delivery notifies the model; caller delivery only returns the result. Local children always enter the parent catalog. */ + readonly delivery: 'parent' | 'caller' +} +``` -工具层根据模型输入和自身配置构建此请求;服务在 `start` 之前针对指定提供方进行校验。必填的 `parent` 提供会话 cwd、谱系与委派深度。可选的 Agent 提供方、模型、推理强度与 token 覆盖、output schema、depth、工具过滤器和 persona 需要对应的能力 flag 匹配。进程内后端会把 `agentOptions` 合并到父 Agent 选项之上,将 filter 和 persona 的作用域限定在子 agent 创建阶段,并通过强制 capture 工具实现所支持的 object-rooted schema。DSH SDK 后端会把四个 Agent 路由字段合并到实例默认值之上,并在子运行时初始化期间校验;ACP、Codex 与 Claude Code 会在启动传输前拒绝 `agentOptions`。 +```ts type-equiv +/** A managed execution; disposal addresses this exact activation, never a later resume. */ +interface SubagentActivation { + /** Stable identity of the child. */ + readonly childId: SessionId + /** Accepted inbox message, when the backend has a local inbox. */ + readonly messageId?: MessageId + /** + * Execution result after teardown settles and notifications are sent; capture failures reject. + * Teardown failures are reported by dispose() without replacing a captured result. + */ + readonly result: Promise + /** Stop and release this activation and its owned descendants; rejects on teardown failure. */ + dispose(): Promise +} +``` ```ts type-equiv /** - * What a caller asks for when starting a ONE-SHOT subagent. The tool layer - * builds this from the model's `{ description, prompt }` plus its own config; - * the service validates {@link SubagentCapabilities} against the named provider - * and resolves the durable descriptor before dispatching to - * {@link SubagentProvider.start}. + * Task and optional capabilities supplied to a backend. startActivation carries + * these fields in request while owning its label, cancellation, and delivery + * policy separately. */ interface SubagentStartRequest { + /** Initial child directory; relative paths resolve against the parent's current directory. Omitted inherits that directory at start. */ + readonly cwd?: string /** Optional short display label persisted with a session-backed child. */ readonly label?: string /** Content delivered as the child's user message. */ readonly prompt: ContentBlock[] /** - * The spawning agent. In-process providers derive workspace, lineage, and - * delegation depth from its durable session state. ACP reads only its cwd, - * and only when no deployment `cwd` override is configured. + * The spawning agent. Its effective directory supplies the default cwd; + * in-process children retain its origin, lineage, and delegation depth. */ readonly parent: Agent /** @@ -106,121 +127,17 @@ interface SubagentStartRequest { } ``` -`signal` 是就绪前后唯一的取消通道。[subagent 组合控制 Agent Note](../../.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md)规定 persona、live 全局工具过滤、绝对深度以及「可见性而非权限」的设计理由。 - -面向调用方的请求不携带目录格式细节或继续执行状态。`SubagentRuntime.start()` 会在能力检查后解析分离的一次性描述符,再将以下面向提供方的请求传给所选传输;可继续子 agent 绝不会到达 `SubagentProvider.start()`: - ```ts type-equiv -/** - * Provider-facing one-shot request after {@link SubagentRuntime.start} resolves - * the durable child descriptor. - */ +/** Provider-facing request with an absolute directory selected before startup. */ interface ResolvedSubagentStartRequest extends SubagentStartRequest { - /** Detached descriptor a session-backed provider persists in the child log. */ - readonly descriptor: SubagentDescriptorData + /** Absolute child directory captured from the parent or explicit request. */ + readonly cwd: string } ``` -## 可继续子 agent 与激活 +## 本地子代理与 activation -**可继续后台 subagent** 是一份持久化子 agent 会话(Session),至多关联一个进程内的 **Activation(激活)**,即被重建的子 Agent 处于驻留状态的时段。Activation 不是请求、结果、取消或 Task:它可以执行多个 FIFO 轮次,并在其创建的后代仍在运行期间保持驻留。继续执行管理器负责 activation 准入、直接父级鉴权、实时所有权图、冷恢复(cold resume)与子级优先释放;agent loop 负责一切轮次排序与执行。任何可继续路径都不会创建 Task,也不会创建承载中间结果的包装层。 - -```text -persisted Session - -> optional live Activation - -> one retained AgentHandle - -> Agent inbox as the only turn FIFO - -> zero or more owned child Activations -``` - -`SubagentRuntime.startContinuable()` 会预留稳定的子 agent id,对版本化的 `subagent/descriptor` payload 建立快照,向指定提供方索取其分离的 `ContinuableCreateSpec`,通过私有的 activation-owner 作用域创建子 Agent,建立任何可继续父级的所有权,并提交初始提示词。当收件箱(inbox)准入产出消息 id 时,它以 `{ childId, messageId }` resolve——无需等待轮次开始,也无需等待消息进入会话日志。在该准入之前的任何失败都会以两个 id 都不返回的方式 reject,并 dispose(资源释放)任何已创建的 handle,回滚 Activation 与父级所有权。 - -`SubagentRuntime.sendMessage()` 是唯一由模型编写消息的操作。它接收确切在线 sender 与目标 id,只允许直接 parent 或直接可继续 child,自行推导 sender 来源信息,并根据目标 child 的 Activation 驻留状态路由: - -| 目标 Activation 状态 | `sendMessage` | -|---|---| -| `running` | 在同一 Activation 中 steer 最近的 step | -| `waiting` | 唤醒并 steer 同一 Activation | -| 无 Activation | 冷恢复新的 Activation,然后 steer | - -`running` 表示 Agent 拥有活跃的 driver 或 maintenance 任务;`waiting` 表示没有活跃的 Agent 工作,但其 Inbox 非空或仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示没有活跃的 Agent 工作、Inbox 为空且其拥有的每个子级都已 dispose,此时管理器会 dispose [`AgentHandle`](core.zh.md#creation-and-ownership) 并移除该 Activation。管理器根据 `Agent.whenIdle()`、`Agent.inbox.hasPending`、其拥有的子级集合,以及让过期观察失效的 Activation generation 推导这些内部条件,而非维护第二套执行状态机。最终 Session flush 之后,child-lock 决策会通过 `Agent.runMaintenance()` 的同步 task 入口占用 idle 阶段,并在同一个 JavaScript turn 内关闭准入。这条保守规则不区分投递模式:`Agent.inject()` 停放的 context 可以让空闲 Activation 及其在线祖先继续驻留,直到唤醒投递将其 claim、queue 变更将其移除,或 manager teardown 将其丢弃。 - -Agent 收件箱是唯一队列。每条 Agent 消息都使用 `Agent.steer()`:空闲目标会启动一个轮次,运行中目标则在最近的 step 边界领取消息。浏览器 `subagent.prompt` Remote 会另行通过同一条内部准入路径携带 `delivery: 'queue' | 'steer'`;Queue 开启后续 FIFO 轮次,Steer 保留 Agent loop 的 best-effort 最近 step 行为以及消息的人类来源。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/inserted`、`agent/inbox/claimed` 与 `agent/inbox/discarded` 事件仍是消息生命周期的观测点,继续执行层不定义第二条队列。 - -权限来自确切在线 sender。parent 到 child 的投递要求目标的 `SessionHeader.parentSession` 指向 sender;child 到 parent 的投递要求 sender 的驻留 Activation 指向目标。sibling、相隔多于一条边的 ancestor、self-target、陈旧 Agent 对象与一次性 child 都会被拒绝。每条已接受消息都以 `Agent sent a message:` 作为前缀,并记录 `AgentMessageSource`;来源信息记录 sender,但不授予权限。 - -对于 `startContinuable()`、`sendMessage()` 与浏览器 prompt 投递,调用方 signal 仅在收件箱接受之前掌管查找、物化与准入。此后管理器独立掌管该 Activation:之后的调用方取消既不会取消已接受的轮次,也不会 dispose 子 agent。公开 subagent 服务不暴露由调用方选择的 Agent 消息调度;浏览器人类 Queue 与 Steer 仍是内部适配器选择。 - -在线 queue occurrence 变更属于 Session 域。只有在线 subagent-owned Agent 的当前 projection identity 为 continuable,且其 descriptor 序号位于该 child 自身的非 seed suffix 时,`session.updateQueue` 才会接纳普通 Edit、Remove 与 QueueDock Steer。Identity projection 以 last-wins 方式折叠 descriptor,因此 child descriptor 会覆盖 fork lineage 保留的 descriptor;own-suffix 序号检查会阻止仅来自 seed 的祖先 identity 授权变更。One-shot、缺失、未知、损坏或冷 child 会被拒绝,queue 变更绝不会冷恢复 child。这些变更以目标 Session id 作为人类权限,包括待处理 `nextStep` steering 或注入 context。Steer 要求 queued `MessageId`,且 command 开始时 Agent 必须报告 running;准入后发生取消时,会使用 Agent 已接受的唤醒 `nextTurn` fallback。Edit 会在同一个 `MessageId` 下改写内容,且 Edit 与 Steer 都会同步完成 Inbox 变更,因此 settlement 只会观察最终状态。`agent/inbox/claimed` 与 `agent/inbox/discarded` 都会唤醒 watcher 重新读取是否仍有待处理 occurrence;这样,直接 Agent 投递可以恢复停放工作,而移除最后一个停放 occurrence 可使 idle child 结算。[人类 inbox 控制 Agent Note](../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md)拥有这些语义。 - -`SubagentRuntime.interrupt(targetSessionId, authority)` 是唯一的公开停止操作:它同步完成鉴权,对在线目标发出 `Agent.cancel(cause, { keepInbox: true })`,然后不等待完全停稳即返回。Activation、其尚未领取的待处理 inbox 工作与已发布的后代均不受影响;已被领取进入中断轮次的工作不会重新入队。被中断的 driver 进入 idle 后,一次唤醒发送会恢复被暂停的 FIFO 队列。不存在的目标——未知、一次性或已结算——以及未绑定管理器的组合是被接受的 no-op。对在线目标,错误的 parent 地址或不在其在线祖先链中的调用方会以 `UNAUTHORIZED` 拒绝;陈旧的 ancestor 对象和指向自身的 ancestor 请求会在查找目标前拒绝。 - -```ts type-equiv -/** - * Authority under which one interrupt request is admitted. `user` carries the - * durable direct-parent address a human client presented; `ancestor` carries - * the exact live Agent object whose recorded lineage must contain the caller. - */ -type SubagentInterruptAuthority = - | { readonly kind: 'user'; readonly parentSessionId: SessionId } - | { readonly kind: 'ancestor'; readonly agent: Agent } -``` - -每个 Activation 都拥有自己的 `AgentHandle` 和一个 `ownedChildren: Set`;由于一份会话至多有一个存活 Activation,子会话 id 无需另一个运行时化身引用即可标识存活的子 agent。启动子 agent 或提交源自 parent 的工作,会在子 agent 能够运行之前将其注册到受继续执行管理的父级集合中;只要该集合非空,该父级就无法 settle。顶层或其他非继续执行的 Agent 没有 Activation,处于 waiting 图之外。只有当子 Agent 没有活跃工作、其 Inbox 为空、该子 agent 的每个子级都已 dispose、best-effort 的最终会话 flush 结算完毕,且子 agent 的 `AgentHandle` 完成 dispose 之后,才会释放子 agent。 - -最终结算会等待 `ctx.sessions.flush(session)`,但会忽略其参与布尔值,因为任意 listener 都无法证明某个持久化后端已存储该状态。rejection 会被记录,但不会使 Activation 失败;管理器仍会 dispose 该 handle 并释放所有权,此后持久化的子 agent 状态在后续恢复时可能缺失或陈旧。管理器卸载会调用内部的管理器全局 drain,关闭准入并 dispose 每片在线森林;`drainContinuableDescendants(parents)` 只关闭由 host 确切拥有的在线 Agent 之下的准入,并 dispose 其可继续后代,而无关森林保持在线。两者都会等待各自作用域内已获准的物化过程,自顶向下传播取消,按 child-first 顺序释放 handle,并且即使个别分支失败也会等待所有选中分支。持久化子会话不受该进程内拆卸的影响。 - -```ts type-equiv -/** Durable attribution for one model-authored message between adjacent Agents. */ -interface AgentMessageSource { - readonly kind: 'agent-message' - /** A message another agent addressed to this one (`relay` context form). */ - readonly form: 'relay' - /** Session id of the Agent whose tool call produced the message. */ - readonly senderSessionId: SessionId -} -``` - -```ts type-equiv -/** Options for one model-authored message between adjacent Agents. */ -interface SubagentSendMessageOptions { - /** Caller cancellation, owning the operation only until inbox acceptance. */ - readonly signal: AbortSignal -} -``` - -```ts type-equiv -/** Identities returned once a continuable child accepted its initial prompt. */ -interface ContinuableStart { - /** The durable child session id, stable across activations. */ - readonly childId: SessionId - /** The accepted initial prompt's inbox message id. */ - readonly messageId: MessageId -} -``` - -当驻留 Activation 结算时,管理器会向该 child 持久化的直接 parent 投递一条通知,说明该 epoch 如何结束,并携带其最终 assistant 输出中的非空文本块;若没有剩余的非空文本,则携带 `It left no closing message.`。对每个调用方拿到过 id 的 child,这条投递都是无条件的;它发生在会让 parent 被判定为已结算的所有权释放之前,并通过与 Agent 消息相同的唤醒 Agent 投递到达驻留 parent。若 parent 自身所在的谱系已在拆卸中,这条通知会以不唤醒的方式送达,因为唤醒一个 idle Agent 是开启一个轮次,而不是排队等待工作。其来源信息使用一个独立的 kind,因此 transcript(文本记录)绝不会把运行时的记账呈现为 child 自己写下的内容。 - -```ts type-equiv -/** - * Durable attribution for the runtime's own account of a continuable child - * settling. Deliberately a different kind from - * {@link AgentMessageSource}: an Agent message is content the sender chose, - * while this message is the manager stating what became of the child, and a - * transcript that merged them would credit the child with words it never wrote. - */ -interface SubagentSettledMessageSource { - readonly kind: 'subagent-settled' - /** A runtime account shown without expanding the row (`notice` context form). */ - readonly form: 'notice' - /** One-line account of how the child ended. */ - readonly summary: string - /** Session id of the child that settled. */ - readonly senderSessionId: SessionId -} -``` - -提供方只参与准备初始创建 spec,`spawn` 与 `fork` 在此有所不同。其返回的 spec 只携带分离的、提供方专属的创建输入——即可选的父级历史种子——不含 Agent、`AgentHandle`、提示词投递、结果、dispose 或恢复操作。冷恢复根本不经由提供方分发:管理器折叠通用描述符,通过同一个 activation-owner 作用域调用 `ctx.agents.resume()`,并提交等待中的轮次。 +本地子代理拥有持久 Session,最多有一个在线 activation。Manager 预留身份和容量,通过后端准备子代理,并管理输入准入、冷恢复以及先子后父的释放顺序。activation 结果在资源释放和完成通知之后结算。释放操作单独报告清理失败,不会覆盖已捕获的结果。结构化结果提交会关闭该 activation 的输入;之后的冷恢复不会继承输出 schema。 ```ts type-equiv /** @@ -231,6 +148,8 @@ interface SubagentSettledMessageSource { * history. */ interface ContinuableCreateRequest { + /** Absolute initial directory captured before provider preparation. */ + readonly cwd: string /** The reserved durable child session id, for provider diagnostics. */ readonly sessionId: SessionId /** The delegating parent agent whose history a seeding provider reads. */ @@ -260,34 +179,66 @@ interface ContinuableCreateSpec { } ``` -描述符([descriptor.ts](../../packages/subagent/subagent/src/descriptor.ts) 中的 `SubagentDescriptorData`)是每个由会话支撑的 subagent 所使用、按模式判别的持久化身份。两种模式都携带提供方名称。`one-shot` 描述符可以携带调用方拥有的可选显示 `label`;`continuable` 描述符要求以委派 `description` 作为持久化创建标签,并另外对已解析的子 agent `agentOptions.provider`/`model`/`reasoningEffort` 与可选的 `persona`/`toolFilter` 建立快照,用于冷恢复。它绝不会对可合并扩展的 `AgentOptions` 对象建立快照,因此无关的扩展值不会破坏继续执行,后续新增组合配置输入则是一次有意的版本更改。描述符省略 `subagentDepth`(冷恢复以持久化 header 中的 `delegationDepth` 作为单调下界)和 `outputSchema`(单次运行或 Activation 的结果约定,而非持久化身份)。 +## 消息与中断 -本地一次性提供方会在子 agent 的初始轮次内、首次请求前追加描述符。继续执行管理器会在任何提供方提供的谱系之后、初始提示词获准之前追加描述符;`Session.inheritedEventCount` 仍是 fork 谱系边界:恢复时的描述符权威读取子 agent 自身的后缀,而身份投影以 last-wins 折叠 `subagent/descriptor`,子 agent 自己的描述符会覆盖 fork seed 中祖先的描述符。该事件只进入日志:不含 `surfaceOp`,绝不进入模型历史,并由仅追加日志跨压缩保留。格式错误的当前版本描述符属于损坏;本运行时无法对不受支持的版本进行分类。 - -## 持久化枚举:`listChildren()`、`listDescendants()` 与其条目 - -模型侧的 `list_agents` 适配器将当前活动表示为 `running` 或 `inactive`。这些值不描述任务完成情况,也不保证 `send_message` 会成功。 - -`SubagentRuntime.listChildren(parentSessionId, signal?)` 通过优先使用在线 Session 的观察读取父会话的 `subagentCatalog` 视图,并在成功或失败时释放观察。它按父会话事件顺序返回直接子级条目,不读取子级日志,也不枚举 Session 语料库。查询失败直接传播;缺少目录投影时显式失败。浏览器条目从共享 projection store 派生成员关系,并从 Session 状态补充活动状态;control stream 推送完整目录更新。`listDescendants()` 递归读取这些目录,并从每个子级目录推导 `hasChildren`。[父目录 Agent Note](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md) 说明创建、fork 隔离、排序和持久化成本。 - -`SubagentRuntime.listDescendants(rootSessionId)` 以稳定前序递归调用同一目录读取函数,保留每个父级的事件顺序。一次性和未知模式条目仍是遍历节点;未知模式产生 `unsupported` 诊断。无法读取的子级目录产生 `corrupt` 或 `unavailable`,只停止该分支。根读取失败、服务或投影缺失、取消会使整个查询失败。每个可达目录只观察一次,并在下一次读取前释放;重复 id 和循环引用会被跳过。可达目录中不存在的 Session 不会被发现,包括普通 Session fork 及其下的子 agent。每行携带目录中的父级和相对根的深度: +sendMessage 根据确切的在线发送者实例授权,仅允许相邻本地代理通信。运行中的子代理接收引导输入;不在线的可继续子代理从持久化恢复。外部执行拒绝后续消息。本地与外部执行都会通过父级结束通知自动回传最终文本、结构化结果、诊断和状态。本地子代理也可以通过 send_message 发送消息。caller 回传会抑制结束通知。drainDescendants 与 drainChildren 对本地和外部 activation 都等待所属任务清理完成。 ```ts type-equiv -/** One catalog descendant with its direct parent and edge distance from the requested root. */ -type SubagentDescendantListEntry = SubagentListEntry & { - /** Parent whose catalog contains this child. */ - readonly parentId: SessionId - /** Edge distance from the requested root; direct children are `1`. */ - readonly depth: number +/** Durable attribution for one model-authored message between adjacent Agents. */ +interface AgentMessageSource { + readonly kind: 'agent-message' + /** A message another agent addressed to this one (`relay` context form). */ + readonly form: 'relay' + /** Session id of the Agent whose tool call produced the message. */ + readonly senderSessionId: SessionId } ``` +```ts type-equiv +/** + * Durable attribution for the runtime's own account of a continuable child + * settling. Deliberately a different kind from + * {@link AgentMessageSource}: an Agent message is content the sender chose, + * while this message is the manager stating what became of the child, and a + * transcript that merged them would credit the child with words it never wrote. + */ +interface SubagentSettledMessageSource { + readonly kind: 'subagent-settled' + /** A runtime account shown without expanding the row (`notice` context form). */ + readonly form: 'notice' + /** One-line account of how the child ended. */ + readonly summary: string + /** Session id of the child that settled. */ + readonly senderSessionId: SessionId +} +``` - +```ts type-equiv +/** Options for one model-authored message between adjacent Agents. */ +interface SubagentSendMessageOptions { + /** Caller cancellation, owning the operation only until inbox acceptance. */ + readonly signal: AbortSignal +} +``` -## 终态结果:`SubagentResult` +```ts type-equiv +/** + * Authority under which one interrupt request is admitted. `user` carries the + * durable direct-parent address a human client presented; `ancestor` carries + * the exact live Agent object whose recorded lineage must contain the caller. + */ +type SubagentInterruptAuthority = + | { readonly kind: 'user'; readonly parentSessionId: SessionId } + | { readonly kind: 'ancestor'; readonly agent: Agent } +``` -单次 run 的最终产出,由 `SubagentRun.result` resolve。`structured` 仅在请求了 `outputSchema` 且成功满足时才存在;请求 schema 不保证一定能得到它,当子 agent 失败或结束时未产出有效 capture 时,提供方可能返回 `stopReason: 'error'`。提供方可以为非 `completed` 结果附带安全且不属于 assistant 内容的 `diagnostic`;在消费方将它与 `output` 分开呈现前,提供方会排除工具输入、文件内容、环境值、凭证与原始协议载荷,并把完整值限制在 4096 个 UTF-8 字节以内。非 `completed` 的 `stopReason` 意味着 `output` 可能不完整——消费方将其映射为 `isError` 的工具结果,而非将部分输出报告为成功。 +## 持久枚举 + +`listChildren` 读取父级拥有的目录,其中包含所有本地子级及 parent 投递的外部子级。caller 投递的外部执行由调用方负责成员关系与结果收集;本地工作流子级在完成后仍可被发现。外部条目携带 `mode: 'external'`,不能打开子 Session 或接受后续消息。目录在创建时记录成员关系,不携带执行状态。`list_agents` 展示可继续的直接子级,其活跃状态为 `running` 或 `inactive`;参见[控制工具](../../packages/subagent/tool-subagent-control/README.zh.md#list_agents)。 + +## 结果与后端句柄 + +SubagentResult 供程序消费方读取。activation 注册表保留外部后端句柄,直到释放完成。清理失败不会覆盖已捕获的执行结果:activation 结果与父级通知保留该结果,dispose 拒绝,而在线 subagent/end 事件报告 error。后端提供的诊断必须满足下述安全信息要求。 ```ts type-equiv /** @@ -322,8 +273,6 @@ interface SubagentResult { } ``` -`SubagentStopReason` 是一个[可合并扩展的派生联合类型](core.zh.md#the-map--derived-union-pattern)——后端可以添加变体,因此消费方应对已知 case 分支处理,将未知的终态原因视为失败: - ```ts type-equiv /** * Why a subagent run ended. Merge-extensible (a backend may add variants); @@ -345,33 +294,17 @@ interface SubagentStopReasonMap { } ``` -## 单次 run:`SubagentRun` - -`SubagentRun` 是消费方持有的、指向一个已发布单次子 agent 的句柄——一次可 dispose 的前台委派,只有一个结果,绝不是持久化子 agent handle。发布后的提示词提交、轮次工作与基础设施故障归 `result` 所有。消费方 await 该结果并始终 dispose 该 run,直至完全停稳。子 agent 失败时以非 completed 的 stop reason resolve;只有无法表示的基础设施故障才会 reject。run 没有 steering,也没有恢复:可继续对话根本没有 run,因为继续执行管理器直接持有它们的 `AgentHandle`,并通过子 agent 自己的收件箱为每个轮次排序。 - ```ts type-equiv /** - * ONE-SHOT child handle returned after publication. Prompt submission, turn - * work, and infrastructure faults after that boundary belong to {@link result}. - * Consumers await that result and must always {@link dispose} to cancel - * remaining work and reach quiescence. A run is one disposable foreground - * delegation with one result; continuable conversations have no run — the - * continuation manager holds their `AgentHandle` directly and orders every - * turn through the child's own inbox. + * Backend execution handle owned directly by the activation registry. + * A result may become available before resource release; the manager always + * disposes the handle and awaits cleanup. Backend startup failures clean up + * partial resources before rejecting, while accepted execution failures settle + * through result. */ interface SubagentRun { - /** - * Parent-scoped run id. For a local run, this MUST equal the published child - * session id, whose `parentSession` records `request.parent.session.id`; a - * remote provider mints an id unique in the parent namespace. - */ + /** Provider-minted id, unique across all parents, providers, and local Sessions in this runtime. */ readonly id: SessionId - /** - * The exact published in-process child, or `undefined` for a remote run. - * When present, its id is {@link id}; the provider retains no ownership - * implication beyond the run's ordinary {@link dispose} contract. - */ - readonly localAgent: Agent | undefined /** * Resolves with the child's terminal {@link SubagentResult} when the run * settles. Does NOT reject on a child-level failure — a model/transport @@ -388,13 +321,9 @@ interface SubagentRun { } ``` -本地单次 run 必须在 `start()` fulfill 之前发布一个普通子 agent/会话,将该子会话 id 作为 `SubagentRun.id` 返回,以 `localAgent` 暴露确切的子 agent,在子 agent 的 `parentSession` header 中记录 `request.parent.session.id`,并在子 agent 的初始轮次内、首次请求前追加已解析的描述符。运行时所有权可以把子 agent 放在 parent、提供方或 root 作用域下。远程提供方则返回 parent 作用域的生命周期 id 与 `localAgent: undefined`;由于没有本地 child Session,它不会出现在持久化枚举结果中。 +## 提供方约定:SubagentProvider - - -## 提供方约定:`SubagentProvider` - -每个提供方都是一个具名的子 agent 传输层,多个提供方可以共存。服务在 `start()` 之前校验请求的启动时能力,并拒绝在没有 `prepareContinuable` 的提供方上发起可继续 start。`inheritsParentContext` 仅描述对话种子注入(`fork`:true;`spawn` 和 `acp`:false),使消费方能生成准确的面向模型措辞,而不暗示继承了工具、服务或权限。如果某个提供方的一次性路由拥有静态的提供方自有默认值,它会公开可选且不可变的 `agentRouteDefaults`,使 Consumer 能够在预检前以正确基线合并模型与工具覆盖。 +`SubagentManager` 统一管理启动、消息准入与执行生命周期。Spawn 与 Fork 提供用于本地 Agent 的独立创建输入。Codex、Claude Code、ACP 和 DSH SDK 提供一个执行句柄,不增加多轮能力。每条 activation 都是直接持有 AgentHandle 或 SubagentRun 的执行期记录。外部完成由句柄的 result promise 确定;inbox 与空闲准入仅属于本地执行。两条路径共用准入、容量、父子所有权、取消和释放机制。[包参考](../../packages/subagent/subagent/README.zh.md)说明组合与部署要求。 ```ts type-equiv /** @@ -417,31 +346,28 @@ interface SubagentProvider { */ readonly inheritsParentContext: boolean /** - * Optional static provider-owned provider/model route for one-shot Agent + * Optional static provider-owned provider/model route for child Agent * options. Consumers merge tool/model overrides over these values before * preflight; providers whose route derives from the parent omit it. The value * is detached immutable data and requires `agentOptions` support. */ readonly agentRouteDefaults?: Readonly<{ provider: string; model: string }> /** - * Establish a ONE-SHOT child and return its handle after publication. + * Establish one external execution and return its owned handle. * The service has already validated that every requested start-time - * capability is supported and resolved `request.descriptor`, so a - * session-backed implementation appends that descriptor inside the child's - * initial turn. Before fulfillment, the provider owns setup and cleans any - * unpublished partial resources before rejecting. Ownership transfers on + * capability is supported. Before fulfillment, the provider owns setup and + * cleans any unpublished partial resources before rejecting. Ownership transfers on * fulfillment; subsequent turn or infrastructure failure settles through * the returned run. Distinct starts may overlap; cancellation, failure, * result settlement, and disposal remain independent for each run. */ - start(request: ResolvedSubagentStartRequest): Promise + start?(request: ResolvedSubagentStartRequest): Promise /** * OPTIONAL (continuable-creation capability): contribute the detached * creation inputs that distinguish this provider's continuable children — * only whether the child session is seeded with parent history. Method - * presence IS the capability: the service rejects continuable starts on - * providers without it, while a provider that has it may still serve - * ordinary one-shot delegations. + * presence selects local activation execution. Providers without it execute + * through start and do not accept subsequent messages. * * This is the provider's ONLY participation in a continuable child. The * continuation manager owns identity reservation, composition, Agent @@ -454,18 +380,27 @@ interface SubagentProvider { } ``` -提供方的 `start()` 会以已发布的 run fulfill。服务铸造唯一的 `runId`,从提供方确切的 `localAgent` 快照 `local`,观察结果,emit `subagent/start`,并返回同一个 run;`start()` rejection 意味着未发布资源已清理,且不会 emit 生命周期事件对,而发布后的结果 rejection 会结束已经 emit 的事件对。每个可继续 Activation 都会为其驻留纪元 emit 相同的仅观察事件对,因此一次冷恢复就是一段拥有自己 `runId` 的新纪元。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,且会隔离各自的 listener 异常。其中的 `provider` 字段标明了启动 run 或 Activation 时段的提供方,并不声明该 edge 发出时提供方仍处于注册状态。 +## 持久化枚举:`listChildren()`、`listDescendants()` 与其条目 -## 进程内后端:权限、深度与种子 +模型侧的 `list_agents` 适配器将当前活动表示为 `running` 或 `inactive`。这些值不描述任务完成情况,也不保证 `send_message` 会成功。 -spawn 和 fork 后端通过 `parent.ctx` 创建一个普通的单次 agent,将取消信号传入核心创建流程,并通过 `AgentHandle` 进行 dispose;而可继续子 agent 则由继续执行管理器通过其自己的 activation-owner 作用域创建。移除提供方会阻止新的 start,但不会撤销已接受的 run。每个子 agent 获得一个新的扁平作用域,而非继承父级注册。权限、深度与 fork 种子注入复用既有的 Session 词汇: +`SubagentRuntime.listChildren(parentSessionId, signal?)` 通过优先使用在线 Session 的观察读取父会话的 `subagentCatalog` 视图,并在成功或失败时释放观察。它按父会话事件顺序返回直接子级条目,不读取子级日志,也不枚举 Session 语料库。查询失败直接传播;缺少目录投影时显式失败。浏览器条目从共享 projection store 派生成员关系,并从 Session 状态补充活动状态;control stream 推送完整目录更新。`listDescendants()` 递归读取这些目录,并从每个子级目录推导 `hasChildren`。[父目录 Agent Note](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md) 说明创建、fork 隔离、排序和持久化成本。 -- **委派权限**在首次 await 前捕获。Auto 与 Full access 父级在 fresh child 完成 fork seed 和 sandbox/approval override 后,追加捕获的 `permission/preset` 身份。单次与可继续 child 共用此路径;cold resume 只读取 child 日志。Read Only 与 Workspace Write 保留继承的 sandbox override 加 `approval: never`,不匹配预设的组合仍为 `custom`。每个 Auto child 调用都使用既有 `parentSession`、创建 prompt 和经过核验的 human/直接父级消息独立审查。[Auto review 决策](../../.agents/notes/implemented/feature/2026-08-28-auto-review.zh.md)定义 low/medium/high 语义;不增加 委派记录、receipt、Header 字段、descriptor 字段或 Session format。 +`SubagentRuntime.listDescendants(rootSessionId)` 以稳定前序递归调用同一目录读取函数,保留每个父级的事件顺序。外部条目是没有本地 Session 的叶节点。一次性和未知模式条目仍是遍历节点;未知模式产生 `unsupported` 诊断。无法读取的子级目录产生 `corrupt` 或 `unavailable`,只停止该分支。根读取失败、服务或投影缺失、取消会使整个查询失败。每个可达目录只观察一次,并在下一次读取前释放;重复 id 和循环引用会被跳过。可达目录中不存在的 Session 不会被发现,包括普通 Session fork 及其下的子 agent。每行携带目录中的父级和相对根的深度: -- **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,冷恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。 -- **Fork 种子注入**使用 [`CreateAgentOptions.seed`](core.zh.md#creation-and-ownership)(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/runtime-diagnostics/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。 +```ts type-equiv +/** One catalog descendant with its direct parent and edge distance from the requested root. */ +type SubagentDescendantListEntry = SubagentListEntry & { + /** Parent whose catalog contains this child. */ + readonly parentId: SessionId + /** Edge distance from the requested root; direct children are `1`. */ + readonly depth: number +} +``` + + + -`SubagentCatalogEntry` 描述一条完整或未知模式的直接子级目录记录;`SubagentCatalogState` 是仅 host 使用的 projection state。`listChildren()` 拥有一次 live-preferred 父 Session observation,不打开子级日志。浏览器消费者通过共享 Session projection store 读取 `subagentCatalog`,并将成员关系与 Session 列表活动状态组合。`SubagentCatalogRow` 属于递归目录列表。[父目录决策](../../.agents/notes/implemented/architecture/2026-09-01-parent-owned-subagent-catalog.zh.md) 规定持久事实与读取语义。 @@ -495,7 +430,7 @@ Source: [`packages/subagent/tool-subagent/src/model-selection-settings.ts`](../. ### `ctx.subagents` — `SubagentRuntime` -Named provider registry with one-shot runs, durable discovery, and continuable-child operations. +Named provider registry with managed activations, durable discovery, and local child messaging. ```ts cordis-catalog /** @@ -506,15 +441,26 @@ Named provider registry with one-shot runs, durable discovery, and continuable-c resolveMaxDepth(configured?: number | 'provider-managed'): number | undefined /** - * Establish one durable continuable child and deliver its initial prompt. - * Resolves when the child's inbox accepts that prompt, without waiting for the - * turn to start or for the message to reach the Session log; any earlier - * failure rejects with no ids and rolls back the child entirely. - * @param spec - provider, delegation request, and caller cancellation. - * @returns the durable child id and the accepted prompt's message id. - * @throws when continuation services are unavailable or materialization fails. + * Start a local child under its reserved identity. + * @param spec - local task, reserved child id, and result recipient. + * @returns activation with its accepted initial message id. */ -async startContinuable(spec: ContinuableStartSpec): Promise +startActivation(spec: SubagentActivationSpec & { readonly childId: SessionId }): Promise + +/** + * Start a local or external child. + * @param spec - task, backend, and result recipient. + * @returns activation with a message id only for local children. + */ +startActivation(spec: SubagentActivationSpec): Promise + +/** + * Join progressing descendants without cancelling them. Idle descendants whose + * inboxes require a later wake stay resident and do not delay host completion. + * @param parent - the exact parent whose descendant work is observed. + * @returns whether work was joined; hosts recheck parent idle after true. + */ +async waitForChildren(parent: Agent): Promise /** * Steer one model-authored message to the sender's direct parent or direct @@ -533,13 +479,13 @@ async startContinuable(spec: ContinuableStartSpec): Promise async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], options: SubagentSendMessageOptions, ): Promise /** - * Interrupt one live continuable child's current turn under a human parent + * Interrupt one live child's current execution under a human parent * address or an exact live ancestor Agent. Fire-and-return: the cancel * signal is issued before this returns, but the target may keep running * until it observes the signal. Unclaimed pending inbox work, the Activation, * and published descendants are preserved; claimed work is not requeued. - * Once the interrupted driver is idle, a waking send resumes the parked FIFO - * queue. An absent target — including a one-shot or unknown id — + * Once the interrupted Agent is idle, a waking send resumes the parked FIFO + * queue. External backends stop their single execution. An absent target * is an accepted no-op, as is a manager-less composition, which cannot own a * live Activation. * @param targetSessionId - the durable child session id to interrupt. @@ -550,28 +496,28 @@ async sendMessage( sender: Agent, targetId: SessionId, content: ContentBlock[], interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void /** - * Close continuable admission below exact live parent Agents, stop only their + * Close subagent admission below exact live parent Agents, stop only their * visible descendant Activations synchronously, then await admitted scoped * materializations and release those forests child-first. The scoped cutoff * lasts until each exact parent leaves the registry; unrelated parent trees * remain live. * @param parents - exact host-owned parent Agents entering teardown. - * @returns once every retained descendant Activation released its `AgentHandle`. + * @returns once every retained descendant activation released its execution handle. * @throws an aggregate error after all branches settle when any failed. */ -async drainContinuableDescendants(parents: readonly Agent[]): Promise +async drainDescendants(parents: readonly Agent[]): Promise /** - * Release selected resident continuable direct children of one exact live + * Release selected resident direct children of one exact live * parent. Other children of the same parent remain admitted and resident. * Absent targets and a manager-less composition are accepted no-ops. * @param parent - exact live direct parent authorizing the selected release. * @param childIds - durable direct-child ids to release when resident. - * @returns once every selected Activation released its `AgentHandle`. + * @returns once every selected activation released its execution handle. * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a * different parent or the supplied parent identity is stale. */ -async drainContinuableChildren(parent: Agent, childIds: readonly SessionId[]): Promise +async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise /** * Read the parent's durable direct-child catalog without loading or resuming an Agent. @@ -587,7 +533,8 @@ listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise ``` Types: [Agent](core.zh.md) · [ContentBlock](llm-streaming.zh.md) · [MessageId](llm-streaming.zh.md) · [SessionId](core.zh.md) @@ -694,7 +628,6 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare * parent carrier as `subagent/start`, so the lifecycle pair reaches the * same scoped audience. * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported * @mode emit */ 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void @@ -752,7 +685,6 @@ A provider established a published child. For in-process providers, `ctx.agents. * parent-scoped listener observes only its own delegations. Paired with * `subagent/end`. * @param info - the provider and published child identity. - * @dshScopeScan unsupported * @mode emit */ 'subagent/start'(this: Scoped, info: SubagentRunInfo): void diff --git a/docs/subsystems/system-prompt.i18n.yaml b/docs/subsystems/system-prompt.i18n.yaml index e11fef3eb7..9c893ee1a7 100644 --- a/docs/subsystems/system-prompt.i18n.yaml +++ b/docs/subsystems/system-prompt.i18n.yaml @@ -15,5 +15,5 @@ en: e31de7452a4f1186 zh: db697d4db7f88642 /system-prompt-assembly/dynamic-prompt-context: - en: 043740c2cf02b0aa - zh: 5f0bca1306975c72 + en: 2abb13dec8e6d041 + zh: 8cf434f360d8a519 diff --git a/docs/subsystems/system-prompt.md b/docs/subsystems/system-prompt.md index 898520e8fc..c529c150bb 100644 --- a/docs/subsystems/system-prompt.md +++ b/docs/subsystems/system-prompt.md @@ -82,8 +82,26 @@ interface PromptContext { readonly name: string /** Contexts are joined in ascending order. */ readonly order: number - /** Static text or a provider evaluated for each assembly. Empty text contributes nothing. */ + /** Static text or a provider evaluated at assembly and admission refresh. Empty text contributes nothing. */ readonly text: string | ((context: AssembleContext) => string) + /** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */ + readonly interpolate?: boolean + /** Keep this operational context when optional runtime context is disabled. */ + readonly required?: boolean +} +``` + +`required: true` keeps an operational contribution when optional runtime context is suppressed. `interpolate: false` preserves literal text, including variable-like directory names. `AssembledContext` carries the resolved text and that interpolation choice into rendering. + +```ts type-equiv +/** One resolved dynamic context contribution. */ +interface AssembledContext { + /** The contributing context's unique name. */ + name: string + /** The resolved text before variable interpolation. */ + text: string + /** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */ + interpolate?: boolean } ``` @@ -135,7 +153,7 @@ getContextOrder(name: PromptContextOrderName): number context(context: PromptContext): () => void /** - * Suppress every dynamic runtime-context contribution in the calling + * Suppress optional dynamic runtime-context contributions in the calling * context's scope without changing the services that own or enforce those * facts. Multiple suppressors remain independently disposable. * @returns the exact Cordis effect disposer. @@ -161,6 +179,18 @@ tools(provider: (context: AssembleContext) => ToolProviderResult): () => void */ variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void +/** + * Refresh accepted registered runtime facts for request admission. Contexts + * added only by the assembly waterfall retain their accepted values. Current + * suppression removes optional contexts, and missing required registrations + * are restored in registry order. Sections, tools, and interpolation variables + * retain the accepted assembly; their providers and waterfall do not rerun. + * @param assembly - accepted assembly for this scope and step. + * @param context - the same scope and current plugin-defined assembly fields. + * @returns the accepted assembly with current runtime-context provider text. + */ +refreshContext(assembly: PromptAssembly, context: AssembleContext = {}): PromptAssembly + /** * Assemble global and scoped providers, detach tool parameters, apply * canonical ordering, then run the assembly waterfall. Scoped sections and diff --git a/docs/subsystems/system-prompt.zh.md b/docs/subsystems/system-prompt.zh.md index 82e557a309..a805c1d771 100644 --- a/docs/subsystems/system-prompt.zh.md +++ b/docs/subsystems/system-prompt.zh.md @@ -82,8 +82,26 @@ interface PromptContext { readonly name: string /** Contexts are joined in ascending order. */ readonly order: number - /** Static text or a provider evaluated for each assembly. Empty text contributes nothing. */ + /** Static text or a provider evaluated at assembly and admission refresh. Empty text contributes nothing. */ readonly text: string | ((context: AssembleContext) => string) + /** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */ + readonly interpolate?: boolean + /** Keep this operational context when optional runtime context is disabled. */ + readonly required?: boolean +} +``` + +`required: true` 在可选运行时上下文被抑制时保留操作所需的贡献。`interpolate: false` 原样保留文本,包括看起来像变量的目录名。`AssembledContext` 将已求值文本及其插值选择传给渲染过程。 + +```ts type-equiv +/** One resolved dynamic context contribution. */ +interface AssembledContext { + /** The contributing context's unique name. */ + name: string + /** The resolved text before variable interpolation. */ + text: string + /** Whether to interpolate prompt variables. Defaults to true; false preserves literal text. */ + interpolate?: boolean } ``` @@ -135,7 +153,7 @@ getContextOrder(name: PromptContextOrderName): number context(context: PromptContext): () => void /** - * Suppress every dynamic runtime-context contribution in the calling + * Suppress optional dynamic runtime-context contributions in the calling * context's scope without changing the services that own or enforce those * facts. Multiple suppressors remain independently disposable. * @returns the exact Cordis effect disposer. @@ -161,6 +179,18 @@ tools(provider: (context: AssembleContext) => ToolProviderResult): () => void */ variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void +/** + * Refresh accepted registered runtime facts for request admission. Contexts + * added only by the assembly waterfall retain their accepted values. Current + * suppression removes optional contexts, and missing required registrations + * are restored in registry order. Sections, tools, and interpolation variables + * retain the accepted assembly; their providers and waterfall do not rerun. + * @param assembly - accepted assembly for this scope and step. + * @param context - the same scope and current plugin-defined assembly fields. + * @returns the accepted assembly with current runtime-context provider text. + */ +refreshContext(assembly: PromptAssembly, context: AssembleContext = {}): PromptAssembly + /** * Assemble global and scoped providers, detach tool parameters, apply * canonical ordering, then run the assembly waterfall. Scoped sections and diff --git a/docs/subsystems/todo.i18n.yaml b/docs/subsystems/todo.i18n.yaml index ff23d182e0..eff1b60972 100644 --- a/docs/subsystems/todo.i18n.yaml +++ b/docs/subsystems/todo.i18n.yaml @@ -3,11 +3,11 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/todo.md /todo: - en: 157e8c91aeaf9f64 - zh: c568d90eecee7b12 + en: 92e62f0694b67bea + zh: 1759d71cfa980af9 /todo/todoitem-one-list-entry: en: 3580467f183af02a zh: e3fc7208b0c2ee59 -/todo/durable-event-and-invariant: - en: ae4046c1260671ae - zh: 4246b14e1965487f +/todo/durable-event: + en: 5e00744b8bf1d72f + zh: c85327d075675630 diff --git a/docs/subsystems/todo.md b/docs/subsystems/todo.md index 70eca60ff4..8a5306793b 100644 --- a/docs/subsystems/todo.md +++ b/docs/subsystems/todo.md @@ -2,7 +2,7 @@ English | [中文](todo.zh.md) -The durable todo vocabulary owned by [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.md). The model-facing tool replaces one agent session's whole list; the package also owns the event declaration, replay projection, and invariant companion. Tool behavior and configuration are on the [package README](../../packages/todo/tool-todo/README.md). +The durable todo vocabulary owned by [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.md). The model-facing tool replaces one agent session's whole list; the package also owns the event declaration and replay projection. Tool behavior and configuration are on the [package README](../../packages/todo/tool-todo/README.md). Source: [`packages/todo/tool-todo/src/types.ts`](../../packages/todo/tool-todo/src/types.ts) @@ -27,6 +27,6 @@ interface TodoItem { } ``` -## Durable event and invariant +## Durable event -The package declaration-merges `todo/write: { todos: TodoItem[] }` into `SessionEventMap`. The event is log-only and carries the complete replacement list; the generated [persistence catalog](../persistence-catalog.md#todowrite--log-only) records its declaration site. The package's invariant companion validates existing and newly announced sessions in one pass, then tracks committed turn boundaries incrementally so every live `todo/write` is checked before append without rescanning the log. +The package declaration-merges `todo/write: { todos: TodoItem[] }` into `SessionEventMap`. The event is log-only and carries the complete replacement list; the generated [persistence catalog](../persistence-catalog.md#todowrite--log-only) records its declaration site. diff --git a/docs/subsystems/todo.zh.md b/docs/subsystems/todo.zh.md index 74f76f59b6..09015a16de 100644 --- a/docs/subsystems/todo.zh.md +++ b/docs/subsystems/todo.zh.md @@ -2,7 +2,7 @@ [English](todo.md) | 中文 -本页记录 [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.zh.md) 拥有的持久 todo 词汇。面向模型的工具会整体替换一个 agent(智能体)会话的列表;该包还拥有事件声明、回放投影和不变量配套插件。工具行为与配置见[包 README](../../packages/todo/tool-todo/README.zh.md)。 +本页记录 [`@deepseek-ai/dsh-tool-todo`](../../packages/todo/tool-todo/README.zh.md) 拥有的持久 todo 词汇。面向模型的工具会整体替换一个 agent(智能体)会话的列表;该包还拥有事件声明和回放投影。工具行为与配置见[包 README](../../packages/todo/tool-todo/README.zh.md)。 源码:[`packages/todo/tool-todo/src/types.ts`](../../packages/todo/tool-todo/src/types.ts) @@ -27,6 +27,6 @@ interface TodoItem { } ``` -## 持久事件与不变量 +## 持久事件 -该包通过声明合并把 `todo/write: { todos: TodoItem[] }` 加入 `SessionEventMap`。此事件仅写入日志,并携带完整替换列表;生成的[持久化目录](../persistence-catalog.zh.md#todowrite--log-only)会记录其声明位置。该包的不变量配套插件会单次遍历校验现有会话和新发布的会话,随后增量追踪已提交的轮次边界,使每个实时 `todo/write` 都能在追加前得到校验,而无需重新扫描日志。 +该包通过声明合并把 `todo/write: { todos: TodoItem[] }` 加入 `SessionEventMap`。此事件仅写入日志,并携带完整替换列表;生成的[持久化目录](../persistence-catalog.zh.md#todowrite--log-only)会记录其声明位置。 diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index 83f05cc0f8..32a5981c35 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -15,8 +15,8 @@ en: 8c3fc4ca26f7e314 zh: 2e27263cb25243b9 /tools/execution-extensible-waterfalls-plus-monotonic-policy: - en: 1ff922c9803d5456 - zh: 983d71cbbff7b702 + en: dcac8fa88ef3e719 + zh: aae2f4ce8852d8d8 /tools/the-enforced-raw-json-schema-subset: en: 28e354bfa2f9cebb zh: be1f75abccf8afdf diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index 4d03629399..585ce1582a 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -17,7 +17,7 @@ interface ToolOutputDefinition { readonly schema: JsonSchemaNode /** Pure projection from validated arguments and value to Native/model content. */ render(args: unknown, value: JsonValue): ContentBlock[] - /** Pure replayable presentation projection, computed only for top-level calls. */ + /** Pure replayable presentation projection for native and nested calls. */ presentationMeta?(args: unknown, value: JsonValue): JsonValue } ``` @@ -389,9 +389,9 @@ interface ToolErrorInfo { } ``` -The result carries only the outcome. Call identity remains on the immutable `ToolExecution` that accompanies it through every hook and on the durable `tool/call` / `tool/result` session events, so wrappers cannot create a second, disagreeing identity. The canonical `value` is execution-local: the loop persists only `content`, `error`, and `meta`, while `tool/ptc-dispatch` stores the sub-call's rendered `content`, `isError`, and optional structured `error`. Replay reproduces presentation but cannot reconstruct canonical intermediate values. Optional `ToolErrorInfo.reason` retains raw user-facing detail without adding it to model-facing content. +The result carries only the outcome. Call identity remains on the immutable `ToolExecution` that accompanies it through every hook and on the durable `tool/call` / `tool/result` session events, so wrappers cannot create a second, disagreeing identity. The canonical `value` is execution-local: the loop persists only `content`, `error`, and `meta`, while `tool/ptc-dispatch` stores the sub-call's rendered `content`, `isError`, optional structured `error`, and presentation `meta`. Replay reproduces presentation but cannot reconstruct canonical intermediate values. Optional `ToolErrorInfo.reason` retains raw user-facing detail without adding it to model-facing content. -On success the registry snapshots and validates the body value, freezes it, and invokes the pure renderer plus the optional top-level-call metadata projector. It separately materializes the durable presentation fields immediately before `tools/result`; an invalid value, renderer/projector failure, or non-JSON presentation becomes a JSON-safe `isError`. The final live observer therefore sees the exact execution-local value beside fields safe for the later durable append. +On success the registry snapshots and validates the body value, freezes it, and invokes the pure renderer plus the optional presentation metadata projector for native and nested calls. It separately materializes the durable presentation fields immediately before `tools/result`; an invalid value, renderer/projector failure, or non-JSON presentation becomes a JSON-safe `isError`. The final live observer therefore sees the exact execution-local value beside fields safe for the later durable append. Before final content, the registry materializes the candidate result; a failure in content, structured error, additional context, or presentation metadata becomes a JSON-safe `isError` result that still reaches `finalizeContent`. The registry invokes that callback exactly once, then materializes and freezes the accepted result immediately before `tools/result`, so the observed live outcome is safe for the later durable `tool/result` append. diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index 9aa6bf9656..4ee27fcb4d 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -17,7 +17,7 @@ interface ToolOutputDefinition { readonly schema: JsonSchemaNode /** Pure projection from validated arguments and value to Native/model content. */ render(args: unknown, value: JsonValue): ContentBlock[] - /** Pure replayable presentation projection, computed only for top-level calls. */ + /** Pure replayable presentation projection for native and nested calls. */ presentationMeta?(args: unknown, value: JsonValue): JsonValue } ``` @@ -389,9 +389,9 @@ interface ToolErrorInfo { } ``` -结果仅承载产出。调用身份保留在不可变的 `ToolExecution` 上,后者伴随结果经过每个钩子,并出现在持久化的 `tool/call` / `tool/result` 会话事件上,因此包装层无法创建第二个相互矛盾的身份。规范的 `value` 仅存在于执行期间:循环只持久化 `content`、`error` 和 `meta`,`tool/ptc-dispatch` 则存储子调用渲染后的 `content`、`isError` 与可选结构化 `error`。回放可以重现展示,却无法重建规范的中间值。可选的 `ToolErrorInfo.reason` 保留面向用户的原始详情,不将其加入模型可见内容。 +结果仅承载产出。调用身份保留在不可变的 `ToolExecution` 上,后者伴随结果经过每个钩子,并出现在持久化的 `tool/call` / `tool/result` 会话事件上,因此包装层无法创建第二个相互矛盾的身份。规范的 `value` 仅存在于执行期间:循环只持久化 `content`、`error` 和 `meta`,`tool/ptc-dispatch` 则存储子调用渲染后的 `content`、`isError`、可选结构化 `error` 和展示 `meta`。回放可以重现展示,却无法重建规范的中间值。可选的 `ToolErrorInfo.reason` 保留面向用户的原始详情,不将其加入模型可见内容。 -成功时,注册表会快照并校验函数体返回值,将其冻结,然后调用纯渲染器;对于直接的外层调用,还会调用可选的元数据投影器。注册表会在 `tools/result` 之前另行物化持久展示字段;无效值、渲染器/投影器失败或非 JSON 展示都会转为 JSON 安全的 `isError`。因此,最终实时观察者能看到精确的执行期值,以及可安全用于后续持久追加的字段。 +成功时,注册表会快照并校验函数体返回值,将其冻结,然后调用纯渲染器,并为原生与嵌套调用执行可选的展示元数据投影器。注册表会在 `tools/result` 之前另行物化持久展示字段;无效值、渲染器/投影器失败或非 JSON 展示都会转为 JSON 安全的 `isError`。因此,最终实时观察者能看到精确的执行期值,以及可安全用于后续持久追加的字段。 在得到最终内容之前,注册表会物化候选结果;若内容、结构化错误、附加上下文或展示元数据无法物化,则会转为仍可到达 `finalizeContent` 的 JSON 安全 `isError` 结果。注册表恰好调用该回调一次,随后在 `tools/result` 之前立即物化并冻结已接受的结果,因此实时观察到的产出可安全用于后续持久化的 `tool/result` 追加。 diff --git a/docs/subsystems/translation.i18n.yaml b/docs/subsystems/translation.i18n.yaml new file mode 100644 index 0000000000..a6f25d640c --- /dev/null +++ b/docs/subsystems/translation.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for translation.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/translation.md +/text-translation: + en: fe10b0616f7bc164 + zh: 84a392a62efdaab9 +/text-translation/requests-and-results: + en: 485abf6107c73e68 + zh: 78a274d488274035 +/text-translation/failures-and-data-handling: + en: cf92a1920650888c + zh: 3e8d3aa244cc8d46 +/text-translation/persistent-translations: + en: 40d175fbe66fd4cd + zh: bff18511a8d386e9 +/text-translation/reasoning-display: + en: 209dbbbf3000e087 + zh: f8e661359d0f3cb5 diff --git a/docs/subsystems/translation.md b/docs/subsystems/translation.md new file mode 100644 index 0000000000..c5de82676f --- /dev/null +++ b/docs/subsystems/translation.md @@ -0,0 +1,103 @@ +# Text translation + +English | [中文](translation.zh.md) + +The experimental [translator](../../packages/experimental/translator/README.md) translates text through anonymous browser endpoints or explicitly selected paid native Flash routes. Consumers own display, scheduling, and Session activation; Session-bound requests retain reusable results through an active Session writer. + +## Requests and results + +`TranslationProvider` selects `bing`, `google`, `deepseek-account` or `deepseek-official`. `TranslationRequest` supplies `text`, `targetLanguage`, and optional `sourceLanguage`, `provider` and branded `sessionId`. `resolve()` captures explicit routing in `TranslationSpec`: the configured provider defaults to Bing and omitted source language uses automatic detection. Common Chinese locale tags map to the selected provider's language codes. `translate()` returns plain text from the resolved provider. + +The service exposes its configured `maxTextChars`, measured in UTF-16 code units. Consumers split longer inputs before requesting translation. Each call bounds the response bytes and includes the response-body read in its deadline. Caller cancellation and service disposal abort accepted requests; disposal waits for them to settle. + +## Failures and data handling + +`TranslationError` carries a `TranslationErrorCode` distinguishing text limits, HTTP failures, invalid responses, response limits, owned deadlines, and request failures. `TRANSLATION_SESSION_REQUIRED` rejects a missing persistence backend or Session; `TRANSLATION_SESSION_INACTIVE` rejects an uncached inactive Session before provider dispatch; `TRANSLATION_STORAGE_ERROR` rejects invalid saved records or failed durability. Provider diagnostics omit submitted text and response bodies. Failed calls do not select another provider. Responses are validated as external JSON and redirects are rejected. + +`TRANSLATION_UNAVAILABLE` rejects a missing active built-in owner or registered route, unconfigured credentials, absent exact `deepseek-flash` catalog membership, an owner/model-list/official credential-reference change before dispatch, provider withdrawal, or a prepared call whose reasoning effort is not `off`. A native `finish` with reason `error` maps to `TRANSLATION_REQUEST_FAILED`; a missing or other non-`stop` finish maps to `TRANSLATION_INVALID_RESPONSE`. + +Google receives source text in a form POST body; Bing receives it in a JSON POST body. Both are remote services. Their anonymous endpoints provide no supported developer availability or quota guarantee. Regional connectivity must be verified on the user's network. + +## Persistent translations + +Session-bound calls use `plugin:translator/request` and `plugin:translator/result` for every provider. Requests retain exact source, language tags, selected provider and recipe identity; a successful result references its request sequence. Optional request metadata holds provider-specific details without changing these record types. The translator checkpoints requests before external dispatch and completed results before returning them. Read-only cache lookup precedes provider work and needs no live Session; failures and incomplete attempts do not supply reusable text. New records use the active Session's existing writer. The translator opens no write handle and does not activate a Session. Translation records preserve the original events and model input. Anonymous calls without a Session stay stateless; paid routes require a Session. Experimental record retention across future format migrations is best-effort. + +Paid cache misses require an active built-in Account or Official owner, configured credentials and exact `deepseek-flash` catalog membership; names and version labels are irrelevant, and pi-ai is excluded. Each miss prepares a separate text-only query with thinking off. Its exact config, system and unique user fragment occupy optional `metadata.modelRequest` in the same request record. Saved results are read before this admission, so missing credentials do not invalidate existing translations. Native request extensions receive no main Session identity and cannot upload its full log. + +## Reasoning display + +The [optional bundle](../../packages/experimental/cot-translation-bundle/README.md) composes the translator with the [GUI consumer](../../packages/experimental/client-ui-cot-translation/README.md). Its `cotTranslation` Remote serves authenticated browser calls. When a Session is inactive and has no saved result, the Host joins normal Session controller activation and retries once. Activation retains normal preset hooks, resume markers, and interrupted-turn repair; cancellation stops the caller's wait and prevents a later translation call without cancelling shared activation. Bundle settings select a translation service and target language; `auto` follows the active interface language. + +`CotTranslationPreferences` contains the accepted provider and target language. `CotTranslationSnapshot` pairs these preferences with the translator's current `maxTextChars` and eligible provider IDs. The browser reads this Host snapshot before registering translation controls, including when local settings storage is unavailable. Accepted settings updates and reconnects refresh both values; a changed request limit cancels and splits the expanded text again. + +The session-scoped `conversation.chat.reasoning-body` chain receives the original `text` and `running` flag. Chat renders original Markdown when no contribution claims it. While enabled, the GUI translates completed paragraphs and request-sized prefixes of long streaming paragraphs only after expansion, and restores completed results from the current Session. Its local cache only optimizes that disclosure. The unfinished tail remains original. Original remains accessible. Closing the disclosure, switching provider or language, and unloading cancel outstanding calls and exclude late results. Provider failure preserves the original and requires an explicit retry; translated text never replaces original Session events or model input. + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.cotTranslation` — `CotTranslationController` + +Optional reasoning translation delegates Session-bound storage to the translator. + +```ts cordis-catalog +/** + * Read accepted translation preferences and the current request limit without sending text. + * @param signal - browser query cancellation or Remote contribution withdrawal. + * @returns authoritative preferences, eligible routes, and maximum UTF-16 text length per request. + */ +@Remote async limits(signal: AbortSignal): Promise + +/** + * Translate one displayed fragment through the reader's selected provider. + * @param request - original text and Session identity supplied by the Client; provider and explicit + * target language match accepted preferences, while auto uses the browser locale. + * @param signal - browser cancellation or Remote contribution withdrawal. + * @returns translated text; an uncached inactive Session joins ordinary GUI activation before retry. + * Failures omit the source and provider response. Cancellation stops this caller's wait, not shared activation. + */ +@Remote async translate(request: TranslationRequest, signal: AbortSignal): Promise +``` + +Source: [`packages/experimental/client-ui-cot-translation/src/index.ts`](../../packages/experimental/client-ui-cot-translation/src/index.ts) + + + +### `ctx.translator` — `Translator` + +One Host service with explicit routing, cancellation and quiescent unload. + +```ts cordis-catalog +/** + * Inspect eligible native routes without inference; saved results remain readable for unavailable routes. + * @param signal - optional caller cancellation, combined with service disposal. + * @returns anonymous choices followed by eligible explicitly selected paid routes. + */ +async availableProviders(signal?: AbortSignal): Promise + +/** + * Resolve provider and source-language defaults without sending text. + * @param request - consumer text, destination and optional routing choices. + * @returns a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`. + */ +resolve(request: TranslationRequest): TranslationSpec + +/** + * Translate one resolved specification; the selected provider receives its text. + * @param spec - complete routing and language choices from `resolve()`. + * @param signal - optional caller cancellation, combined with service disposal. + * @returns translated plain text, durably retained before return when a Session is supplied. + * An uncached supplied Session must be active; otherwise rejects with `TRANSLATION_SESSION_INACTIVE` before dispatch. + * Rejects provider/storage/limit failures and preserves cancellation reasons. + */ +async translate(spec: TranslationSpec, signal?: AbortSignal): Promise +``` + +Source: [`packages/experimental/translator/src/index.ts`](../../packages/experimental/translator/src/index.ts) + diff --git a/docs/subsystems/translation.zh.md b/docs/subsystems/translation.zh.md new file mode 100644 index 0000000000..e3700edcbd --- /dev/null +++ b/docs/subsystems/translation.zh.md @@ -0,0 +1,103 @@ +# 文本翻译 + +[English](translation.md) | 中文 + +实验性 [translator](../../packages/experimental/translator/README.zh.md) 通过免登录浏览器端点或显式选择的付费原生 Flash 路由翻译文本。消费者负责展示、调度及 Session 激活;绑定 Session 的请求通过已激活 Session 的写入器保留可复用结果。 + +## 请求与结果 + +`TranslationProvider` 选择 `bing`、`google`、`deepseek-account` 或 `deepseek-official`。`TranslationRequest` 提供 `text`、`targetLanguage` 及可选的 `sourceLanguage`、`provider` 与带品牌类型的 `sessionId`。`resolve()` 将显式路由写入 `TranslationSpec`:配置的默认 Provider 为 Bing,省略源语言时自动检测。常见中文 locale 标签映射为所选 Provider 的语言代码。`translate()` 返回所选 Provider 翻译后的纯文本。 + +服务公开配置的 `maxTextChars`,单位为 UTF-16 code unit。消费者先拆分超长输入,再请求翻译。每次调用限制响应字节数,并将响应体读取计入超时。调用者取消和服务卸载都会中止已接收的请求;卸载等待这些请求完成。 + +## 失败与数据处理 + +`TranslationError` 的 `TranslationErrorCode` 区分文本超限、HTTP 失败、无效响应、响应超限、服务自身超时与请求失败。`TRANSLATION_SESSION_REQUIRED` 拒绝缺失的持久化后端或 Session,`TRANSLATION_SESSION_INACTIVE` 在调用 Provider 前拒绝未激活且缓存未命中的 Session,`TRANSLATION_STORAGE_ERROR` 拒绝无效保存记录或持久化失败。Provider 错误信息省略提交的文本与响应体。失败时不选择其他 Provider。响应作为外部 JSON 校验,重定向被拒绝。 + +`TRANSLATION_UNAVAILABLE` 拒绝缺失的活跃内置所有者或已注册路由、未配置的凭据、目录中不存在精确的 `deepseek-flash`、派发前所有者/模型列表/官方凭据引用变化、提供方卸载,以及准备后的调用推理强度不是 `off` 的情况。原生 `finish` 的原因为 `error` 时映射为 `TRANSLATION_REQUEST_FAILED`;缺失 finish 或其他非 `stop` 原因映射为 `TRANSLATION_INVALID_RESPONSE`。 + +Google 在 form POST 请求体中接收原文,Bing 在 JSON POST 请求体中接收原文。两者都是远端服务。免登录端点不保证开发者 API 的可用性或免费额度。区域连通性须在用户网络上验证。 + +## 持久化翻译 + +绑定 Session 的调用对所有 Provider 使用 `plugin:translator/request` 与 `plugin:translator/result`。请求保留精确原文、语言标签、所选 Provider 和翻译规则标识,成功结果引用请求序号。可选请求 metadata 保存 Provider 专有细节,不改变记录类型。translator 在外部派发前完成请求检查点,在返回前完成结果检查点。只读缓存查找先于 Provider 调用,且不要求 Session 已激活;失败或未完成尝试不提供可复用译文。新记录使用已激活 Session 的现有写入器。translator 不打开写句柄,也不激活 Session。翻译记录保留原始事件与模型输入。不指定 Session 的匿名调用保持无状态;付费路由要求 Session。未来格式迁移对实验性记录的保留为尽力而为。 + +付费缓存未命中时要求内置账号或官方 API 所有者处于活动状态、凭据已配置,且目录包含 ID 恰好为 `deepseek-flash` 的模型;名称和版本标签不参与匹配,pi-ai 路由被排除。每次未命中准备一次关闭思考的独立纯文本查询。精确配置、系统指令和唯一原文片段保存在同一请求记录的可选 `metadata.modelRequest` 中。读取保存结果先于此项准入,因此凭据缺失不会使既有译文失效。原生请求扩展不接收主 Session 身份,不能上传其完整日志。 + +## 思考内容展示 + +[可选 Bundle](../../packages/experimental/cot-translation-bundle/README.zh.md) 将 translator 与 [GUI 消费者](../../packages/experimental/client-ui-cot-translation/README.zh.md) 组合。其 `cotTranslation` Remote 为经过身份验证的浏览器调用提供服务。Session 未激活且没有已保存结果时,Host 等待 Session controller 的正常激活,再重试一次。激活保留正常的 preset 钩子、恢复标记和中断轮次修复;取消会结束调用者的等待并阻止随后调用翻译,不取消共享激活。Bundle 设置选择翻译服务及目标语言;`auto` 跟随当前界面语言。 + +`CotTranslationPreferences` 包含已接受的翻译服务和目标语言。`CotTranslationSnapshot` 将这些偏好与 translator 当前的 `maxTextChars` 及可用服务 ID 一起返回。浏览器在注册翻译控件前读取此 Host 快照,本地设置存储不可用时也如此。接受的设置更新及重新连接会刷新这两项值;请求长度限制变化时,展开内容的请求被取消并重新分片。 + +Session 作用域的 `conversation.chat.reasoning-body` chain 接收原始 `text` 和 `running` 标志。没有 contribution 接管时,Chat 渲染原始 Markdown。启用后,GUI 只在展开后翻译完整段落和流式长段落中达到请求长度的前缀,并从当前 Session 恢复已完成结果;本地缓存只优化当前展开区域。未完成的末尾片段保留原文。原文始终可访问。关闭展开内容、切换 Provider 或语言、卸载插件都会取消未完成调用并忽略迟到的结果。Provider 失败时保留原文,必须显式重试;译文从不替换原始 Session 事件或模型输入。 + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.cotTranslation` — `CotTranslationController` + +Optional reasoning translation delegates Session-bound storage to the translator. + +```ts cordis-catalog +/** + * Read accepted translation preferences and the current request limit without sending text. + * @param signal - browser query cancellation or Remote contribution withdrawal. + * @returns authoritative preferences, eligible routes, and maximum UTF-16 text length per request. + */ +@Remote async limits(signal: AbortSignal): Promise + +/** + * Translate one displayed fragment through the reader's selected provider. + * @param request - original text and Session identity supplied by the Client; provider and explicit + * target language match accepted preferences, while auto uses the browser locale. + * @param signal - browser cancellation or Remote contribution withdrawal. + * @returns translated text; an uncached inactive Session joins ordinary GUI activation before retry. + * Failures omit the source and provider response. Cancellation stops this caller's wait, not shared activation. + */ +@Remote async translate(request: TranslationRequest, signal: AbortSignal): Promise +``` + +Source: [`packages/experimental/client-ui-cot-translation/src/index.ts`](../../packages/experimental/client-ui-cot-translation/src/index.ts) + + + +### `ctx.translator` — `Translator` + +One Host service with explicit routing, cancellation and quiescent unload. + +```ts cordis-catalog +/** + * Inspect eligible native routes without inference; saved results remain readable for unavailable routes. + * @param signal - optional caller cancellation, combined with service disposal. + * @returns anonymous choices followed by eligible explicitly selected paid routes. + */ +async availableProviders(signal?: AbortSignal): Promise + +/** + * Resolve provider and source-language defaults without sending text. + * @param request - consumer text, destination and optional routing choices. + * @returns a complete specification; exceeding `maxTextChars` throws `TRANSLATION_TEXT_LIMIT`. + */ +resolve(request: TranslationRequest): TranslationSpec + +/** + * Translate one resolved specification; the selected provider receives its text. + * @param spec - complete routing and language choices from `resolve()`. + * @param signal - optional caller cancellation, combined with service disposal. + * @returns translated plain text, durably retained before return when a Session is supplied. + * An uncached supplied Session must be active; otherwise rejects with `TRANSLATION_SESSION_INACTIVE` before dispatch. + * Rejects provider/storage/limit failures and preserves cancellation reasons. + */ +async translate(spec: TranslationSpec, signal?: AbortSignal): Promise +``` + +Source: [`packages/experimental/translator/src/index.ts`](../../packages/experimental/translator/src/index.ts) + diff --git a/docs/subsystems/typert.md b/docs/subsystems/typert.md index 2444ee2fc4..f0ef75f223 100644 --- a/docs/subsystems/typert.md +++ b/docs/subsystems/typert.md @@ -442,8 +442,6 @@ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema ``` -Types: [TypertContribution](invariants.md) · [TypertFace](invariants.md) · [TypertPackageFilter](invariants.md) · [TypertPackageRecord](invariants.md) · [TypertSchemaFilter](invariants.md) · [TypertSchemaRecord](invariants.md) - Source: [`packages/typert/registry/src/service.ts`](../../packages/typert/registry/src/service.ts) diff --git a/docs/subsystems/typert.zh.md b/docs/subsystems/typert.zh.md index 60932a3b77..e2414dfc5e 100644 --- a/docs/subsystems/typert.zh.md +++ b/docs/subsystems/typert.zh.md @@ -442,8 +442,6 @@ listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema ``` -Types: [TypertContribution](invariants.zh.md) · [TypertFace](invariants.zh.md) · [TypertPackageFilter](invariants.zh.md) · [TypertPackageRecord](invariants.zh.md) · [TypertSchemaFilter](invariants.zh.md) · [TypertSchemaRecord](invariants.zh.md) - Source: [`packages/typert/registry/src/service.ts`](../../packages/typert/registry/src/service.ts) diff --git a/docs/subsystems/web-server.i18n.yaml b/docs/subsystems/web-server.i18n.yaml index b873980cb0..c2e4d0bec6 100644 --- a/docs/subsystems/web-server.i18n.yaml +++ b/docs/subsystems/web-server.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/web-server.md /http-server: - en: 8ddf00047d960062 - zh: 79fa692e5c59e1c9 + en: 778b8499fa5ec417 + zh: f2073db3d2ed8b70 /http-server/routes: en: 940bc78642c990b0 zh: 901d2ba7e354a766 diff --git a/docs/subsystems/web-server.md b/docs/subsystems/web-server.md index 7b4d3a17a6..2826bbe9d2 100644 --- a/docs/subsystems/web-server.md +++ b/docs/subsystems/web-server.md @@ -2,7 +2,7 @@ English | [中文](web-server.zh.md) -[dsh-host-webserver](../../packages/host/webserver) is the browser HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.webServer`, a named-route registry, optional gzip response compression, index.html transform callbacks, and one fallback handler that a plugin may claim. It is not part of the agent loop and not a capability seam; it knows no harness concepts, and another plugin registers every feature route, including the `/api` bridge, plugin bundles, and the HMR event stream ([layering note](../../.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.md)). It serves browsers only: Electron loads the built files over `file://` and sends fetch requests through an IPC bridge instead of this server. +[dsh-host-webserver](../../packages/host/webserver) is the browser HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.webServer`, a named-route registry, optional gzip response compression, index.html transform callbacks, and one fallback handler that a plugin may claim. It is not part of the agent loop and not a capability seam; it knows no harness concepts, and another plugin registers every feature route, including the `/api` bridge, plugin bundles, and the HMR event stream ([layering reference](../../packages/boot/app-boot/README.md)). It serves browsers only: Electron loads the built files over `file://` and sends fetch requests through an IPC bridge instead of this server. Source: [`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) diff --git a/docs/subsystems/web-server.zh.md b/docs/subsystems/web-server.zh.md index 5a95bbaff4..72ef366958 100644 --- a/docs/subsystems/web-server.zh.md +++ b/docs/subsystems/web-server.zh.md @@ -2,7 +2,7 @@ [English](web-server.md) | 中文 -[dsh-host-webserver](../../packages/host/webserver) 是 GUI Host 的浏览器 HTTP 载体:它是一个提供 `ctx.webServer` 的 `node:http` 插件,包含具名路由注册表、可选的 gzip 响应压缩、index.html 转换回调,以及一个可由插件认领的回退处理器。它不属于 agent loop(智能体循环),也不是能力 seam;它不了解任何 harness 概念。其他插件负责注册所有功能路由,包括 `/api` 桥接、插件 bundle 和 HMR(热模块替换)事件流([分层说明](../../.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md))。该服务器只服务浏览器:Electron 通过 `file://` 加载已构建文件,并经 IPC 桥接发送 fetch 请求,不使用本服务器。 +[dsh-host-webserver](../../packages/host/webserver) 是 GUI Host 的浏览器 HTTP 载体:它是一个提供 `ctx.webServer` 的 `node:http` 插件,包含具名路由注册表、可选的 gzip 响应压缩、index.html 转换回调,以及一个可由插件认领的回退处理器。它不属于 agent loop(智能体循环),也不是能力 seam;它不了解任何 harness 概念。其他插件负责注册所有功能路由,包括 `/api` 桥接、插件 bundle 和 HMR(热模块替换)事件流([分层说明](../../packages/boot/app-boot/README.zh.md))。该服务器只服务浏览器:Electron 通过 `file://` 加载已构建文件,并经 IPC 桥接发送 fetch 请求,不使用本服务器。 源码:[`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) diff --git a/docs/subsystems/webhook.i18n.yaml b/docs/subsystems/webhook.i18n.yaml index 3232039c4d..bb5cff569f 100644 --- a/docs/subsystems/webhook.i18n.yaml +++ b/docs/subsystems/webhook.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/webhook.md /webhook-runtime: - en: a9237452c010af40 - zh: ce76dc8401ca88ee + en: c636a9ba10162b52 + zh: 9c33989015c9fbbc /webhook-runtime/shared-values: en: 99af66fb4c51ecf6 zh: fa1f2e88c7b13e46 diff --git a/docs/subsystems/webhook.md b/docs/subsystems/webhook.md index 5ca2b722e1..8bc71ab121 100644 --- a/docs/subsystems/webhook.md +++ b/docs/subsystems/webhook.md @@ -2,7 +2,7 @@ English | [中文](webhook.zh.md) -The Webhook subsystem turns authenticated external deliveries into optional ordinary root Sessions. Provider adapters own authentication and generic JSON intake; trusted programmatic rules own conditions and external calls; `ctx.webhookRuntime` owns callback lifetime plus Workspace-backed Session creation. The [implemented decision](../../.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.md) records why the runtime keeps no delivery or completion state. +The Webhook subsystem turns authenticated external deliveries into optional ordinary root Sessions. Provider adapters own authentication and generic JSON intake; trusted programmatic rules own conditions and external calls; `ctx.webhookRuntime` owns callback lifetime plus Workspace-backed Session creation. The [historical implemented decision](../../.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md) records why the runtime keeps no delivery or completion state. ## Shared values @@ -66,5 +66,5 @@ register(rule: WebhookRule): () => Promise dispatch(delivery: VerifiedWebhookDelivery): void ``` -Source: [`packages/webhook/webhook/src/index.ts`](../../packages/webhook/webhook/src/index.ts) +Source: [`packages/experimental/webhook/src/index.ts`](../../packages/experimental/webhook/src/index.ts) diff --git a/docs/subsystems/webhook.zh.md b/docs/subsystems/webhook.zh.md index 2eadf03aa5..4d0e108f72 100644 --- a/docs/subsystems/webhook.zh.md +++ b/docs/subsystems/webhook.zh.md @@ -2,7 +2,7 @@ [English](webhook.md) | 中文 -Webhook 子系统会把已通过身份验证的外部交付转换为可选的普通根 Session。提供方适配器拥有身份验证与通用 JSON 接收;受信任的程序化规则拥有条件与外部调用;`ctx.webhookRuntime` 拥有回调生命周期以及基于 Workspace 的 Session 创建。[已实现决策](../../.agents/notes/implemented/feature/2026-08-22-fire-and-forget-webhook-sessions.zh.md)记录了 runtime 为何不保留交付或完成状态。 +Webhook 子系统会把已通过身份验证的外部交付转换为可选的普通根 Session。提供方适配器拥有身份验证与通用 JSON 接收;受信任的程序化规则拥有条件与外部调用;`ctx.webhookRuntime` 拥有回调生命周期以及基于 Workspace 的 Session 创建。[历史已实现决策](../../.agents/notes/archived/feature/2026-08-22-fire-and-forget-webhook-sessions.md)记录了 runtime 为何不保留交付或完成状态。 ## 共享值 @@ -66,5 +66,5 @@ register(rule: WebhookRule): () => Promise dispatch(delivery: VerifiedWebhookDelivery): void ``` -Source: [`packages/webhook/webhook/src/index.ts`](../../packages/webhook/webhook/src/index.ts) +Source: [`packages/experimental/webhook/src/index.ts`](../../packages/experimental/webhook/src/index.ts) diff --git a/docs/subsystems/workflow.i18n.yaml b/docs/subsystems/workflow.i18n.yaml index fdd5f9c496..f3b906de0d 100644 --- a/docs/subsystems/workflow.i18n.yaml +++ b/docs/subsystems/workflow.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/workflow.md /workflow: - en: ba4972edc6ffa75b - zh: cd93b733e5c524a6 + en: 4b264eb88e5cedfb + zh: 78585763a79cd1c9 /workflow/the-start-request: en: 6c8755833293a36c zh: a0f061387fe0d3a6 @@ -24,5 +24,5 @@ en: de123821a128c1d1 zh: e50bcaea2812575a /workflow/durable-chat-records: - en: 59896f51d9696ffa - zh: a12b33117f60d1fe + en: eefba4d75e7bcfe9 + zh: feeeaf660f97f96d diff --git a/docs/subsystems/workflow.md b/docs/subsystems/workflow.md index 8f56774699..5b51dc1ff1 100644 --- a/docs/subsystems/workflow.md +++ b/docs/subsystems/workflow.md @@ -4,7 +4,7 @@ English | [中文](workflow.zh.md) The workflow seam lets an agent run a model-written orchestration SCRIPT that starts subagents. Like [subagent](subagent.md) it is **one optional capability**, not part of the agent loop, so its types and operations live here rather than in [core.md](core.md). Like bash, it permits ONE engine implementation per context to provide `ctx.workflowEngine`; there is no named-provider registry (a second engine replaces the first through plugin configuration rather than running beside it). -Service Definition: [dsh-workflow](../../packages/workflow/workflow) (`ctx.workflowEngine` and the vocabulary below). [dsh-workflow-ptc](../../packages/workflow/workflow-ptc) executes the VM and helpers through the shared Node PTC process runtime under the calling Session's file policy. The consumers are [dsh-tool-workflow](../../packages/workflow/tool-workflow) and the opt-in [dsh-tool-ralph](../../packages/workflow/tool-ralph). [Workflow sandbox reuse](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md) owns execution choices; the [dynamic-workflows decision](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) owns script semantics. +Service Definition: [dsh-workflow](../../packages/workflow/workflow) (`ctx.workflowEngine` and the vocabulary below). [dsh-workflow-ptc](../../packages/workflow/workflow-ptc) executes the VM and helpers through the shared Node PTC process runtime under the calling Session's file policy. The consumers are [dsh-tool-workflow](../../packages/workflow/tool-workflow) and the opt-in [dsh-tool-ralph](../../packages/experimental/tool-ralph). [Workflow sandbox reuse](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.md) owns execution choices; the [dynamic-workflows reference](../../packages/workflow/workflow/README.md) owns script semantics. Sources: browser-safe vocabulary in [`packages/workflow/workflow/src/types.ts`](../../packages/workflow/workflow/src/types.ts), Host request and live-run handles in [`runtime-types.ts`](../../packages/workflow/workflow/src/runtime-types.ts). @@ -123,7 +123,7 @@ The `workflow/*` events (`workflow/start`, `workflow/phase`, `workflow/log`, `wo The top-level `dsh-tool-workflow` consumer projects display facts into its calling parent Session without changing execution ownership. It writes `tool-workflow/run-start` after a run is accepted, pairs member start and end by `runId + seq`, and writes `tool-workflow/run-end` only after the result is known and disposal reaches quiescence. Nested transport calls write no record. The first append failure disables later writes for that run, so the log remains empty or a legal continuous prefix and the tool result is unchanged. -`dsh-tool-workflow/invariant` validates the same protocol before live commit and when a Session is loaded: one start per run, positive unique member sequences, paired member endings, no run ending with open members, and no updates after the run ending. A missing member ending or run ending at the log tail is valid interruption evidence rather than corruption. +The record protocol allows one start per run, positive unique member sequences, paired member endings, no run ending with open members, and no updates after the run ending. A missing member ending or run ending at the log tail is valid interruption evidence rather than corruption. `dsh-client-ui-workflow-run` folds the four events through the Conversation Node engine into one `workflow-run` Chat node anchored at the run-start sequence, after the original workflow tool node. Phase groups come only from actual member starts and preserve exact strings, including the distinction between an omitted phase and `''`. Closed Locations turn missing terminal facts into interrupted presentation. The [UI package README](../../packages/client/ui-workflow-run/README.md) owns disclosure, status, and same-parent local navigation behavior. diff --git a/docs/subsystems/workflow.zh.md b/docs/subsystems/workflow.zh.md index dd24f920f6..1514fa31d6 100644 --- a/docs/subsystems/workflow.zh.md +++ b/docs/subsystems/workflow.zh.md @@ -4,7 +4,7 @@ 工作流 seam 允许 agent(智能体)运行由模型编写、会启动 subagent 的编排脚本。与 [subagent](subagent.zh.md) 一样,它是**一项可选能力**,不属于 agent loop,因此其类型和操作记录在此处,而非 [core.md](core.zh.md)。与 bash 一样,每个上下文只允许一个引擎实现提供 `ctx.workflowEngine`;没有命名提供方注册表(第二个引擎通过插件配置替换第一个,而不与它同时运行)。 -Service Definition:[dsh-workflow](../../packages/workflow/workflow)(`ctx.workflowEngine` 和下文词汇)。[dsh-workflow-ptc](../../packages/workflow/workflow-ptc)通过共享 Node PTC 进程运行时按调用 Session 的文件策略执行 VM 与辅助函数。消费方为 [dsh-tool-workflow](../../packages/workflow/tool-workflow) 和需显式启用的 [dsh-tool-ralph](../../packages/workflow/tool-ralph)。[工作流沙箱复用](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责执行选择;[动态工作流决策](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md)负责脚本语义。 +Service Definition:[dsh-workflow](../../packages/workflow/workflow)(`ctx.workflowEngine` 和下文词汇)。[dsh-workflow-ptc](../../packages/workflow/workflow-ptc)通过共享 Node PTC 进程运行时按调用 Session 的文件策略执行 VM 与辅助函数。消费方为 [dsh-tool-workflow](../../packages/workflow/tool-workflow) 和需显式启用的 [dsh-tool-ralph](../../packages/experimental/tool-ralph)。[工作流沙箱复用](../../.agents/notes/implemented/architecture/2026-09-13-workflow-ptc-sandbox-reuse.zh.md)负责执行选择;[动态工作流参考](../../packages/workflow/workflow/README.zh.md)负责脚本语义。 源码:浏览器安全词汇位于 [`packages/workflow/workflow/src/types.ts`](../../packages/workflow/workflow/src/types.ts),Host 请求与活跃运行句柄位于 [`runtime-types.ts`](../../packages/workflow/workflow/src/runtime-types.ts)。 @@ -123,7 +123,7 @@ interface WorkflowRun { 顶层 `dsh-tool-workflow` 消费方把展示事实投影到调用它的父 Session,同时不改变执行所有权。运行接受后写 `tool-workflow/run-start`,以 `runId + seq` 配对成员开始与结束,并且只在结果已取得且 dispose 完全停稳后写 `tool-workflow/run-end`。嵌套 transport 调用不写记录。第一次 append 失败会禁用本运行后续写入,因此日志保持为空或合法连续前缀,工具结果不变。 -`dsh-tool-workflow/invariant` 会在实时提交前和 Session 加载时校验同一协议:每个运行只有一个 start,成员序号为正且唯一,成员 end 必须配对,仍有开放成员时不能结束运行,运行结束后不能继续更新。日志尾部缺少成员 end 或 run end 是有效的中断证据,不是损坏。 +该记录协议规定:每个运行只有一个 start,成员序号为正且唯一,成员 end 必须配对,仍有开放成员时不能结束运行,运行结束后不能继续更新。日志尾部缺少成员 end 或 run end 是有效的中断证据,不是损坏。 `dsh-client-ui-workflow-run` 通过 Conversation Node 引擎把四类事件折叠为一个 `workflow-run` Chat 节点,以 run-start 序号锚定在原工作流工具节点之后。阶段组只来自真正开始过的成员,并保留精确字符串,包括字段缺省与 `''` 的区别。Location 关闭时,缺失终点会显示为已中断。[界面包 README](../../packages/client/ui-workflow-run/README.zh.md)负责定义 disclosure、状态与同父本地导航行为。 diff --git a/docs/subsystems/working-directory.i18n.yaml b/docs/subsystems/working-directory.i18n.yaml new file mode 100644 index 0000000000..de4ffb228e --- /dev/null +++ b/docs/subsystems/working-directory.i18n.yaml @@ -0,0 +1,13 @@ +# Bilingual-pair consistency record for working-directory.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/working-directory.md +/working-directory: + en: 0882d8b4c09c2890 + zh: a75af7ce19f661fa +/working-directory/directory-selection: + en: b67e268cc9422d31 + zh: 4c7243c87aadcd7a +/working-directory/model-context: + en: 9789081854cbdd35 + zh: 1f6712c4e5b9c47e diff --git a/docs/subsystems/working-directory.md b/docs/subsystems/working-directory.md new file mode 100644 index 0000000000..4e9ae74c57 --- /dev/null +++ b/docs/subsystems/working-directory.md @@ -0,0 +1,64 @@ +# Working Directory + +English | [中文](working-directory.zh.md) + +A Session's active directory is owned by [`dsh-working-directory`](../../packages/session/working-directory/README.md). The immutable header identifies the original project; `working-directory/change` records the effective directory used by later directory-based operations. The generated [persistence catalog](../persistence-catalog.md) owns the event declaration. + +## Directory selection + +`ctx.workingDirectory.get(session)` reads the logged value. `ensure(agent)` validates it and restores the original project when the directory disappeared. `set(agent, path)` validates and records a requested change. Sessions without a header directory use the configured runtime fallback; an unavailable fallback produces an error. + +Changes are local to one Session. Relative filesystem operations, newly started processes, instruction discovery, skills, and file completion use the active directory. Existing terminals and persistent shells retain their own directories. Sandbox writable roots and project membership remain anchored to the original project. + +## Model context + +The initial value and subsequent current values reach the model as required user-context snapshots, even when optional runtime context is disabled. Each committed change attempts to queue a notice through the ordinary Agent inbox; cancellation or disposal can discard notices before request admission. The directory event survives, and the next admitted request receives the current value. Directory context never enters the system prompt. [`working_directory`](../../packages/session/tool-working-directory/README.md) exposes one optional `cd` argument. + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.workingDirectory` — `WorkingDirectoryService` + +One owner for each Session's effective directory and its model-visible changes. + +```ts cordis-catalog +/** + * Read the committed directory without filesystem I/O. + * @param session - Session whose directory is requested. + * @returns its effective absolute directory. + */ +get(session: Session): string + +/** + * Validate the current directory and restore the original project when it disappeared. + * @param agent - live or unpublished Agent owning the Session. + * @param signal - cancellation for filesystem inspection. + * @returns the existing directory; recovery is committed before fulfillment. + * A notice failure is warned without reverting the committed state. + * @throws when the original project is also unavailable. + */ +ensure(agent: Agent, signal?: AbortSignal): Promise + +/** + * Change one Session's directory without changing existing processes or permissions. + * @param agent - live or unpublished Agent owning the Session. + * @param path - absolute path or a path relative to its current directory. + * @param signal - cancellation before the durable change. + * @returns the canonical absolute directory, committed before fulfillment. + * A notice failure is warned; the next request still receives the committed directory. + * @throws when the requested path is not an existing directory. + */ +set(agent: Agent, path: string, signal?: AbortSignal): Promise +``` + +Types: [Agent](core.md) · [Session](session.md) + +Source: [`packages/session/working-directory/src/index.ts`](../../packages/session/working-directory/src/index.ts) + diff --git a/docs/subsystems/working-directory.zh.md b/docs/subsystems/working-directory.zh.md new file mode 100644 index 0000000000..bb239ddc43 --- /dev/null +++ b/docs/subsystems/working-directory.zh.md @@ -0,0 +1,64 @@ +# 工作目录 + +[English](working-directory.md) | 中文 + +Session 的当前目录由 [`dsh-working-directory`](../../packages/session/working-directory/README.zh.md) 拥有。不可变 header 标识原始项目;`working-directory/change` 记录后续目录相关操作使用的有效目录。生成的[持久化目录](../persistence-catalog.zh.md) 拥有事件声明。 + +## 目录选择 + +`ctx.workingDirectory.get(session)` 读取日志中的值。`ensure(agent)` 验证它,并在目录消失时恢复原始项目。`set(agent, path)` 验证并记录请求的变更。header 没有目录的 Session 使用配置的运行时回退目录;回退目录不可用时返回错误。 + +变更仅影响一个 Session。相对路径文件系统操作、新启动的进程、指令发现、技能和文件补全使用当前目录。已有终端和持久 shell 保留自己的目录。沙箱可写根目录与项目归属继续绑定原始项目。 + +## 模型上下文 + +初始值与后续当前值通过必需的用户上下文快照到达模型,即使可选运行时上下文被禁用也不例外。每次已提交变更都会尝试通过普通 Agent 收件箱排入通知;取消或销毁可能丢弃尚未进入请求的通知。目录事件仍然保留,下一次获准进入的请求会收到当前值。目录上下文不进入系统提示词。[`working_directory`](../../packages/session/tool-working-directory/README.zh.md) 提供一个可选的 `cd` 参数。 + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.workingDirectory` — `WorkingDirectoryService` + +One owner for each Session's effective directory and its model-visible changes. + +```ts cordis-catalog +/** + * Read the committed directory without filesystem I/O. + * @param session - Session whose directory is requested. + * @returns its effective absolute directory. + */ +get(session: Session): string + +/** + * Validate the current directory and restore the original project when it disappeared. + * @param agent - live or unpublished Agent owning the Session. + * @param signal - cancellation for filesystem inspection. + * @returns the existing directory; recovery is committed before fulfillment. + * A notice failure is warned without reverting the committed state. + * @throws when the original project is also unavailable. + */ +ensure(agent: Agent, signal?: AbortSignal): Promise + +/** + * Change one Session's directory without changing existing processes or permissions. + * @param agent - live or unpublished Agent owning the Session. + * @param path - absolute path or a path relative to its current directory. + * @param signal - cancellation before the durable change. + * @returns the canonical absolute directory, committed before fulfillment. + * A notice failure is warned; the next request still receives the committed directory. + * @throws when the requested path is not an existing directory. + */ +set(agent: Agent, path: string, signal?: AbortSignal): Promise +``` + +Types: [Agent](core.zh.md) · [Session](session.zh.md) + +Source: [`packages/session/working-directory/src/index.ts`](../../packages/session/working-directory/src/index.ts) + diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml index 535ca0ce8a..7701312154 100644 --- a/docs/subsystems/workspace.i18n.yaml +++ b/docs/subsystems/workspace.i18n.yaml @@ -12,8 +12,8 @@ en: 697698bc46adc477 zh: 73d073933ed68c53 /workspaces/the-registry-ctx-workspaceregistry: - en: 7ba5b4e06c162bd6 - zh: f1dd7eacacd07e0b + en: 73b54ccf836b7f46 + zh: 7ee93ba8bb0a616a /workspaces/default-workspace-initialization: en: c4307480e504ba81 zh: dc8a98e1b447b900 diff --git a/docs/subsystems/workspace.md b/docs/subsystems/workspace.md index 900be6948c..e501d7b7bb 100644 --- a/docs/subsystems/workspace.md +++ b/docs/subsystems/workspace.md @@ -117,7 +117,7 @@ Ownership truth is the record's ordered `sessionIds`, never derived from session ## The registry: `ctx.workspaceRegistry` -`WorkspaceRegistry` ([signatures](#ctxworkspaceregistry--workspaceregistry)) owns registration and resolution. `create(path, title?)` requires a fully qualified path, canonicalizes it, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? defaultWorkspaceTitle(path)` prepended to the durable registry order (different canonical paths may share a display title, and a path with no final segment uses its root spelling). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same fully qualified realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption. +`WorkspaceRegistry` ([signatures](#ctxworkspaceregistry--workspaceregistry)) owns registration and resolution. `create(path, title?)` requires a fully qualified path, canonicalizes it, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? defaultWorkspaceTitle(path)` prepended to the durable registry order (different canonical paths may share a display title, and a path with no final segment uses its root spelling). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same fully qualified realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([reference](../../packages/workspace/workspace/README.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption. Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`. @@ -232,10 +232,10 @@ Typed Remote control of transient Session-owned terminal processes. ```ts cordis-catalog /** - * Read the Session working directory and terminal limits without resolving a shell. + * Read the Session's current directory and terminal limits without filesystem validation or shell lookup. * @param agent - Session owner supplied by the Gateway. * @param signal - request cancellation. - * @returns the Session workspace directory and terminal limits. + * @returns the logged current directory and terminal limits; retained terminals keep their own process directories. */ @Remote environment(agent: Agent, signal: AbortSignal): TerminalEnvironment diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md index ad78c7e1f3..868712b4c9 100644 --- a/docs/subsystems/workspace.zh.md +++ b/docs/subsystems/workspace.zh.md @@ -117,7 +117,7 @@ interface Workspace { ## 注册表:`ctx.workspaceRegistry` -`WorkspaceRegistry`([签名](#ctxworkspaceregistry--workspaceregistry))拥有注册与解析。`create(path, title?)` 要求完全限定路径并将其规范化,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? defaultWorkspaceTitle(path)` 的记录并前插到持久的注册表顺序中(不同规范路径可以共享同一显示标题,没有最终路径段时使用根路径拼写)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套完全限定 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。 +`WorkspaceRegistry`([签名](#ctxworkspaceregistry--workspaceregistry))拥有注册与解析。`create(path, title?)` 要求完全限定路径并将其规范化,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? defaultWorkspaceTitle(path)` 的记录并前插到持久的注册表顺序中(不同规范路径可以共享同一显示标题,没有最终路径段时使用根路径拼写)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套完全限定 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([参考](../../packages/workspace/workspace/README.zh.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。 会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.zh.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。 @@ -232,10 +232,10 @@ Typed Remote control of transient Session-owned terminal processes. ```ts cordis-catalog /** - * Read the Session working directory and terminal limits without resolving a shell. + * Read the Session's current directory and terminal limits without filesystem validation or shell lookup. * @param agent - Session owner supplied by the Gateway. * @param signal - request cancellation. - * @returns the Session workspace directory and terminal limits. + * @returns the logged current directory and terminal limits; retained terminals keep their own process directories. */ @Remote environment(agent: Agent, signal: AbortSignal): TerminalEnvironment diff --git a/docs/subsystems/worktrees.i18n.yaml b/docs/subsystems/worktrees.i18n.yaml new file mode 100644 index 0000000000..03544fbc81 --- /dev/null +++ b/docs/subsystems/worktrees.i18n.yaml @@ -0,0 +1,13 @@ +# Bilingual-pair consistency record for worktrees.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/worktrees.md +/git-worktrees: + en: 21ec4a3b825d7e81 + zh: c841c5ca4b4e8959 +/git-worktrees/creation-and-retention: + en: 53f643c5d30e00b2 + zh: ba9e52efc62c7656 +/git-worktrees/request-and-result: + en: 8391b091607600eb + zh: 6c735a337d60d566 diff --git a/docs/subsystems/worktrees.md b/docs/subsystems/worktrees.md new file mode 100644 index 0000000000..14db6711bc --- /dev/null +++ b/docs/subsystems/worktrees.md @@ -0,0 +1,76 @@ +# Git Worktrees + +English | [中文](worktrees.zh.md) + +The experimental [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.md) service provides `ctx.worktrees`. Its [`create_worktree`](../../packages/experimental/tool-worktree/README.md) consumer creates a new Git branch and checkout, then selects it through the [working-directory service](working-directory.md). The [Git Worktrees optional bundle](../../packages/experimental/tool-worktree/README.md#use-this-package) loads both packages through the GUI plugin manager. It ships switched off; explicit compositions can also mount the two packages. + +## Creation and retention + +The calling Session's current directory selects the source repository. Creation pins the requested local commit, branch, or tag before allocating the checkout; omission selects `HEAD`. Each name must identify a new branch and an unused checkout path. The operation neither fetches missing objects nor copies staged, unstaged, untracked, or ignored files from the source checkout. + +The default destination is `/.agents/worktrees/`. `repositoryRoot` is the top-level directory of the caller’s current checkout; a linked checkout therefore contains its own pool. The service writes a `.gitignore` containing `*` and a trailing newline only when its allocation creates the configured pool directory. Existing files and ignore rules are preserved. The calling Session’s existing sandbox policy must permit writes to the destination and shared Git administration directory. Successful Git setup precedes the working-directory change. + +Leaving through `working_directory({ cd: path })` retains the checkout and branch. Cancellation or failure can also retain partial artifacts; the error identifies their location. The service aborts and drains pending operations on disposal. The [package README](../../packages/experimental/worktree/README.md) owns configuration and Git requirements. + +## Request and result + +The request selects only the new name and local revision. The result identifies the created checkout and exact commit; it does not change the Session's original project or write permissions. + +```ts type-equiv +/** A new checkout from a locally available Git commit, branch, or tag. */ +interface CreateWorktreeRequest { + /** New branch name, also used as the relative checkout directory; omission generates a name. */ + name?: string + /** Local revision to resolve before creation; omission selects HEAD. */ + from?: string +} +``` + +```ts type-equiv +/** Committed Git baseline and the calling Session's resulting working directory. */ +interface CreatedWorktree { + /** Canonical absolute checkout directory in the filesystem provider's execution world. */ + path: string + /** Newly created branch name. */ + branch: string + /** Resolved commit object name; repository-local replacement refs can change its checked-out content. */ + baseCommit: string + /** Canonical root of the source checkout. */ + repositoryRoot: string +} +``` + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.worktrees` — `WorktreeService` + +Creates retained Git worktrees under the mounted filesystem and sandbox providers. + +```ts cordis-catalog +/** + * Create a fresh branch and checkout at a pinned local revision, then enter it. + * Existing branches or checkout paths fail. Uncommitted files stay in the source checkout. + * Checkout disables configured clean, smudge, and process filters without changing Git config. + * Repository-local replacement refs still apply; baseCommit reports the resolved object name. + * The new checkout becomes current only after Git setup succeeds. Failures may retain newly + * allocated Git/filesystem artifacts; no branch or checkout is removed automatically. + * @param agent - caller whose current directory selects the source repository and file policy. + * @param request - optional new name and local revision; defaults are generated name and HEAD. + * @param signal - cancellation of lookup, creation, and working-directory publication. + * @returns canonical checkout path, branch name, pinned commit, and source repository root. + */ +async create(agent: Agent, request: CreateWorktreeRequest = {}, signal?: AbortSignal): Promise +``` + +Types: [Agent](core.md) + +Source: [`packages/experimental/worktree/src/index.ts`](../../packages/experimental/worktree/src/index.ts) + diff --git a/docs/subsystems/worktrees.zh.md b/docs/subsystems/worktrees.zh.md new file mode 100644 index 0000000000..719d99266b --- /dev/null +++ b/docs/subsystems/worktrees.zh.md @@ -0,0 +1,76 @@ +# Git 工作树 + +[English](worktrees.md) | 中文 + +实验性的 [`dsh-experimental-worktree`](../../packages/experimental/worktree/README.zh.md) 服务提供 `ctx.worktrees`。其消费方 [`create_worktree`](../../packages/experimental/tool-worktree/README.zh.md) 创建新的 Git 分支与检出目录,再通过[工作目录服务](working-directory.zh.md)选中它。[Git 工作树可选 bundle](../../packages/experimental/tool-worktree/README.zh.md#use-this-package)通过 GUI 插件管理器一起加载这两个包。它随安装提供,但默认关闭;显式组合也可以挂载这两个包。 + +## 创建与保留 + +调用方 Session 的当前目录决定源仓库。创建操作在分配检出目录前,将请求的本地提交、分支或标签固定为具体提交;省略时选择 `HEAD`。每个名称必须对应新分支与未使用的检出路径。操作既不获取缺失对象,也不复制源检出目录中的暂存、未暂存、未跟踪或被忽略文件。 + +默认目标为 `/.agents/worktrees/`。`repositoryRoot` 是调用方当前检出目录的顶层目录;因此,链接检出目录会在自身内部保存工作树父目录。仅当服务的分配操作创建了配置指定的父目录时,服务才会写入内容为 `*` 加换行符的 `.gitignore`。已有文件与忽略规则保持不变。调用方 Session 的现有沙箱策略必须允许写入目标目录与共享 Git 管理目录。Git 初始化成功后才改变工作目录。 + +通过 `working_directory({ cd: path })` 离开时,会保留检出目录与分支。取消或失败也可能留下部分产物;错误会指出它们的位置。服务销毁时会中止待完成操作并等待它们结束。[包 README](../../packages/experimental/worktree/README.zh.md)拥有配置与 Git 要求。 + +## 请求与结果 + +请求只选择新名称与本地版本。结果标识创建的检出目录与确切提交;它不会改变 Session 的原始项目或写权限。 + +```ts type-equiv +/** A new checkout from a locally available Git commit, branch, or tag. */ +interface CreateWorktreeRequest { + /** New branch name, also used as the relative checkout directory; omission generates a name. */ + name?: string + /** Local revision to resolve before creation; omission selects HEAD. */ + from?: string +} +``` + +```ts type-equiv +/** Committed Git baseline and the calling Session's resulting working directory. */ +interface CreatedWorktree { + /** Canonical absolute checkout directory in the filesystem provider's execution world. */ + path: string + /** Newly created branch name. */ + branch: string + /** Resolved commit object name; repository-local replacement refs can change its checked-out content. */ + baseCommit: string + /** Canonical root of the source checkout. */ + repositoryRoot: string +} +``` + + + + + +## Cordis API + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.zh.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.worktrees` — `WorktreeService` + +Creates retained Git worktrees under the mounted filesystem and sandbox providers. + +```ts cordis-catalog +/** + * Create a fresh branch and checkout at a pinned local revision, then enter it. + * Existing branches or checkout paths fail. Uncommitted files stay in the source checkout. + * Checkout disables configured clean, smudge, and process filters without changing Git config. + * Repository-local replacement refs still apply; baseCommit reports the resolved object name. + * The new checkout becomes current only after Git setup succeeds. Failures may retain newly + * allocated Git/filesystem artifacts; no branch or checkout is removed automatically. + * @param agent - caller whose current directory selects the source repository and file policy. + * @param request - optional new name and local revision; defaults are generated name and HEAD. + * @param signal - cancellation of lookup, creation, and working-directory publication. + * @returns canonical checkout path, branch name, pinned commit, and source repository root. + */ +async create(agent: Agent, request: CreateWorktreeRequest = {}, signal?: AbortSignal): Promise +``` + +Types: [Agent](core.zh.md) + +Source: [`packages/experimental/worktree/src/index.ts`](../../packages/experimental/worktree/src/index.ts) + diff --git a/docs/testing.i18n.yaml b/docs/testing.i18n.yaml index 2c9372e193..d7936c0fef 100644 --- a/docs/testing.i18n.yaml +++ b/docs/testing.i18n.yaml @@ -6,11 +6,11 @@ en: 0d7399a58529b9eb zh: 7f445b06430f21a4 /testing-policy/tiers: - en: 180c81863aa3f4dd - zh: ff3e96d3175c5cea + en: c024da7e8d9cb056 + zh: c4cc1a66d2903656 /testing-policy/how-specs-execute: - en: 298c13b105e86eae - zh: 4f1b0b7742fb7b49 + en: 5d8eb65958da096d + zh: 4a40f1c5fe5b2d14 /testing-policy/the-with-key-policy-inference-is-cheap-here: en: d555af53068bf523 zh: ddc4265e5de5b3be @@ -30,5 +30,5 @@ en: 5ea1c7ff7a9a5728 zh: bbae70e0cd2d5cbf /testing-policy/when-a-snapshot-test-is-required: - en: bc88377209d1077a - zh: 369a688d74a11df2 + en: 055966993c5392c0 + zh: eb35536ad2c4320b diff --git a/docs/testing.md b/docs/testing.md index c726270b79..9ea6db1501 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -12,13 +12,13 @@ How this repo tests, tier by tier, and the rules that keep a green suite meaning - **Owner-local expected output** (`pnpm run test:expected`): keyless assembled CLI/process expectations without a recorded-session round trip. Drivers use `*.expected.e2e.ts` beside `tests/expected/`; CI runs built exports. Package/script expectations use `test`, while browser expectations use `test:web`. - **Performance benchmarks** (`pnpm run test:bench`; required Linux PR gate `node 24 / benchmarks`): `benchmarks/` groups user-path gates. It builds libraries and workers; timed code runs under plain Node, never TSX. Synthetic inputs enforce time, heap, and scaling budgets; package-local `.perf.ts` stays diagnostic ([rules](../.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.md)). - **Snapshot** (`pnpm run test:snapshot`): a top-level scenario's highest recorded parent generation supplies user input and model replay, then serves as the expected persisted result. Parent filenames are `session[.vN].jsonl`; child roles are `session.[.vN].jsonl`; v0 omits `.v0`, positive versions require lowercase `.vN`, and each filename must agree with its header. Process scenarios start through `dsh`: headless owns one-shot behavior, the SDK owns persistent control, ACP owns automation-protocol behavior, and Web retains browser/ARIA evidence beside the same Session. `snapshot.yml` declares the profile, composition/header class, recording policy, exceptional replay or input metadata, and workspace facts. Typed tokens preserve parent/child identity relationships; only header pins own prompt/schema sidecars. A mutating scenario independently compares the complete `workspace.expected/` tree, which record and refresh never rewrite. Use `test:snapshot:record` when a model transcript changes and `test:snapshot:refresh` when replay input remains valid; review every resulting diff. -- **Web browser snapshot** (`pnpm run test:web`; required Linux PR gate): The command builds plugin CSS, then compares all session-driven `snapshots/web/` and UI-only `apps/web/tests/expected/` output in Chromium; the model/reasoning picker also runs in WebKit. CI enforces read-only `DSH_SNAPSHOT=replay`; record/refresh stay local, with every diff reviewed ([web lane](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md), [CI decision](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md)). +- **Web browser snapshot** (`pnpm run test:web`; required Linux PR gate): The command builds plugin CSS, then compares all session-driven `snapshots/web/` and UI-only `apps/web/tests/expected/` output in Chromium; the model/reasoning picker also runs in WebKit. CI enforces read-only `DSH_SNAPSHOT=replay`; record/refresh stay local, with every diff reviewed ([web lane](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md), [CI reference](../scripts/run-web-snapshots.ts)). Session fixtures retain headers and payloads but omit body sequence/time envelopes; replay synthesizes them. Replay, record, and refresh select each parent/child role's highest generation. Current fixtures use the [writer format](session-format-status.md) in their filenames and headers, one row per event, and embedded compact Assistant streams. Historical fixtures retain their released representation; explicit `sessionFormat` owners preserve migration coverage. Follow the [format-version cookbook](cookbook/adding-a-session-format-version.md#snapshot-successors) to add successors without changing predecessors. ## How specs execute -Forked workers run several spec files at once, the coverage gate splits into concurrent partitions beside the other gates in its job, and the self-hosted runners share one host and one volume. Only the process is isolated: ports, predictable paths, external namespaces, and inherited children are not. Own each acquired resource through its teardown, and read a spec that passes only when it runs alone as a defect in the spec rather than an unstable runner. [dsh-ci-test-reliability](../.agents/skills/dsh-ci-test-reliability/SKILL.md) owns the allocation, restoration, synchronization, timeout-budget, platform, and teardown rules; its [flake diagnosis workflow](../.agents/skills/dsh-ci-test-reliability/references/ci-flake-diagnosis.md) classifies an existing probabilistic failure. +Forked workers run several spec files at once, the coverage gate splits into concurrent partitions beside the other gates in its job, and the self-hosted runners share one host and one volume. Only the process is isolated: ports, predictable paths, external namespaces, and inherited children are not. Own each acquired resource through teardown. A spec passing only alone has a test defect, not an unstable runner. [dsh-ci-test-reliability](../.agents/skills/dsh-ci-test-reliability/SKILL.md) owns the allocation, restoration, synchronization, timeout-budget, platform, and teardown rules; its [flake diagnosis workflow](../.agents/skills/dsh-ci-test-reliability/references/ci-flake-diagnosis.md) classifies an existing probabilistic failure. [Unexpected fork exits](../scripts/coverage-fork-diagnostics.ts) report PID, file, code, and signal. ## The with-key policy: inference is cheap here @@ -52,4 +52,4 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword ## When a snapshot test is required -Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless recorded-session scenario in the same PR; package, e2e, mock-only, and rationale evidence does not replace the assembled transcript. Headless, SDK, ACP, and Web recordings live under `snapshots/session/`, `snapshots/sdk/`, `snapshots/acp/`, and `snapshots/web/`; a Web rendering may explicitly borrow another scenario's canonical session. Expected output that is not driven by a recorded session stays with its owning app, package, or script under `tests/expected/` and does not use the `*.snapshot.ts` suffix. [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.md) owns the shared storage rules and profile adapters. Agent-loop, session-lifecycle, and `SessionEventMap` changes update both SDK projections: `snapshots/sdk/` owns TypeScript, while [Python-runtime CI](../.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.md) owns `scripts/snapshots/python-sdk-single-exe/`. New capability seams and lifecycle or transcript variants name every required tier at plan time. +Every non-trivial model-, protocol-, or human-visible change adds or updates a keyless recorded-session scenario in the same PR; package, e2e, mock-only, and rationale evidence does not replace the assembled transcript. Headless, SDK, ACP, and Web recordings live under `snapshots/session/`, `snapshots/sdk/`, `snapshots/acp/`, and `snapshots/web/`; a Web rendering may explicitly borrow another scenario's canonical session. Expected output that is not driven by a recorded session stays with its owning app, package, or script under `tests/expected/` and does not use the `*.snapshot.ts` suffix. [`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.md) owns the shared storage rules and profile adapters. Agent-loop, session-lifecycle, and `SessionEventMap` changes update both SDK projections: `snapshots/sdk/` owns TypeScript, while [Python-runtime CI](../.github/workflows/ci-master.yml) owns `scripts/snapshots/python-sdk-single-exe/`. New capability seams and lifecycle or transcript variants name every required tier at plan time. diff --git a/docs/testing.zh.md b/docs/testing.zh.md index 977960c76d..81c16c60ab 100644 --- a/docs/testing.zh.md +++ b/docs/testing.zh.md @@ -12,13 +12,13 @@ - **所属位置的预期输出**(`pnpm run test:expected`):无录制会话往返的无密钥组装 CLI/进程预期。驱动使用 `*.expected.e2e.ts`,并与 `tests/expected/` 同属一处;CI 针对构建产物运行。包/脚本预期使用 `test`,浏览器预期使用 `test:web`。 - **性能基准**(`pnpm run test:bench`;必需的 Linux PR gate `node 24 / benchmarks`):`benchmarks/` 按用户路径组织门禁。它先构建 library 和 worker;被计时代码在纯 Node 下运行,不使用 TSX。合成输入执行耗时、堆和缩放预算;包内 `.perf.ts` 保留为诊断([规则](../.agents/notes/implemented/testing/2026-09-04-session-open-performance-gate.zh.md))。 - **快照**(`pnpm run test:snapshot`):顶层场景数值最高的已录制 parent generation 同时提供用户输入和模型回放,并作为持久化结果的预期值。parent 文件名是 `session[.vN].jsonl`;child 角色使用 `session.[.vN].jsonl`;v0 省略 `.v0`,正版本必须使用小写 `.vN`,且每个文件名必须与其 header 一致。进程级场景都通过 `dsh` 启动:headless 负责一次性行为,SDK 负责持久控制,ACP 负责自动化协议行为,Web 在同一 Session 旁保留浏览器与 ARIA 证据。`snapshot.yml` 声明 profile、组合与请求头类别、录制策略、例外回放或输入元数据以及 workspace 事实。带类型的 token 保留父子身份关系;只有请求头 pin 拥有 prompt/schema sidecar。变更 workspace 的场景会独立比较完整的 `workspace.expected/` 目录,record 与 refresh 绝不改写该目录。当模型 transcript(文本记录)变化时使用 `test:snapshot:record`,回放输入仍有效时使用 `test:snapshot:refresh`;请审查所有结果差异。 -- **Web 浏览器快照**(`pnpm run test:web`;必需的 Linux PR(Pull Request)门禁):命令先构建插件 CSS,再用 Chromium 比较 `snapshots/web/` 下全部会话驱动输出和 `apps/web/tests/expected/` 下全部纯 UI 输出;模型与推理强度选择场景还在 WebKit 中运行。CI 强制只读的 `DSH_SNAPSHOT=replay`;record/refresh 留在本地,每处 diff 都须评审([web 车道](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md)、[CI 决策](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md))。 +- **Web 浏览器快照**(`pnpm run test:web`;必需的 Linux PR(Pull Request)门禁):命令先构建插件 CSS,再用 Chromium 比较 `snapshots/web/` 下全部会话驱动输出和 `apps/web/tests/expected/` 下全部纯 UI 输出;模型与推理强度选择场景还在 WebKit 中运行。CI 强制只读的 `DSH_SNAPSHOT=replay`;record/refresh 留在本地,每处 diff 都须评审([web 车道](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md)、[CI 说明](../scripts/run-web-snapshots.ts))。 Session fixture 保留 header 与 payload,但省略正文 seq/time envelope;replay 会合成这些 envelope。Replay、record 与 refresh 会选择每个 parent/child 角色的最高 generation。当前 fixture 在文件名与 header 中使用[写入格式](session-format-status.zh.md),每个事件一行,并嵌入紧凑 Assistant stream。历史 fixture 保留其已发布表示;显式 `sessionFormat` 所有者保留迁移覆盖。按照[格式版本实操手册](cookbook/adding-a-session-format-version.zh.md#snapshot-successors)添加后继代际,不改动前代。 ## spec 如何被执行 -fork 出的 worker 会同时运行多个 spec 文件,coverage gate 会拆成并发的 partition,与同一个 job 中的其它 gate 并排运行,而自托管 runner 共用同一台宿主机和同一个卷。被隔离的只有进程:端口、可预测路径、外部命名空间和继承而来的子进程都不隔离。为每个占用的资源负责到它的 teardown,并把「只有单独运行时才通过」的 spec 读作该 spec 的缺陷,而不是 runner 不稳定。[dsh-ci-test-reliability](../.agents/skills/dsh-ci-test-reliability/SKILL.md) 负责资源分配、状态恢复、同步、超时预算、平台差异与 teardown 规则;它的 [flake 诊断流程](../.agents/skills/dsh-ci-test-reliability/references/ci-flake-diagnosis.md)用于归类已经存在的概率性失败。 +fork 出的 worker 会同时运行多个 spec 文件,coverage gate 会拆成并发的 partition,与同一个 job 中的其它 gate 并排运行,而自托管 runner 共用同一台宿主机和同一个卷。被隔离的只有进程:端口、可预测路径、外部命名空间和继承而来的子进程都不隔离。为每个占用的资源负责到 teardown。只在单独运行时通过的 spec 存在测试缺陷,而非 runner 不稳定。[dsh-ci-test-reliability](../.agents/skills/dsh-ci-test-reliability/SKILL.md) 负责资源分配、状态恢复、同步、超时预算、平台差异与 teardown 规则;它的 [flake 诊断流程](../.agents/skills/dsh-ci-test-reliability/references/ci-flake-diagnosis.md)用于归类已经存在的概率性失败。 [fork 意外退出](../scripts/coverage-fork-diagnostics.ts)时会报告 PID、文件、退出码和信号。 ## 带密钥策略:推理(inference)在这里很便宜 @@ -52,4 +52,4 @@ e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身 ## 何时需要快照测试 -每项非平凡的模型可见、协议可见或人类可见变更,都在同一 PR 中添加或更新无密钥录制会话场景;包级、e2e、仅 mock 和 PR 理由证据不能取代组装后的 transcript。Headless、SDK、ACP 和 Web 录制分别位于 `snapshots/session/`、`snapshots/sdk/`、`snapshots/acp/` 和 `snapshots/web/`;Web 渲染可以显式借用另一个场景的规范会话。不由录制会话驱动的预期输出保留在所属应用、包或脚本的 `tests/expected/` 下,并且不使用 `*.snapshot.ts` 后缀。[`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.zh.md) 拥有共享存储规则和 profile 适配器。Agent loop、会话生命周期和 `SessionEventMap` 变更应更新两个 SDK 投影:`snapshots/sdk/` 拥有 TypeScript,[Python 运行时 CI](../.agents/notes/implemented/process/2026-09-06-master-only-platform-ci.zh.md) 拥有 `scripts/snapshots/python-sdk-single-exe/`。新增 capability seam、生命周期或 transcript 变体应在计划阶段列出每个必需层级。 +每项非平凡的模型可见、协议可见或人类可见变更,都在同一 PR 中添加或更新无密钥录制会话场景;包级、e2e、仅 mock 和 PR 理由证据不能取代组装后的 transcript。Headless、SDK、ACP 和 Web 录制分别位于 `snapshots/session/`、`snapshots/sdk/`、`snapshots/acp/` 和 `snapshots/web/`;Web 渲染可以显式借用另一个场景的规范会话。不由录制会话驱动的预期输出保留在所属应用、包或脚本的 `tests/expected/` 下,并且不使用 `*.snapshot.ts` 后缀。[`dsh-session-snapshot`](../packages/test-support/session-snapshot/README.zh.md) 拥有共享存储规则和 profile 适配器。Agent loop、会话生命周期和 `SessionEventMap` 变更应更新两个 SDK 投影:`snapshots/sdk/` 拥有 TypeScript,[Python 运行时 CI](../.github/workflows/ci-master.yml) 拥有 `scripts/snapshots/python-sdk-single-exe/`。新增 capability seam、生命周期或 transcript 变体应在计划阶段列出每个必需层级。 diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index 48ff1a5b62..e14eeb549f 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 6cc2cd9ced70bb09 zh: 1c0e6cb4ba2ed637 /tool-schema-catalog/tool-package-map: - en: 75120ef94b3c2830 - zh: c5db28dd6853e91a + en: 7caa258c67464109 + zh: 53bfb5581c48dc45 /tool-schema-catalog/deepseek-ai-dsh-plugin-manager: en: e0143916bb634762 zh: e0143916bb634762 @@ -60,8 +60,8 @@ en: 76fbbf0fe38aef10 zh: 76fbbf0fe38aef10 /tool-schema-catalog/deepseek-ai-dsh-tools/run-code: - en: daae801c6b93b624 - zh: 7cd6fd029b683d69 + en: 2cfa8042a920b191 + zh: bbd0aae394fb3806 /tool-schema-catalog/deepseek-ai-dsh-plan-mode: en: b500b530845e26a9 zh: b500b530845e26a9 @@ -136,28 +136,28 @@ zh: c6ce31ff2c195520 /tool-schema-catalog/deepseek-ai-dsh-tool-fs-search/grep: en: 6e1f7da62cc9a35b - zh: ee7f3081887f8067 + zh: 594817b0befeea86 /tool-schema-catalog/deepseek-ai-dsh-tool-terminal: en: 77501c1b592b6a88 zh: 77501c1b592b6a88 /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-close: - en: 6d6e428c866ac6c1 - zh: 8033539fcdcb6e7b + en: 4da73ab1c95aadc6 + zh: 998471bc4f5af00a /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-list: - en: ae7d5bce32142aa5 - zh: e956a650a5bf294b + en: 4cde2b1d38f93e04 + zh: 2c93f43d54ec9da2 /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-open: - en: c542bd7f7e872441 - zh: 14731de81780b518 + en: 7051c20587ae426d + zh: 39a6aad70d643fbc /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-read: - en: 6a7491d2436e4404 - zh: 772ff408ec0a67e3 + en: 833b47cca9b00b9d + zh: 24b3db5b48f0446a /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-send: - en: e24ca8735dd59adb - zh: 24f7140a0aed0968 + en: 60a8805611533884 + zh: 50e4ac6e7f7b9b0a /tool-schema-catalog/deepseek-ai-dsh-tool-terminal/terminal-signal: - en: 36a7f502acf00049 - zh: 00b4567b534eb7b2 + en: 0191964f5be70426 + zh: 501c2769633a2a0b /tool-schema-catalog/deepseek-ai-dsh-tool-goal: en: 74fff3741e7c13de zh: 74fff3741e7c13de @@ -190,13 +190,13 @@ zh: 32982123b1f0e5bc /tool-schema-catalog/deepseek-ai-dsh-tool-lsp/lsp: en: 877f1dc0f69e2038 - zh: 2fa570020f87919c + zh: 5dda8001fb614315 /tool-schema-catalog/deepseek-ai-dsh-tool-ralph: en: 45bd083870e2c92d zh: 45bd083870e2c92d /tool-schema-catalog/deepseek-ai-dsh-tool-ralph/ralph: - en: a14c562ee26ddfd8 - zh: caa6d1acf95a8199 + en: e97552156225cf10 + zh: 9ea32b8485e950ec /tool-schema-catalog/deepseek-ai-dsh-tool-skill: en: 927a5ac0974b7642 zh: 927a5ac0974b7642 @@ -207,20 +207,20 @@ en: eac1dff4b6d9e195 zh: eac1dff4b6d9e195 /tool-schema-catalog/deepseek-ai-dsh-tool-session-query/session-event-read: - en: 6687105065ac3a5f - zh: 48b2f4d38536e4c7 + en: 285bab0bd6b50949 + zh: 9c839e05237a7226 /tool-schema-catalog/deepseek-ai-dsh-tool-session-query/session-event-search: - en: 41251b4cbe9af70d - zh: 8855a5f32b4720e5 + en: 5cd9f05b83f5797e + zh: dc6880566b0c5138 /tool-schema-catalog/deepseek-ai-dsh-tool-session-query/session-event-trace: - en: 14b5ec05fa8c0932 - zh: 3c85d44c13cc42c5 + en: de64e837459d7313 + zh: 60bfcb3691ab862c /tool-schema-catalog/deepseek-ai-dsh-tool-session-query/session-search: - en: 8263202c97568fc3 - zh: 92067337294c7a00 + en: c7bc2ee2892bf0e9 + zh: 1cd6a5d6f7494ca1 /tool-schema-catalog/deepseek-ai-dsh-tool-session-query/session-trace: - en: 22866623e207d94e - zh: 59f4f8eadcdba85c + en: 394175dfcf2bf489 + zh: f62bda32d3a775c7 /tool-schema-catalog/deepseek-ai-dsh-tool-subagent: en: 841abe0dd5913ea7 zh: 841abe0dd5913ea7 @@ -228,20 +228,20 @@ en: c89084b31ffccbce zh: d7b3b7000f2d2be9 /tool-schema-catalog/deepseek-ai-dsh-tool-subagent/subagent: - en: 55cbd29d70bcf3d7 - zh: 886a1b57446bd5e1 + en: 9270966e03a2a62b + zh: 0544ac614aed3144 /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control: en: ab82e130c3413f04 zh: ab82e130c3413f04 /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/interrupt-agent: - en: 0cec3f2520050839 - zh: 06735f086cea84f1 + en: 9190a32df5e10ddd + zh: 48e1b05956d63e26 /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/list-agents: en: ae22e8543d123c39 - zh: 4943a5c5980fc2d3 + zh: 0991a73ef49bcdea /tool-schema-catalog/deepseek-ai-dsh-tool-subagent-control/send-message: - en: 2ebdc450faef6446 - zh: 7e3cff35fedc6a32 + en: 69c1c0a80c24e3a4 + zh: 04f4ac5ec365aa63 /tool-schema-catalog/deepseek-ai-dsh-tool-jobs: en: 9be673b1604d0114 zh: 9be673b1604d0114 @@ -252,8 +252,8 @@ en: 0c37fb1fed979d72 zh: 5ea9d07f5a4a8a6c /tool-schema-catalog/deepseek-ai-dsh-tool-jobs/job-output: - en: f927cca09b283e21 - zh: 2acd9b36737f17c2 + en: 1e084359a8cd42b3 + zh: 3f6c245dbbb2b920 /tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team: en: 6b1106f417b50db6 zh: 6b1106f417b50db6 @@ -288,8 +288,20 @@ en: dea62d26fb18ff68 zh: dea62d26fb18ff68 /tool-schema-catalog/deepseek-ai-dsh-tool-todo/todo-write: - en: 8c64fe5f2e58a890 - zh: e3c780fc842aeb43 + en: f252137b940b38d1 + zh: 25bd9c6b1ce783d8 +/tool-schema-catalog/deepseek-ai-dsh-tool-working-directory: + en: 1bdc4627ca4fff29 + zh: 1bdc4627ca4fff29 +/tool-schema-catalog/deepseek-ai-dsh-tool-working-directory/working-directory: + en: ac1944f6035b8b50 + zh: f5fc005c13185211 +/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-worktree: + en: d5f5679abe86e316 + zh: d5f5679abe86e316 +/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-worktree/create-worktree: + en: 3cf89e31f011ae9b + zh: 28e78b4fae2d075b /tool-schema-catalog/deepseek-ai-dsh-tool-workflow: en: bb38a9d29f46563c zh: bb38a9d29f46563c diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 867eec6d33..143d1b29df 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -19,29 +19,31 @@ This table connects model-visible tool names to the plugin package and service s | `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`, `stagehand_extract`, `stagehand_navigate`, `stagehand_observe`, `stagehand_screenshot`, `stagehand_tabs` | `ctx.browserUse`, `ctx.agents`, `ctx.tools`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`, `ctx.userQuestions` | `tool/call`, `tool/result after an answer or timeout`, `late user/message` | - | ask_user_question keeps the original blocking behavior by default; set `mode: timed` to opt into a foreground timeout and pending result while the question remains answerable. In timed mode, `timeout: -1` keeps that call blocking indefinitely. | -| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`, `ctx.ptcRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. | +| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.workingDirectory (Agent executions)`, `ctx.tools`, `ctx.ptcRuntime (execution time)`, `ctx.systemPrompt` | `tool/call`, `one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`, `tool/result` | - | Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`, `ctx.systemPrompt`, `ctx.userQuestions (execution time, opportunistic)` | `tool/call`, `plan/mode inactive on an approved review`, `tool/result` | - | exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. | -| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. With a job registry composed every call registers with the generic `ctx.jobs` runtime as it starts, collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; without one, or with `enableRunInBackground: false`, the tool registers a foreground-only schema without the `run_in_background` parameter. | -| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. | -| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. | +| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`, `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The bash tool is the model-facing consumer of the bash executor seam. With a job registry composed every call registers with the generic `ctx.jobs` runtime as it starts, collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; without one, or with `enableRunInBackground: false`, the tool registers a foreground-only schema without the `run_in_background` parameter. | +| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`, `ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented after a successful final result`, `tool/result` | - | Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards. | +| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.workingDirectory`, `ctx.tools`, `ctx.shell`, `ctx.systemPrompt`, `ctx.shellEnv`, `ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`, `tool/result` | - | The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. | | `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect_list`, `cordis_inspect_query` | `ctx.tools`, `ctx.cordisInspect` | `tool/call`, `tool/result` | - | Creator mode provides two read-only runtime inspection tools. The Cordis host runner supplies the inspection registry; Client queries require a connected page. Author persistent changes as bundles and install them with plugin_manager. | -| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. | -| `@deepseek-ai/dsh-tool-pwsh-persistent` | `pwsh` | `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent pwsh tool, the Windows counterpart of the persistent bash tool; deployment composition supplies a pwsh-dialect PTY backend and may override the model-facing environment description. | +| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.workingDirectory`, `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. | +| `@deepseek-ai/dsh-tool-pwsh-persistent` | `pwsh` | `ctx.workingDirectory`, `ctx.tools`, `ctx.terminals`, `an owning Agent at execution time` | `tool/call`, `PTY shell state`, `tool/result` | - | One owner-isolated persistent pwsh tool, the Windows counterpart of the persistent bash tool; deployment composition supplies a pwsh-dialect PTY backend and may override the model-facing environment description. | | `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`, `ctx.fs` | `tool/call`, `fs/observed after view presence/absence, edit absence, or successful mutation`, `tool/result` | - | Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal API. | -| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `read_image`, `write` | `ctx.tools`, `ctx.fs`, `ctx.systemPrompt`, `ctx.attachments (image-tool registration)`, `ctx.llm + an image-capable route (image-tool execution)` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after read presence/absence or successful file operation`, `durable attachment (read_image)`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The image tool is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input. | -| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.tools`, `ctx.subprocess`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. | -| `@deepseek-ai/dsh-tool-terminal` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.tools`, `ctx.terminals`, `ctx.systemPrompt`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. | +| `@deepseek-ai/dsh-tool-fs` | `edit`, `read`, `read_image`, `write` | `ctx.workingDirectory`, `ctx.tools`, `ctx.fs`, `ctx.systemPrompt`, `ctx.attachments (image-tool registration)`, `ctx.llm + an image-capable route (image-tool execution)` | `tool/call`, `fs/write-intent or fs/edit-intent for mutations`, `fs/observed after read presence/absence or successful file operation`, `durable attachment (read_image)`, `tool/result` | - | The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. The image tool is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input. | +| `@deepseek-ai/dsh-tool-fs-search` | `glob`, `grep` | `ctx.workingDirectory`, `ctx.tools`, `ctx.subprocess`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. | +| `@deepseek-ai/dsh-tool-terminal` | `terminal_close`, `terminal_list`, `terminal_open`, `terminal_read`, `terminal_send`, `terminal_signal` | `ctx.workingDirectory`, `ctx.tools`, `ctx.terminals`, `ctx.systemPrompt`, `ctx.jobs at call time for run_in_background` | `tool/call`, `tool/result` | - | The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. | | `@deepseek-ai/dsh-tool-goal` | `create_goal`, `get_goal`, `update_goal` | `ctx.tools`, `ctx.agents`, `ctx.goals`, `ctx.systemPrompt`, `a calling Agent in an authorized open turn` | `tool/call`, `goal/change for mutations`, `tool/result` | - | create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. | | `@deepseek-ai/dsh-tool-schedule` | `schedule_create`, `schedule_delete`, `schedule_list`, `schedule_update` | `ctx.tools`, `ctx.schedule` | `tool/call`, `Schedule storage domain create, update, or delete`, `tool/result` | - | A preset or Agent scope mounts this package; the preset decides which agents receive the four management tools. Each call acts on the calling Agent's Session. Accepts after_seconds, explicit absolute at, bounded fixed-rate every_seconds, daily and weekly local times in an explicit IANA zone, and cron as a five-field expression. Management uses the Host storage domain; due messages resume the original Session. | -| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.tools`, `ctx.lsp`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema. | +| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.workingDirectory`, `ctx.tools`, `ctx.lsp`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema. | | `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`, `ctx.workflowEngine`, `ctx.subagents`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents every fresh round)` | `tool/call`, `tool/result`, `workflow and child session events during execution` | - | A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. | -| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - | +| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.workingDirectory`, `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - | | `@deepseek-ai/dsh-tool-session-query` | `session_event_read`, `session_event_search`, `session_event_trace`, `session_search`, `session_trace` | `ctx.tools`, `ctx.systemPrompt`, `ctx.sessionQuery`, `a calling Agent for workspace authority` | `tool/call`, `tool/result` | - | The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. | -| `@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-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 selects its backend through `provider` and reads model-selection settings through `modelSelectionSettings`. Every delegation returns a child id promptly; local children accept follow-ups, while external backends report their single execution automatically. | +| `@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 discovery, interruption, and local-message tools over managed 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 and PTY sends 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` | `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-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | Reads or changes the calling Session directory. Existing shells and processes retain their own directories; origin metadata and permission roots stay fixed. | +| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | Explicit experimental composition only. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout. | | `@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-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `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. | @@ -74,7 +76,7 @@ List plugins or bundles in the current profile, enable or disable them, install }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", @@ -563,7 +565,7 @@ Execute a TypeScript program against the available tools. Takes two required arg Source: [`packages/core/tools/src/ptc.ts`](../packages/core/tools/src/ptc.ts) -Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` / `mode: both` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. +Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: ptc` (see the PTC mode Agent Note). Under `ptc` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. @@ -1141,7 +1143,7 @@ Close one persistent terminal and wait until its captured owned process tree is } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_list` @@ -1154,7 +1156,7 @@ List persistent terminal sessions owned by the current agent. } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_open` @@ -1174,7 +1176,7 @@ Create a persistent, owner-isolated terminal session from a registered backend t }, "cwd": { "type": "string", - "description": "Initial working directory. Defaults to the deployment workspace root." + "description": "Initial working directory, relative to the Session current directory when not absolute." } }, "required": [ @@ -1183,7 +1185,7 @@ Create a persistent, owner-isolated terminal session from a registered backend t } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_read` @@ -1212,7 +1214,7 @@ Read a bounded page of retained output from a persistent terminal without sendin } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_send` @@ -1246,7 +1248,7 @@ Send text to a persistent terminal. By default Enter is submitted and the call w } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_signal` @@ -1279,7 +1281,7 @@ Send an allowed signal to the current foreground process group of a persistent t } ``` -Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +Source: [`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. @@ -1745,7 +1747,7 @@ Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only } ``` -Source: [`packages/workflow/tool-ralph/src/index.ts`](../packages/workflow/tool-ralph/src/index.ts) +Source: [`packages/experimental/tool-ralph/src/index.ts`](../packages/experimental/tool-ralph/src/index.ts) A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. @@ -1809,7 +1811,7 @@ Read one full unabridged event and optional neighboring raw-event summaries from } ``` -Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +Source: [`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_event_search` @@ -1869,7 +1871,7 @@ Search prior events in one authorized session; the current session excludes the } ``` -Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +Source: [`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_event_trace` @@ -1894,7 +1896,7 @@ Read every direct replacement and relationship to a cited source event for one e } ``` -Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +Source: [`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_search` @@ -1987,7 +1989,7 @@ Search prior sessions in the caller workspace and return the strongest matching } ``` -Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +Source: [`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_trace` @@ -2005,7 +2007,7 @@ Read the authorized session lineage around one session, including complete visib } ``` -Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +Source: [`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. @@ -2037,12 +2039,16 @@ Source: [`packages/subagent/tool-subagent/src/list-models.ts`](../packages/subag ### `subagent` -Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. This call waits for the result by default. +Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. This tool starts an independently managed subagent and immediately returns its id. The runtime notifies you when it finishes. The child reports results with `send_message`; use `send_message` to steer it while running or continue its conversation after it finishes. ```json { "type": "object", "properties": { + "cwd": { + "type": "string", + "description": "Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent." + }, "description": { "type": "string", "description": "A short (3-5 word) description of the delegated task, for display." @@ -2050,10 +2056,6 @@ Delegate a self-contained task to a subagent (a separate agent that works in its "prompt": { "type": "string", "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." - }, - "run_in_background": { - "type": "boolean", - "description": "Run as a background job and return its id (collect with job_output, stop with job_kill). Defaults to false." } }, "required": [ @@ -2065,7 +2067,7 @@ Delegate a self-contained task to a subagent (a separate agent that works in its Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts) -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`. +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 selects its backend through `provider` and reads model-selection settings through `modelSelectionSettings`. Every delegation returns a child id promptly; local children accept follow-ups, while external backends report their single execution automatically. @@ -2073,7 +2075,7 @@ The registered delegation name is the load-time `toolName` config (default `suba ### `interrupt_agent` -Ask a subagent to stop its current work. This call returns without waiting for it to stop. You can continue a direct child's conversation later with send_message. Subagents it started will keep running. +Ask a subagent to stop its current work. This call returns without waiting for it to stop. You can continue a local direct child's conversation later with send_message. External executions stop permanently and cannot receive follow-ups. Subagents it started will keep running. ```json { @@ -2140,7 +2142,7 @@ Send a message to an agent. A working agent receives it at its next step; an idl Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) -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). +The globally named discovery, interruption, and local-message tools over managed 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). @@ -2213,7 +2215,7 @@ Read a background job: output since the previous read for stream jobs, or the re Source: [`packages/jobs/tool-jobs/src/index.ts`](../packages/jobs/tool-jobs/src/index.ts) -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()`. +The kind-agnostic background-job controller: background bash commands and PTY sends are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. @@ -2555,6 +2557,58 @@ Source: [`packages/todo/tool-todo/src/index.ts`](../packages/todo/tool-todo/src/ 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-working-directory` + +### `working_directory` + +Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories. + +```json +{ + "type": "object", + "properties": { + "cd": { + "type": "string", + "description": "Existing directory to enter. Omit to read the current directory." + } + } +} +``` + +Source: [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) + +Reads or changes the calling Session directory. Existing shells and processes retain their own directories; origin metadata and permission roots stay fixed. + + + +## `@deepseek-ai/dsh-experimental-tool-worktree` + +### `create_worktree` + +Create a new Git branch and worktree from a local commit, branch, or tag, then change this session's working directory to it. Defaults to HEAD and a generated name. Uncommitted files stay in the source checkout. Existing names fail. Use working_directory with cd to leave; the checkout and branch remain. + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "New branch name, also used as the checkout directory. Omit to generate a unique name." + }, + "from": { + "type": "string", + "description": "Local commit, branch, or tag to start from. Defaults to HEAD; no fetch is performed." + } + } +} +``` + +Source: [`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) + +Explicit experimental composition only. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout. + ## `@deepseek-ai/dsh-tool-workflow` diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 17fd5d6caa..23ef011dc5 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -23,29 +23,31 @@ | `@deepseek-ai/dsh-mcp-resources` | `list_mcp_resource_templates`, `list_mcp_resources`, `read_mcp_resource` | `ctx.tools`, `ctx.mcpResources` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-experimental-browser-use-stagehand-native` | `stagehand_act`、`stagehand_extract`、`stagehand_navigate`、`stagehand_observe`、`stagehand_screenshot`、`stagehand_tabs` | `ctx.browserUse`、`ctx.agents`、`ctx.tools`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | - | | `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question` | `ctx.tools`、`ctx.userQuestions` | `tool/call`、`tool/result after an answer or timeout`、`late user/message` | - | ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才启用前台超时与 pending 结果,同时问题仍可回答;timed 模式内 `timeout: -1` 让本次调用无限期阻塞。 | -| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.tools`、`ctx.ptcRuntime (execution time)`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | 在 `mode: ptc`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `ptc` 下,它是注册表对协议格式(wire format)的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 | +| `@deepseek-ai/dsh-tools` | `run_code` | `ctx.workingDirectory (Agent executions)`、`ctx.tools`、`ctx.ptcRuntime (execution time)`、`ctx.systemPrompt` | `tool/call`、`one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call`、`tool/result` | - | 在 `mode: ptc` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `ptc` 下,它是注册表对协议格式(wire format)的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 | | `@deepseek-ai/dsh-plan-mode` | `exit_plan_mode` | `ctx.tools`、`ctx.systemPrompt`、`ctx.userQuestions (execution time, opportunistic)` | `tool/call`、`plan/mode inactive on an approved review`、`tool/result` | - | 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 | -| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。组合中有 job 注册表时,每次调用一启动就注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;没有注册表或 `enableRunInBackground: false` 时,工具注册不带 `run_in_background` 参数的纯前台 schema。 | -| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 | -| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`。 | +| `@deepseek-ai/dsh-tool-bash` | `bash` | `ctx.workingDirectory`、`ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | bash 工具是 bash 执行器 seam 面向模型的消费方。组合中有 job 注册表时,每次调用一启动就注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;没有注册表或 `enableRunInBackground: false` 时,工具注册不带 `run_in_background` 参数的纯前台 schema。 | +| `@deepseek-ai/dsh-tool-present` | `present` | `ctx.workingDirectory`、`ctx.tools`, `ctx.fs`, `ctx.sessionProjections` | `tool/call`, `deliverables/presented 在成功的最终结果之后`, `tool/result` | - | 交付归调用方 Session 所有;Web ui-deliverables 提供源文件打开与卡片。 | +| `@deepseek-ai/dsh-tool-pwsh` | `pwsh` | `ctx.workingDirectory`、`ctx.tools`、`ctx.shell`、`ctx.systemPrompt`、`ctx.shellEnv`、`ctx.jobs for run_in_background and the job-backed foreground path` | `tool/call`、`tool/result` | - | pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`。 | | `@deepseek-ai/dsh-tool-cordis` | `cordis_inspect_list`, `cordis_inspect_query` | `ctx.tools`, `ctx.cordisInspect` | `tool/call`, `tool/result` | - | 创造模式提供两个只读运行时检查工具。Cordis host runner 提供检查注册表;Client 查询需要已连接页面。持久化变更编写为组合包,再通过 plugin_manager 安装。 | -| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.tools`、`ctx.terminals`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 | -| `@deepseek-ai/dsh-tool-pwsh-persistent` | `pwsh` | `ctx.tools`、`ctx.terminals`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 pwsh 工具,持久 bash 工具的 Windows 对应物;部署组合提供 pwsh 方言的 PTY 后端,并可覆盖面向模型的环境描述。 | +| `@deepseek-ai/dsh-tool-bash-persistent` | `bash` | `ctx.workingDirectory`、`ctx.tools`、`ctx.terminals`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 | +| `@deepseek-ai/dsh-tool-pwsh-persistent` | `pwsh` | `ctx.workingDirectory`、`ctx.tools`、`ctx.terminals`、`an owning Agent at execution time` | `tool/call`、`PTY shell state`、`tool/result` | - | 一个按所有者隔离的持久 pwsh 工具,持久 bash 工具的 Windows 对应物;部署组合提供 pwsh 方言的 PTY 后端,并可覆盖面向模型的环境描述。 | | `@deepseek-ai/dsh-tool-str-replace-editor` | `str_replace_editor` | `ctx.tools`、`ctx.fs` | `tool/call`、`fs/observed after view presence/absence, edit absence, or successful mutation`、`tool/result` | - | 基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。 | -| `@deepseek-ai/dsh-tool-fs` | `edit`、`read`、`read_image`、`write` | `ctx.tools`、`ctx.fs`、`ctx.systemPrompt`、`ctx.attachments (image-tool registration)`、`ctx.llm + an image-capable route (image-tool execution)` | `tool/call`、`fs/write-intent or fs/edit-intent for mutations`、`fs/observed after read presence/absence or successful file operation`、`durable attachment (read_image)`、`tool/result` | - | 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-observation-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments` 时图片工具不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图片输入,否则拒绝。 | -| `@deepseek-ai/dsh-tool-fs-search` | `glob`、`grep` | `ctx.tools`、`ctx.subprocess`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 | -| `@deepseek-ai/dsh-tool-terminal` | `terminal_close`、`terminal_list`、`terminal_open`、`terminal_read`、`terminal_send`、`terminal_signal` | `ctx.tools`、`ctx.terminals`、`ctx.systemPrompt`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.jobs`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 | +| `@deepseek-ai/dsh-tool-fs` | `edit`、`read`、`read_image`、`write` | `ctx.workingDirectory`、`ctx.tools`、`ctx.fs`、`ctx.systemPrompt`、`ctx.attachments (image-tool registration)`、`ctx.llm + an image-capable route (image-tool execution)` | `tool/call`、`fs/write-intent or fs/edit-intent for mutations`、`fs/observed after read presence/absence or successful file operation`、`durable attachment (read_image)`、`tool/result` | - | 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-observation-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments` 时图片工具不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图片输入,否则拒绝。 | +| `@deepseek-ai/dsh-tool-fs-search` | `glob`、`grep` | `ctx.workingDirectory`、`ctx.tools`、`ctx.subprocess`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 | +| `@deepseek-ai/dsh-tool-terminal` | `terminal_close`、`terminal_list`、`terminal_open`、`terminal_read`、`terminal_send`、`terminal_signal` | `ctx.workingDirectory`、`ctx.tools`、`ctx.terminals`、`ctx.systemPrompt`、`ctx.jobs at call time for run_in_background` | `tool/call`、`tool/result` | - | 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.jobs`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 | | `@deepseek-ai/dsh-tool-goal` | `create_goal`、`get_goal`、`update_goal` | `ctx.tools`、`ctx.agents`、`ctx.goals`、`ctx.systemPrompt`、`a calling Agent in an authorized open turn` | `tool/call`、`goal/change for mutations`、`tool/result` | - | create、edit、pause 和 resume 要求直接来自人类的根权限;complete 和 blocked 也接受确切的当前 Goal Round。blocked 的默认下限是 3 个获准的 Round。 | | `@deepseek-ai/dsh-tool-schedule` | `schedule_create`、`schedule_delete`、`schedule_list`、`schedule_update` | `ctx.tools`、`ctx.schedule` | `tool/call`、Schedule storage domain 创建、更新或删除、`tool/result` | - | preset 或 Agent scope 挂载本包;由 preset 决定哪些 agent 获得这四个管理工具。每次调用都作用于调用方 Agent 的 Session。接受 after_seconds、显式绝对 at、有界固定速率 every_seconds、带显式 IANA 时区的每日与每周本地时间,以及作为五字段表达式的 cron。管理使用宿主 storage domain;到期消息会恢复原 Session。 | -| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.tools`、`ctx.lsp`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,因此其模型可见 schema 在更换提供方时保持稳定。运行时要求已注册提供方,例如 `@deepseek-ai/dsh-lsp-stdio`;如果没有提供方,查询会返回结构化 `LSP_UNAVAILABLE` 错误,而不会改变 schema。 | +| `@deepseek-ai/dsh-tool-lsp` | `lsp` | `ctx.workingDirectory`、`ctx.tools`、`ctx.lsp`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,因此其模型可见 schema 在更换提供方时保持稳定。运行时要求已注册提供方,例如 `@deepseek-ai/dsh-lsp-stdio`;如果没有提供方,查询会返回结构化 `LSP_UNAVAILABLE` 错误,而不会改变 schema。 | | `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`、`ctx.workflowEngine`、`ctx.subagents`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents every fresh round)` | `tool/call`、`tool/result`、`workflow and child session events during execution` | - | 固定的前台工作流会在每个 Round 启动一个全新的结构化子级;模型只能选择不可变目标和可选的 Round 上限。 | -| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`、`ctx.agents`、`ctx.skills` | `tool/call`、`tool/result`、`user/message replacement catalogs via agent.inject()` | - | - | +| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.workingDirectory`、`ctx.tools`、`ctx.agents`、`ctx.skills` | `tool/call`、`tool/result`、`user/message replacement catalogs via agent.inject()` | - | - | | `@deepseek-ai/dsh-tool-session-query` | `session_event_read`、`session_event_search`、`session_event_trace`、`session_search`、`session_trace` | `ctx.tools`、`ctx.systemPrompt`、`ctx.sessionQuery`、`a calling Agent for workspace authority` | `tool/call`、`tool/result` | - | 这 5 个只读工具会隐藏提供方游标,并根据不可变的调用 agent 会话为每个结果授权。该包需要选择启用;需要强制截止时间或限制行内输出的组合还会挂载通用超时或 spill 策略。 | -| `@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-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` 始终使用固定路由。每个实例通过 `provider` 选择后端,通过 `modelSelectionSettings` 读取模型选择设置。每次委派都会立即返回子代理 id;本地子代理接受后续消息,外部后端自动汇报单次执行结果。 | +| `@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 发送 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 | | `@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-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | 读取或改变调用方 Session 的目录。已有 shell 和进程保留自己的目录;原始元数据和权限根目录保持不变。 | +| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | 仅通过显式实验组合加载。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。 | | `@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-workspace-dependencies` | `load_workspace_dependencies` | `ctx.tools` | `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 在更换后端时保持稳定。 | @@ -78,7 +80,7 @@ }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", @@ -567,7 +569,7 @@ ask_user_question 默认保持原有阻塞行为;设置 `mode: timed` 后才 来源:[`packages/core/tools/src/ptc.ts`](../packages/core/tools/src/ptc.ts) -在 `mode: ptc`/`mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `ptc` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 +在 `mode: ptc` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 PTC mode Agent Note)。在 `ptc` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 @@ -1124,7 +1126,7 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费 glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 - + ## `@deepseek-ai/dsh-tool-terminal` @@ -1147,7 +1149,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_list` @@ -1160,7 +1162,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_open` @@ -1180,7 +1182,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn }, "cwd": { "type": "string", - "description": "Initial working directory. Defaults to the deployment workspace root." + "description": "Initial working directory, relative to the Session current directory when not absolute." } }, "required": [ @@ -1189,7 +1191,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_read` @@ -1218,7 +1220,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_send` @@ -1252,7 +1254,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) ### `terminal_signal` @@ -1285,7 +1287,7 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn } ``` -来源:[`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/tool-terminal/src/index.ts) +来源:[`packages/experimental/tool-terminal/src/index.ts`](../packages/experimental/tool-terminal/src/index.ts) 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.jobs`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 @@ -1724,7 +1726,7 @@ preset 或 Agent scope 挂载本包;由 preset 决定哪些 agent 获得这四 lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,因此其模型可见 schema 在更换提供方时保持稳定。运行时要求已注册提供方,例如 `@deepseek-ai/dsh-lsp-stdio`;如果没有提供方,查询会返回结构化 `LSP_UNAVAILABLE` 错误,而不会改变 schema。 - + ## `@deepseek-ai/dsh-tool-ralph` @@ -1751,7 +1753,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/workflow/tool-ralph/src/index.ts`](../packages/workflow/tool-ralph/src/index.ts) +来源:[`packages/experimental/tool-ralph/src/index.ts`](../packages/experimental/tool-ralph/src/index.ts) 固定的前台工作流会在每个 Round 启动一个全新的结构化子级;模型只能选择不可变目标和可选的 Round 上限。 @@ -1815,7 +1817,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +来源:[`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_event_search` @@ -1875,7 +1877,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +来源:[`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_event_trace` @@ -1900,7 +1902,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +来源:[`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_search` @@ -1993,7 +1995,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +来源:[`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) ### `session_trace` @@ -2011,7 +2013,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, } ``` -来源:[`packages/session-query/tool-session-query/src/index.ts`](../packages/session-query/tool-session-query/src/index.ts) +来源:[`packages/experimental/tool-session-query/src/index.ts`](../packages/experimental/tool-session-query/src/index.ts) 这 5 个只读工具会隐藏提供方游标,并根据不可变的调用 agent 会话为每个结果授权。该包需要选择启用;需要强制截止时间或限制行内输出的组合还会挂载通用超时或 spill 策略。 @@ -2043,12 +2045,16 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, ### `subagent` -将一项自包含任务委派给 subagent(在自身上下文中工作的独立 agent),用它卸载聚焦且独立的工作,例如研究、限定范围的实现或分析,以免消耗当前对话的上下文。subagent 会返回结果,但不会返回中间步骤。此调用默认等待结果。 +将一项自包含任务委派给 subagent(在自身上下文中工作的独立 agent),用它卸载聚焦且独立的工作,例如研究、限定范围的实现或分析,以免消耗当前对话的上下文。subagent 会返回结果,但不会返回中间步骤。请提供完整、独立的提示词,因为它看不到当前对话。此工具启动独立管理的 subagent 并立即返回其 id。运行时会在任务结束时通知你。子代理通过 `send_message` 汇报结果;你可以使用 `send_message` 在运行时引导它,或在结束后继续对话。 ```json { "type": "object", "properties": { + "cwd": { + "type": "string", + "description": "Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent." + }, "description": { "type": "string", "description": "A short (3-5 word) description of the delegated task, for display." @@ -2056,10 +2062,6 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, "prompt": { "type": "string", "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." - }, - "run_in_background": { - "type": "boolean", - "description": "Run as a background job and return its id (collect with job_output, stop with job_kill). Defaults to false." } }, "required": [ @@ -2071,7 +2073,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, 来源:[`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts) -注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制是否读取模型选择设置及其后台行为。 +注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `provider` 选择后端,通过 `modelSelectionSettings` 读取模型选择设置。每次委派都会立即返回子代理 id;本地子代理接受后续消息,外部后端自动汇报单次执行结果。 @@ -2079,7 +2081,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, ### `interrupt_agent` -请 subagent 停止当前工作。此调用不等待其停止即返回。之后可以用 send_message 继续与直接子级的对话。它启动的 subagent 会继续运行。 +根据 agent id 请求取消后台 agent 的当前轮次。目标可以是你的直接子级,也可以是在你下方创建的更深层 agent。对于本地 agent,只有当前轮次会停止:已经排队发给该 agent 的消息会一直搁置到后续的 send_message;它启动的 agent 会继续运行;该 agent 本身仍可接受后续操作。外部执行会永久停止,无法接收后续消息。停止请求被接受后,此调用立即返回,因此目标可能还会短暂运行;中断一个已经完成的 agent 是可接受的空操作。 ```json { @@ -2100,7 +2102,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, ### `list_agents` -列出你启动的 subagent 及其 id、标签和状态。running 表示正在工作;inactive 表示当前未在工作。subagent 完成时你会收到通知,无需反复查看状态。使用 send_message 继续对话。 +按持久 ID 和标签列出调用方目录中可继续的直接子代理。使用此工具回忆已启动的子代理,不要轮询完成状态;完成时会收到通知。状态来自在线注册表:running 表示当前正在工作;inactive 表示当前没有执行轮次,无论子代理已加载还是需要恢复,并不表示任务完成、成功、失败或正在等待其他代理。`send_message` 可在最近的步骤边界引导正在运行的子代理,或启动、恢复非活跃子代理的轮次;任何状态下的直接子级都可作为 `send_message` 的候选目标。列表快照不保证投递成功,`send_message` 仍进行权威检查,且可能失败。 ```json { @@ -2122,7 +2124,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, ### `send_message` -向某个 agent 发送消息。工作中的 agent 会在下一个 step 收到消息;空闲的 agent 会以该消息开始新一轮。返回投递确认,而不是该 agent 的答案。 +根据 agent id 向直接可继续 child 发送消息。如果你是驻留的可继续 child,也可以把自己的直接 parent 作为目标。如果目标仍在工作,消息会 steer 其最近的 step;如果目标处于 idle,消息会启动一个轮次。此调用不会返回该 agent 的答案,只会确认消息已投递。调用失败表示消息**未**投递。 ```json { @@ -2146,7 +2148,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, 来源:[`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) -这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 +这些是用于发现、中断托管 subagent 和向本地子代理发送消息的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 @@ -2219,7 +2221,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, 来源:[`packages/jobs/tool-jobs/src/index.ts`](../packages/jobs/tool-jobs/src/index.ts) -与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 +与任务种类无关的后台任务控制器:后台 bash 命令与 PTY 发送 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 @@ -2562,6 +2564,58 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后, todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 + + +## `@deepseek-ai/dsh-tool-working-directory` + +### `working_directory` + +读取当前工作目录,或通过 cd 改变它。相对路径使用当前目录。已有 shell 和运行中的进程保留自己的目录。 + +```json +{ + "type": "object", + "properties": { + "cd": { + "type": "string", + "description": "Existing directory to enter. Omit to read the current directory." + } + } +} +``` + +来源:[`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) + +读取或改变调用方 Session 的目录。已有 shell 和进程保留自己的目录;原始元数据和权限根目录保持不变。 + + + +## `@deepseek-ai/dsh-experimental-tool-worktree` + +### `create_worktree` + +从本地提交、分支或标签创建新的 Git 分支与工作树,再将本 Session 的工作目录切换到该位置。默认使用 HEAD 和自动生成的名称。未提交文件保留在源检出目录中。名称已存在时失败。使用 working_directory 的 cd 参数离开;检出目录与分支仍然保留。 + +```json +{ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "New branch name, also used as the checkout directory. Omit to generate a unique name." + }, + "from": { + "type": "string", + "description": "Local commit, branch, or tag to start from. Defaults to HEAD; no fetch is performed." + } + } +} +``` + +来源:[`packages/experimental/tool-worktree/src/index.ts`](../packages/experimental/tool-worktree/src/index.ts) + +仅通过显式实验组合加载。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。 + ## `@deepseek-ai/dsh-tool-workflow` diff --git a/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.i18n.yaml b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.i18n.yaml new file mode 100644 index 0000000000..b0499e2ffe --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.md +/: + en: 26d3106bedaf5410 + zh: 0414d05784bcfc3e +/account-sign-in-error-codes: + en: 8d92729fd1c381f0 + zh: a526d1a54a583d2b +/account-sign-in-error-codes/change: + en: f1bace911bbf5247 + zh: bee091aef4b4a0e5 +/account-sign-in-error-codes/migration: + en: be6e29a39279aa38 + zh: 29c34f23f3b172fa diff --git a/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.md b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.md new file mode 100644 index 0000000000..9c6fad0bc4 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "Account sign-in transport failures now report no-response instead of network." +--- + +# Account sign-in error codes + +English | [中文](guide.zh.md) + +## Change + +`SignInErrorCode` adds `no-response`. Account state returned by `account/getState`, `account/startSignIn`, and `account/watch` reports this code when fetch fails before returning a Response, including request timeouts. These failures previously reported `network`. HTTP response errors still report `network`; authorization-attempt expiry still reports `expired`. + +## Migration + +1. Add `no-response` to validators and exhaustive handlers for `SignInAttemptView.errorCode` in clients of the account API or `@deepseek-ai/dsh-deepseek-account`. +2. Show network troubleshooting for `no-response`. Keep generic failure handling for `network`, which also covers HTTP and account-detail retrieval failures. +3. Confirm a failed login with `errorCode: 'no-response'` is accepted by the client and offers retry, while an HTTP error response retains generic failure handling. diff --git a/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.zh.md b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.zh.md new file mode 100644 index 0000000000..8a441004c0 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/account-sign-in-errors/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "账号登录传输失败现在报告 no-response,而非 network。" +--- + +# 账号登录错误码 + +[English](guide.md) | 中文 + +## 变更 + +`SignInErrorCode` 新增 `no-response`。当 fetch 在返回 Response 前失败(包括单次请求超时)时,`account/getState`、`account/startSignIn` 和 `account/watch` 返回的账号状态报告此代码。这些失败此前报告 `network`。HTTP 响应错误仍报告 `network`;授权尝试到期仍报告 `expired`。 + +## 迁移 + +1. 在账号 API 或 `@deepseek-ai/dsh-deepseek-account` 的客户端中,为 `SignInAttemptView.errorCode` 的校验器和穷尽处理逻辑添加 `no-response`。 +2. 对 `no-response` 提示检查网络。对 `network` 保留通用失败处理;该代码也涵盖 HTTP 和账号详情获取失败。 +3. 确认客户端接受 `errorCode: 'no-response'` 的登录失败状态并提供重试,同时 HTTP 错误响应仍使用通用失败处理。 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml new file mode 100644 index 0000000000..18689b57d4 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md +/: + en: 3cb412fe69354a6b + zh: fbd2dd7abcd08ed7 +/runtime-invariant-plugins-are-removed: + en: 573fd5239a856cab + zh: edb2683d049a81b7 +/runtime-invariant-plugins-are-removed/change: + en: 58c246cfc02ab19d + zh: 4826b8cbb34e1363 +/runtime-invariant-plugins-are-removed/migration: + en: b42a35a30b792f84 + zh: 0ddbccc4b93410a3 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md new file mode 100644 index 0000000000..e42dc95ac6 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.md @@ -0,0 +1,23 @@ +--- +kind: upgrade-guide +description: "The runtime invariant registry package and every package's `./invariant` subpath export are removed." +--- + +# Runtime invariant plugins are removed + +English | [中文](guide.zh.md) + +## Change + +In v0.2.0-rc.2, `@deepseek-ai/dsh-invariants` provided the `ctx.invariants` service, and workspace packages such as `@deepseek-ai/dsh-session`, `@deepseek-ai/dsh-agent`, `@deepseek-ai/dsh-scope`, and `@deepseek-ai/dsh-agent-loop` published `./invariant` companion plugins that registered checks with it. The `sdk-minimal` profile mounted five of these rows with ids `invariants`, `session-invariant`, `agent-invariant`, `scope-invariant`, and `agent-loop-invariant`. + +The next release no longer publishes `@deepseek-ai/dsh-invariants`, `InvariantRegistry`, `InvariantInstaller`, `InvariantFailure`, `InvariantError`, or any `/invariant` subpath. The `sdk-minimal` profile no longer contains the five rows. The `credentials/reference-updated`, `credentials/record-updated`, `authorization/settled`, and `llm/adapters-updated` emitters contain and log every listener failure, including errors with `code: 'INVARIANT'`, instead of rethrowing them. + +A `cordis.yml`, patch, or overlay that names a removed module fails to load it. A patch that targets one of the five `sdk-minimal` ids logs `patch: entry not found`. TypeScript code that imports a removed module or symbol no longer compiles. + +## Migration + +1. Delete rows whose `name` is `@deepseek-ai/dsh-invariants` or ends in `/invariant` from `cordis.yml`, `$DSH_HOME/profiles//cordis.patch.yml`, and `--patch` overlays. +2. Delete patch entries that target the ids `invariants`, `session-invariant`, `agent-invariant`, `scope-invariant`, or `agent-loop-invariant`. +3. Remove `@deepseek-ai/dsh-invariants` from `package.json` dependencies and delete imports of it and of `/invariant` subpaths. A plugin that relied on a rethrown `INVARIANT` failure from one of the four emitters must report the failure through its own channel. +4. Confirm: `dsh` starts the profile without a module-not-found error or `patch: entry ... not found` warning, and `tsc` reports no missing module or export. diff --git a/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md new file mode 100644 index 0000000000..39e076d1bf --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/remove-runtime-invariants/guide.zh.md @@ -0,0 +1,23 @@ +--- +kind: upgrade-guide +description: "运行时不变式注册表包和每个包的 `./invariant` 子路径导出均被移除。" +--- + +# 运行时不变式插件被移除 + +[English](guide.md) | 中文 + +## 变更 + +在 v0.2.0-rc.2 中,`@deepseek-ai/dsh-invariants` 提供 `ctx.invariants` 服务,`@deepseek-ai/dsh-session`、`@deepseek-ai/dsh-agent`、`@deepseek-ai/dsh-scope`、`@deepseek-ai/dsh-agent-loop` 等 workspace 包发布向该服务注册检查的 `./invariant` 配套插件。`sdk-minimal` profile 挂载其中五行,id 为 `invariants`、`session-invariant`、`agent-invariant`、`scope-invariant` 和 `agent-loop-invariant`。 + +下一版本不再发布 `@deepseek-ai/dsh-invariants`、`InvariantRegistry`、`InvariantInstaller`、`InvariantFailure`、`InvariantError`,也不再发布任何 `/invariant` 子路径。`sdk-minimal` profile 不再包含这五行。`credentials/reference-updated`、`credentials/record-updated`、`authorization/settled` 和 `llm/adapters-updated` 的发出方会隔离并记录每个监听器失败,包括 `code: 'INVARIANT'` 的错误,不再重新抛出。 + +引用已移除模块的 `cordis.yml`、patch 或 overlay 无法加载该模块。指向上述五个 `sdk-minimal` id 之一的 patch 会记录 `patch: entry not found`。导入已移除模块或符号的 TypeScript 代码无法编译。 + +## 迁移 + +1. 从 `cordis.yml`、`$DSH_HOME/profiles//cordis.patch.yml` 和 `--patch` overlay 中删除 `name` 为 `@deepseek-ai/dsh-invariants` 或以 `/invariant` 结尾的行。 +2. 删除指向 id `invariants`、`session-invariant`、`agent-invariant`、`scope-invariant` 或 `agent-loop-invariant` 的 patch 条目。 +3. 从 `package.json` 依赖中移除 `@deepseek-ai/dsh-invariants`,并删除对它和 `/invariant` 子路径的导入。依赖上述四个发出方重新抛出 `INVARIANT` 失败的插件,必须通过自己的通道报告该失败。 +4. 确认:`dsh` 启动该 profile 时没有模块未找到错误或 `patch: entry ... not found` 警告,`tsc` 不再报告缺失的模块或导出。 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.i18n.yaml b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.i18n.yaml new file mode 100644 index 0000000000..bc266b2e8c --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md +/: + en: 2b61d3edeb5cb9b3 + zh: c8c19ac891de1483 +/automation-tasks-bundle-removed: + en: fb9d856d884313d9 + zh: 6fa0d728f26c5f8e +/automation-tasks-bundle-removed/change: + en: 0a35ea93af06452f + zh: 094dd5f6422bab8d +/automation-tasks-bundle-removed/migration: + en: 04278f963f780cad + zh: f8e49107b3a5df07 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md new file mode 100644 index 0000000000..d95d2b3b16 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md @@ -0,0 +1,22 @@ +--- +kind: upgrade-guide +description: "The Automation tasks optional bundle is removed; the Web composition mounts Schedule itself." +--- + +# Automation tasks bundle removed + +English | [中文](guide.zh.md) + +## Change + +In v0.2.0-rc.2, switching on Automation tasks in the Plugins page appended `@deepseek-ai/dsh-experimental-schedule-bundle` to `dsh.profile.bundles` in `$DSH_HOME/profiles//package.json`. That bundle inserted the `time-context`, `schedule`, and `ui-schedule` rows. + +The next release removes the bundle. `@deepseek-ai/dsh-web-app` mounts `schedule` and `ui-schedule` in every Web profile, and the `standard`, `cordis`, and `ptc` presets declare the clock reading and the four `schedule_*` tools; `minimal` declares neither ([details](../../../subsystems/schedule.md)). + +Loading a profile removes `@deepseek-ai/dsh-experimental-schedule-bundle` from its `dsh.profile.bundles` and rewrites `package.json`; other manifest fields are kept. Stored tasks and delivery records stay on disk and remain in use. + +## Migration + +1. Start `dsh` once with each affected profile; no manual edit is needed. A profile directory that another tool writes must drop the entry from `dsh.profile.bundles` itself. +2. Keep overrides on `schedule` and `ui-schedule` in `cordis.patch.yml` or a `--patch` overlay; the Web composition carries those rows. A top-level override on `time-context` no longer matches a row, because the clock row belongs to the presets ([time-context](../../../../packages/context/time-context/README.md)); delete it. +3. Confirm: `dsh.profile.bundles` no longer lists the bundle, the Plugins page shows no failed Automation tasks entry, and the sidebar shows Automation tasks with the previously stored reminders. diff --git a/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.zh.md b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.zh.md new file mode 100644 index 0000000000..bb6eb314ec --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.zh.md @@ -0,0 +1,22 @@ +--- +kind: upgrade-guide +description: "“自动化任务”可选 bundle 已移除;Web 组合自行挂载 Schedule。" +--- + +# “自动化任务” bundle 已移除 + +[English](guide.md) | 中文 + +## 变更 + +在 v0.2.0-rc.2 中,在插件管理页开启“自动化任务”会把 `@deepseek-ai/dsh-experimental-schedule-bundle` 追加到 `$DSH_HOME/profiles//package.json` 的 `dsh.profile.bundles`。该 bundle 插入 `time-context`、`schedule` 和 `ui-schedule` 三行。 + +下一版本移除该 bundle。`@deepseek-ai/dsh-web-app` 在每个 Web profile 中挂载 `schedule` 和 `ui-schedule`,`standard`、`cordis` 和 `ptc` preset 声明时钟读数和四个 `schedule_*` 工具;`minimal` 两者都不声明([详情](../../../subsystems/schedule.zh.md))。 + +加载 profile 时会从其 `dsh.profile.bundles` 中删除 `@deepseek-ai/dsh-experimental-schedule-bundle` 并重写 `package.json`;其他 manifest 字段保持不变。已存储的任务和投递记录仍保留在磁盘上并继续使用。 + +## 迁移 + +1. 用每个受影响的 profile 启动一次 `dsh`,无需手动编辑。由其他工具写入的 profile 目录需要自行从 `dsh.profile.bundles` 删除该条目。 +2. 保留 `cordis.patch.yml` 或 `--patch` overlay 中针对 `schedule` 和 `ui-schedule` 的覆盖项;Web 组合包含这两行。顶层针对 `time-context` 的覆盖项不再匹配任何行,因为时钟行属于 preset([time-context](../../../../packages/context/time-context/README.zh.md));请删除它。 +3. 确认:`dsh.profile.bundles` 不再列出该 bundle,插件管理页不再显示异常的“自动化任务”条目,且侧栏显示“自动化任务”并列出之前存储的提醒。 diff --git a/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.i18n.yaml b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.i18n.yaml new file mode 100644 index 0000000000..2abe43fe57 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.md +/: + en: 671d5c0f87811869 + zh: 442229358c471ea8 +/subpath-plugins-read-subpath-icon-instead-of-subpath-package-json: + en: 4e7a5119af04d780 + zh: e70b1fc5f253d6f4 +/subpath-plugins-read-subpath-icon-instead-of-subpath-package-json/change: + en: 4793cf77e0c3a9ba + zh: a4fbe548044af6e6 +/subpath-plugins-read-subpath-icon-instead-of-subpath-package-json/migration: + en: bd32f99339b4a69e + zh: b0d6b46724bc57cc diff --git a/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.md b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.md new file mode 100644 index 0000000000..4f7ee72f02 --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.md @@ -0,0 +1,27 @@ +--- +kind: upgrade-guide +description: "Subpath plugins no longer read display text or icons from an exported /package.json; they use locale meta and /icon." +--- + +# Subpath plugins read `/icon` instead of `/package.json` + +English | [中文](guide.zh.md) + +## Change + +In v0.2.0-rc.2, Plugin Manager and Settings read a subpath plugin such as `my-plugins/search` through `my-plugins/search/package.json` when the package exported it: `name` and `description` filled missing locale fields, and `icon` supplied the row image. A subpath is not a package, so the next release never reads a `package.json` for it. Its title and description come only from `my-plugins/search/locale/*.json`, and its image comes from the exported `my-plugins/search/icon` resource. Package-root plugins keep their `package.json` text and `icon`; when `icon` is omitted, an exported `./icon` supplies the image. Authors of packages that export subpath `package.json` files are affected. + +## Migration + +1. Move the subpath manifest's `name` and `description` into `meta.title` and `meta.description` of the subpath locale files, and export them, for example `"./search/locale/*.json": "./locale/search/*.json"`. +2. Replace the subpath manifest's `icon` with an icon export and publish its target: + + ```json + { + "exports": { "./search/icon": "./assets/search.svg" }, + "files": ["assets/search.svg"] + } + ``` + +3. Remove the `./search/package.json` export and its file. +4. Run `pnpm run verify-package-meta` in a DSH checkout, or open Plugin Manager, and confirm the subpath row shows its title, description, and image. diff --git a/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.zh.md b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.zh.md new file mode 100644 index 0000000000..8e8c18073d --- /dev/null +++ b/docs/upgrade-guide/v0.2.0-rc.2/subpath-plugin-display-manifest/guide.zh.md @@ -0,0 +1,27 @@ +--- +kind: upgrade-guide +description: "子路径插件不再从导出的 <子路径>/package.json 读取展示文本和图标,改用 locale meta 和 <子路径>/icon。" +--- + +# 子路径插件改读 `<子路径>/icon`,不再读 `<子路径>/package.json` + +[English](guide.md) | 中文 + +## 变更 + +在 v0.2.0-rc.2 中,插件管理页和设置页会在包导出 `my-plugins/search/package.json` 时,通过它读取 `my-plugins/search` 这样的子路径插件:`name` 和 `description` 补全缺失的 locale 字段,`icon` 提供行图片。子路径不是包,因此下一版本不再为它读取任何 `package.json`。其标题和描述只来自 `my-plugins/search/locale/*.json`,图片来自导出的 `my-plugins/search/icon` 资源。包根插件保留 `package.json` 文本和 `icon`;省略 `icon` 时,导出的 `./icon` 提供图片。受影响的是导出子路径 `package.json` 的包作者。 + +## 迁移 + +1. 将子路径清单的 `name` 和 `description` 移到子路径 locale 文件的 `meta.title` 和 `meta.description`,并导出这些文件,例如 `"./search/locale/*.json": "./locale/search/*.json"`。 +2. 用图标导出替换子路径清单的 `icon`,并发布其目标文件: + + ```json + { + "exports": { "./search/icon": "./assets/search.svg" }, + "files": ["assets/search.svg"] + } + ``` + +3. 删除 `./search/package.json` 导出及其文件。 +4. 在 DSH 仓库中运行 `pnpm run verify-package-meta`,或打开插件管理页,确认子路径行显示标题、描述和图片。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.i18n.yaml new file mode 100644 index 0000000000..92dd85b5a7 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.md +/: + en: ba51914a407e33ae + zh: f0440eb120093d36 +/explicit-installation-and-composition-for-optional-capabilities: + en: 5a3fcd469d908d58 + zh: df94cc0beddc20ef +/explicit-installation-and-composition-for-optional-capabilities/change: + en: 3143b032a22268ec + zh: e55603836a24022a +/explicit-installation-and-composition-for-optional-capabilities/migration: + en: e197a95e2e59af22 + zh: dfdda504922c1dc5 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.md new file mode 100644 index 0000000000..678146bfc1 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.md @@ -0,0 +1,28 @@ +--- +kind: upgrade-guide +description: "Explicit installation, peer dependencies, and configuration for optional hooks, webhooks, tools, and skills." +--- + +# Explicit installation and composition for optional capabilities + +English | [中文](guide.zh.md) + +## Change + +The CLI and Python runtime omit the hook bridges from their installation dependencies, and the CLI omits the webhook pair. Base and the full Web presets omit dormant Ralph rows, and base omits its dormant badge row. A patch that only enables a removed row does not insert its plugin. Default active behavior, plugin registration names, tool names, and recorded Session event names remain unchanged. + +Package ownership and naming follow the [experimental package policy](../../../../scripts/experimental-package-policy.ts). Installation and composition are separate: installing a plugin package does not mount it, and a bundle must be selected before its contributed rows can be configured. + +## Migration + +1. Install any packages missing from the target profile together with their required peers. Profiles disable automatic peer installation. For example, run `dsh plugin --profile web add @deepseek-ai/dsh-hooks-claude-code @deepseek-ai/dsh-hook-protocol`. The Codex bridge needs the same protocol peer; the GitHub adapter needs `@deepseek-ai/dsh-webhook` alongside `@deepseek-ai/dsh-webhook-github`. +2. Select an available bundle before overriding its rows. For a capability composed manually, replace an override of a removed row with a complete insertion. Add preset-local tools inside that preset's `config.plugins`, including the services they require. A Host badge insertion is: + + ```yaml + - insert: + - id: skill-badge + name: '@deepseek-ai/dsh-skill-badge' + ``` + +3. For GitHub review, use the example distributed with the adapter instead of the removed CLI example. Follow the [installed-profile instructions](../../../user/guide/github-review.md); keep customized rule modules inside the profile so their imports resolve its installed packages. +4. Start the profile and confirm that its configured plugins activate without missing-package, missing-peer, or missing-row errors. Confirm the expected tool or skill is available in the intended Agent preset. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.zh.md new file mode 100644 index 0000000000..4ca4f3cf51 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/experimental-package-composition/guide.zh.md @@ -0,0 +1,28 @@ +--- +kind: upgrade-guide +description: "可选 hooks、webhooks、工具与技能所需的显式安装、peer 依赖和配置。" +--- + +# 可选能力的显式安装与组合 + +[English](guide.md) | 中文 + +## 变更 + +CLI 与 Python 运行时的安装依赖不再包含 hook 桥接,CLI 也不再包含 webhook 包对。Base 与完整 Web 预设省略休眠的 Ralph 行,base 也省略休眠的徽章行。仅启用已移除行的 patch 不会插入其插件。默认活动行为、插件注册名称、工具名称与已记录的 Session 事件名称保持不变。 + +包归属和命名遵循[实验包策略](../../../../scripts/experimental-package-policy.ts)。安装与组合相互独立:安装插件包不会挂载它,必须先选择 bundle 才能配置其贡献的行。 + +## 迁移 + +1. 将目标 profile 缺少的包与所需 peer 一起安装。Profile 禁用自动 peer 安装。例如,运行 `dsh plugin --profile web add @deepseek-ai/dsh-hooks-claude-code @deepseek-ai/dsh-hook-protocol`。Codex 桥接需要相同的协议 peer;GitHub 适配器需要同时安装 `@deepseek-ai/dsh-webhook` 与 `@deepseek-ai/dsh-webhook-github`。 +2. 覆盖 bundle 的行之前先选择可用的 bundle。对于手动组合的能力,将覆盖已移除行的 patch 替换为完整插入。在预设的 `config.plugins` 中添加预设内工具及其所需服务。Host 徽章插入示例: + + ```yaml + - insert: + - id: skill-badge + name: '@deepseek-ai/dsh-skill-badge' + ``` + +3. GitHub review 使用适配器随附的示例,替代已删除的 CLI 示例。遵循[安装版 profile 说明](../../../user/guide/github-review.zh.md);将定制规则模块保留在 profile 内,使其导入能解析该 profile 安装的包。 +4. 启动 profile,确认配置的插件激活时没有缺包、缺 peer 或缺行错误。确认预期工具或技能在目标 Agent 预设中可用。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.i18n.yaml new file mode 100644 index 0000000000..fe7644b98d --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.md +/: + en: 7081d888aedd32d2 + zh: 167263e5e4e514cc +/instruction-loading-resolves-one-harness-home: + en: b308584aa55910bd + zh: 910cc6b2b7f9b3ea +/instruction-loading-resolves-one-harness-home/change: + en: afd003587be927b4 + zh: 15dcd2ec55a5c968 +/instruction-loading-resolves-one-harness-home/migration: + en: 483d23f92ad84a8c + zh: 3c2648ebf2156b34 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.md new file mode 100644 index 0000000000..4c8f156407 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "The `agent-instructions` row no longer accepts `dshHome`; instruction loading always resolves the harness home from `$DSH_HOME` or `~/.dsh`." +--- + +# Instruction loading resolves one harness home + +English | [中文](guide.zh.md) + +## Change + +`@deepseek-ai/dsh-agent-instructions` removed its `dshHome` config field. The plugin resolves the harness home through `resolveDshHome()` from `@deepseek-ai/dsh-home-paths`, like every other harness consumer: the home is `$DSH_HOME` when it is set and non-blank, otherwise `~/.dsh`, with tilde expansion. + +A `cordis.yml`, profile patch, or `--patch` overlay that still sets `dshHome` on the `agent-instructions` row has no effect: the row keeps the stray key and the plugin ignores it, so instruction loading follows the process home. Other providers, such as `dsh-skill-filesystem` and `dsh-shell-env`, keep their own home fields; this removal covers the instruction loader only. + +## Migration + +1. Delete `dshHome` from the `agent-instructions` row in `cordis.yml`, `$DSH_HOME/cordis.patch.yml`, `$DSH_HOME/profiles//cordis.patch.yml`, and any `--patch` overlay. +2. Where the override pointed at a non-default home, export `DSH_HOME` for the process instead, so every harness consumer reads that home. +3. Confirm with `dsh --profile --dump-config` that the `agent-instructions` row lists only its remaining fields, and that a new session's first request shows `Instructions from: $DSH_HOME/AGENTS.md` (or `~/.dsh/AGENTS.md`) when that file exists. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.zh.md new file mode 100644 index 0000000000..9af456de67 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/instruction-home-override/guide.zh.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "`agent-instructions` 行不再接受 `dshHome`;指令加载始终从 `$DSH_HOME` 或 `~/.dsh` 解析 harness home。" +--- + +# 指令加载只解析一个 harness home + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-agent-instructions` 移除了 `dshHome` 配置字段。该插件通过 `@deepseek-ai/dsh-home-paths` 的 `resolveDshHome()` 解析 harness home,与 harness 其他消费方一致:`$DSH_HOME` 已设置且非空白时以它为准,否则使用 `~/.dsh`,并展开波浪号。 + +`cordis.yml`、profile patch 或 `--patch` overlay 中 `agent-instructions` 行上残留的 `dshHome` 不再有任何效果:该行会保留这个多余键,插件忽略它,指令加载跟随进程 home。其他 provider(如 `dsh-skill-filesystem`、`dsh-shell-env`)保留各自的 home 字段;本次删除只覆盖指令加载器。 + +## 迁移 + +1. 从 `cordis.yml`、`$DSH_HOME/cordis.patch.yml`、`$DSH_HOME/profiles//cordis.patch.yml` 以及任何 `--patch` overlay 的 `agent-instructions` 行中删除 `dshHome`。 +2. 若该覆盖原本指向非默认 home,改为在进程中导出 `DSH_HOME`,让所有 harness 消费方读取同一个 home。 +3. 用 `dsh --profile --dump-config` 确认 `agent-instructions` 行只剩其余字段,并确认新会话的首次请求在该文件存在时显示 `Instructions from: $DSH_HOME/AGENTS.md`(或 `~/.dsh/AGENTS.md`)。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.i18n.yaml new file mode 100644 index 0000000000..abb5d6e7be --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.md +/: + en: 50f48b2d699ff15d + zh: 3767619b47fd76fb +/read-named-events-from-the-messages-mock-server: + en: d97603a00507734c + zh: 6cfec46c21e97b70 +/read-named-events-from-the-messages-mock-server/change: + en: f03d6e1d637f6397 + zh: cabeac0144d33ac4 +/read-named-events-from-the-messages-mock-server/migration: + en: 7528082a3376359e + zh: 0e8eaad875394baa diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.md new file mode 100644 index 0000000000..eabae9f724 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.md @@ -0,0 +1,17 @@ +--- +kind: upgrade-guide +description: "Messages mock streams publish named SSE events for native SDK consumers." +--- + +# Read named events from the Messages mock server + +English | [中文](guide.zh.md) + +## Change + +The next release makes `@deepseek-ai/dsh-llm-mock-server` emit the Messages event name beside each structured SSE payload. Native SDKs require these names to process message and content-block events. Consumers that accept only unnamed SSE messages must update their event handling. Scripted payloads, ordering, request capture, and chunk counts are unchanged; the deliberately malformed JSON behavior still sends its invalid data directly. + +## Migration + +1. Consume the named Messages events and parse their `data` fields. Do not filter the stream to unnamed events. +2. Verify a complete text response, tool-call response, and the fault behaviors your test uses. The [mock server reference](../../../../packages/test-support/llm-mock-server/README.md) describes the available scripts. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.zh.md new file mode 100644 index 0000000000..126c489e03 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/mock-messages-events/guide.zh.md @@ -0,0 +1,17 @@ +--- +kind: upgrade-guide +description: "Messages 模拟流为原生 SDK 消费方发布具名 SSE 事件。" +--- + +# 读取 Messages 模拟服务器的具名事件 + +[English](guide.md) | 中文 + +## 变更 + +下一版本的 `@deepseek-ai/dsh-llm-mock-server` 在每个结构化 SSE 载荷旁发送 Messages 事件名。原生 SDK 需要这些名称才能处理消息与内容块事件。只接受无名 SSE 消息的消费方必须更新事件处理。脚本载荷、顺序、请求捕获与分片计数保持不变;刻意生成畸形 JSON 的行为仍直接发送无效数据。 + +## 迁移 + +1. 消费具名 Messages 事件并解析其 `data` 字段。不要将流过滤为仅含无名事件。 +2. 验证完整文本响应、工具调用响应和测试使用的故障行为。[模拟服务器参考](../../../../packages/test-support/llm-mock-server/README.zh.md)说明了可用脚本。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml new file mode 100644 index 0000000000..870c91d93d --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md +/: + en: a77928d6130fe601 + zh: da46d612268c3ced +/native-subagent-bundles-include-preset-tools: + en: b5c79b41fc34574a + zh: d784adcf343b0817 +/native-subagent-bundles-include-preset-tools/change: + en: 7d4af3e4ab91ef72 + zh: 837377c96d5eee12 +/native-subagent-bundles-include-preset-tools/migration: + en: 3257a5d18d07cada + zh: b663a7ae020bbfee diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md new file mode 100644 index 0000000000..5a08637440 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md @@ -0,0 +1,32 @@ +--- +kind: upgrade-guide +description: "Claude Code and Codex bundles add delegation tools to full Web presets while retaining their package and row identities." +--- + +# Native subagent bundles include preset tools + +English | [中文](guide.zh.md) + +## Change + +`@deepseek-ai/dsh-subagent-claude-code` and `@deepseek-ai/dsh-subagent-codex` keep their package names, bundle selections, provider row IDs, configuration, and native authentication. In Web and Desktop, a selected bundle now adds enabled delegation tools to `preset-standard`, `preset-cordis`, and `preset-ptc`; previously the bundle registered only its provider. Existing Agents retain their composition; new Agents and subsequently reopened Sessions receive the selected tools. + +The contributed rows retain `tool-subagent-claude-code` and `tool-subagent-codex`. Later user layers can override their configuration or disabled state, and a complete preset configuration replacement keeps its own child list. An override does not select an unselected bundle. Duplicate declarations of the same model-facing tool still fail. + +Delegation follows the shared managed-activation API: the tool returns a child ID, and completion arrives as a logged notice to the parent Agent. Remove obsolete `backgroundMode` settings; `maxDepth: provider-managed` remains supported for these native providers. + +## Migration + +1. Preserve selected bundle names, provider configuration, and authentication. Existing provider-only packages need **Update** in Plugins → Official to receive the tool-contributing bundle; restart when requested. To adopt a new provider, enable its card. [Installation targets](../../../../packages/boot/plugin-manager/README.md#use-this-package) follow the published DSH version or source checkout; equal version strings do not make an older registry package equivalent to a development link. Off only deselects; Remove remains separate. +2. Configure the familiar tool row ID with a scoped patch. Configuration overrides still replace the complete configuration. For example, this keeps Codex disabled in standard while other full presets use their selected composition: + + ```yaml + - preset: preset-standard + id: tool-subagent-codex + disabled: true + ``` + +3. If an additional manual row registers the same tool in a preset, remove that redundant row or disable the bundle's contribution explicitly. A copied complete preset can retain its existing tool declarations and disabled state. Verify the effective tool catalog in a new Session; minimal keeps its current shell and working-directory tools. +4. Plugin clients receive required `BundleInfo.official` and `BundleInfo.availability` fields; detail-slot `PluginPackageRef` receives them too. `official` identifies project maintenance, `availability` reports `installation`, `profile`, or `missing`, and `installed` still means a profile dependency declaration. Detail-slot readers that only display existing fields need no changes; constructors and exact validators must include the new fields. Optional `installTarget` identifies the offered version and registry or local-link spec. + +**Other profiles.** Headless, SDK, ACP, and custom profiles without the three full preset targets are outside this compatibility scope. Selecting these bundle layers fails for missing preset targets and can retain the saved selection; there is no automatic rollback. Such profiles can keep the installed provider package without selecting its bundle and explicitly compose provider/tool rows using the [Codex](../../../../packages/subagent/subagent-codex/README.md#exposing-the-tool) or [Claude Code](../../../../packages/subagent/subagent-claude-code/README.md#exposing-the-tool) example. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md new file mode 100644 index 0000000000..e98a34f503 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md @@ -0,0 +1,32 @@ +--- +kind: upgrade-guide +description: "Claude Code 与 Codex bundle 为完整 Web 预设添加委派工具,同时保留包名和行标识。" +--- + +# 原生子代理 bundle 包含预设工具 + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-subagent-claude-code` 和 `@deepseek-ai/dsh-subagent-codex` 保留包名、bundle 选择、提供方行 ID、配置及原生认证。在 Web 和 Desktop 中,已选中的 bundle 现在会为 `preset-standard`、`preset-cordis` 和 `preset-ptc` 添加启用的委派工具;此前 bundle 只注册提供方。已有 Agent 保留其组合;新 Agent 和之后重新打开的 Session 使用选中的工具。 + +贡献行保留 `tool-subagent-claude-code` 和 `tool-subagent-codex`。后续用户层可以覆盖其配置或禁用状态,完整的预设配置替换保留自己的子列表。覆盖不会选中未选中的 bundle。重复声明同一个面向模型的工具仍会失败。 + +委派遵循共享的托管 activation API:工具返回子级 ID,完成结果通过已记录的通知发送给父 Agent。移除已废弃的 `backgroundMode` 设置;这些原生提供方仍支持 `maxDepth: provider-managed`。 + +## 迁移 + +1. 保留已选中的 bundle 名称、提供方配置和认证。已有仅提供方的包需要在插件 → 官方中**更新**才能获得贡献工具的 bundle;按提示重启。要使用新的提供方,启用其卡片。[安装目标](../../../../packages/boot/plugin-manager/README.zh.md#use-this-package)对应已发布的 DSH 版本或源码 checkout;版本字符串相同不代表旧注册表包等同于开发链接。关闭只取消选择;删除仍为独立操作。 +2. 使用作用域补丁配置熟悉的工具行 ID。配置覆盖仍会替换整份配置。例如,以下补丁在 standard 中保持 Codex 禁用,其他完整预设则使用其选中的组合: + + ```yaml + - preset: preset-standard + id: tool-subagent-codex + disabled: true + ``` + +3. 如果额外的手动行在预设中注册了同一个工具,移除该重复行,或显式禁用 bundle 的贡献行。复制的完整预设可以保留其现有工具声明和禁用状态。在新 Session 中验证有效工具目录;minimal 保留当前的 shell 和工作目录工具。 +4. 插件客户端收到必填的 `BundleInfo.official` 和 `BundleInfo.availability` 字段;详情 slot 的 `PluginPackageRef` 也会收到它们。`official` 标识项目维护,`availability` 表示 `installation`、`profile` 或 `missing`,`installed` 仍表示 profile 的依赖声明。只展示已有字段的详情 slot 读取方无需修改;构造方和精确校验器必须包含新增字段。可选的 `installTarget` 标识所提供的版本及注册表或本地链接 spec。 + +**其他 profile。** Headless、SDK、ACP 和缺少三个完整预设目标的自定义 profile 不在此次兼容范围内。选择这些 bundle 层会因缺少预设目标而失败,并可能保留已保存的选择;没有自动回滚。这些 profile 可以保留已安装的提供方包而不选择其 bundle,并参考 [Codex](../../../../packages/subagent/subagent-codex/README.zh.md#exposing-the-tool) 或 [Claude Code](../../../../packages/subagent/subagent-claude-code/README.zh.md#exposing-the-tool) 示例显式组合提供方和工具行。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.i18n.yaml new file mode 100644 index 0000000000..01ecc3a8b0 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.md +/: + en: ad37e45180bc096f + zh: e44c85b10b1d9d4b +/start-a-fresh-agent-with-new-session: + en: 5e4b26181326018f + zh: 99bc75dcd12bf647 +/start-a-fresh-agent-with-new-session/change: + en: ab12aadb31d0d3e8 + zh: 637b446aef86456e +/start-a-fresh-agent-with-new-session/migration: + en: 9565e66b6bcdf269 + zh: 226e470de85aa8b6 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.md new file mode 100644 index 0000000000..bc735fe299 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "New Session creates a fresh Agent instead of reusing an existing empty Session." +--- + +# Start a fresh Agent with New Session + +English | [中文](guide.zh.md) + +## Change + +In Web and Desktop, New Session creates a fresh Session and Agent using the current preset definition. It previously reused an existing empty Session in the workspace, which could retain tools from before an optional bundle changed. + +Client calls to `uiWorkspace.startSession(workspaceId)` follow the same behavior. Workspace reconnection through `openWorkspace` or `connectWorkspace` still reuses an eligible blank. Calls with draft-preparation options preserve their existing reuse and content rules. Existing Agents retain their composition. + +## Migration + +1. After changing optional bundles, use New Session to obtain the current tool composition. Continue in an existing Session when its retained composition is intentional. +2. Client integrations that reconnect a workspace should use `openWorkspace` or `connectWorkspace`. Keep explicit draft-preparation options when preparing an existing draft. +3. Confirm that the new Agent has the selected tools; no persisted Session files or profile patches need rewriting. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.zh.md new file mode 100644 index 0000000000..b3371908a1 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/new-session-generations/guide.zh.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "新会话会创建新的 Agent,不再复用已有空 Session。" +--- + +# 通过新会话创建新的 Agent + +[English](guide.md) | 中文 + +## 变更 + +在 Web 和 Desktop 中,新会话按当前预设定义创建新的 Session 和 Agent。此前该操作会复用工作区内已有的空 Session,因而可能保留可选组合包变更前的工具。 + +客户端调用 `uiWorkspace.startSession(workspaceId)` 采用相同行为。通过 `openWorkspace` 或 `connectWorkspace` 重连工作区时仍复用符合条件的空 Session。带草稿准备选项的调用保留已有复用与内容规则。已有 Agent 保留其组合。 + +## 迁移 + +1. 更改可选组合包后,通过新会话取得当前工具组合。需要保留原组合时,继续使用已有 Session。 +2. 重连工作区的客户端集成应使用 `openWorkspace` 或 `connectWorkspace`。准备已有草稿时保留显式草稿准备选项。 +3. 确认新 Agent 拥有已选择的工具;无需重写持久化 Session 文件或 profile patch。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.i18n.yaml new file mode 100644 index 0000000000..e632de9feb --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.md +/: + en: 3f9b48b2230ec6e4 + zh: c1aad0e9121d46a4 +/dsh-plugin-option-placement: + en: 3d717a2b7e9087d3 + zh: e6004d015e6a4829 +/dsh-plugin-option-placement/change: + en: 15fe8cf74db0ba53 + zh: c6ea1056aa7a6f77 +/dsh-plugin-option-placement/migration: + en: b7239597ebc4d970 + zh: fa45f851cf1f3af1 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.md new file mode 100644 index 0000000000..8bb89d56a9 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "dsh plugin reads --profile only before the forwarded pnpm arguments and prints its own help." +--- + +# `dsh plugin` option placement + +English | [中文](guide.zh.md) + +## Change + +`dsh plugin` reads `--profile` only before the forwarded pnpm arguments, which start at the first token it does not recognize, and it has its own help. Previously it accepted `--profile ` anywhere on the command line, had no help of its own, and forwarded `-h` and `--help` to pnpm from any position. + +When the forwarded arguments begin with the pnpm command, everything after that command now reaches pnpm verbatim, including `-h`, `--help`, and `--profile`. In any other position, including after a leading pnpm option such as `--filter `, `-h` or `--help` prints the `dsh plugin` help and exits 0. Scripts that place `--profile` after the pnpm command or after a leading pnpm option fail with `required option '--profile ' not specified`, and `dsh plugin --profile --help` no longer prints pnpm's help. + +## Migration + +1. Move `--profile ` before every pnpm argument: replace `dsh plugin add --profile ` with `dsh plugin --profile add `. +2. Request pnpm's help for one command after that command, with any pnpm options after it too, as in `dsh plugin --profile add --help`. For pnpm's general help, forward `help`: `dsh plugin --profile help`. +3. Confirm that `dsh plugin --help` prints `Usage: dsh plugin [options] [args...]` and that the commands from steps 1 and 2 reach pnpm. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.zh.md new file mode 100644 index 0000000000..50c71978cf --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/plugin-option-placement/guide.zh.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "dsh plugin 只在被转发的 pnpm 参数之前读取 --profile,并提供自己的帮助。" +--- + +# `dsh plugin` 选项位置 + +[English](guide.md) | 中文 + +## 变更 + +`dsh plugin` 只在被转发的 pnpm 参数之前读取 `--profile`,被转发的参数从它无法识别的第一个 token 开始;它还提供自己的帮助。此前它接受出现在命令行任意位置的 `--profile `,没有自己的帮助,并且无论 `-h` 和 `--help` 位于何处都会转发给 pnpm。 + +被转发的参数以 pnpm 命令开头时,该命令之后的所有内容现在都会原样交给 pnpm,包括 `-h`、`--help` 和 `--profile`。在其他任何位置,包括位于 `--filter ` 等开头的 pnpm 选项之后,`-h` 或 `--help` 都会打印 `dsh plugin` 的帮助并以 0 退出。把 `--profile` 写在 pnpm 命令或开头的 pnpm 选项之后的脚本会因 `required option '--profile ' not specified` 而失败,`dsh plugin --profile --help` 也不再打印 pnpm 的帮助。 + +## 迁移 + +1. 把 `--profile ` 移到所有 pnpm 参数之前:将 `dsh plugin add --profile ` 改为 `dsh plugin --profile add `。 +2. 查看某个 pnpm 命令的帮助时,把帮助选项和其他 pnpm 选项都写在该命令之后,例如 `dsh plugin --profile add --help`。查看 pnpm 的总体帮助时转发 `help`:`dsh plugin --profile help`。 +3. 确认 `dsh plugin --help` 打印 `Usage: dsh plugin [options] [args...]`,并且第 1、2 步中的命令能够到达 pnpm。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.i18n.yaml new file mode 100644 index 0000000000..bf382c449c --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.md +/: + en: 25de822f0f884eb9 + zh: c910706695f91c76 +/identify-bundle-rows-by-their-preset: + en: 95cdaba0de8fe664 + zh: f7382ff6b9f65a49 +/identify-bundle-rows-by-their-preset/change: + en: e43402c18f6b6de8 + zh: 32f3e46546e9d1f8 +/identify-bundle-rows-by-their-preset/migration: + en: 89cead6b9cd199bd + zh: 8a13a234d9feacbb diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.md new file mode 100644 index 0000000000..225cf96268 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.md @@ -0,0 +1,29 @@ +--- +kind: upgrade-guide +description: "Plugin-manager bundle inventories include preset-scoped rows whose row IDs are unique only within their preset." +--- + +# Identify bundle rows by their preset + +English | [中文](guide.zh.md) + +## Change + +The next release adds preset-scoped declarations to `PluginManager.listBundles().rows`. A bundle can declare the same `rowId` in several presets. `PluginRowRef` values passed to detail slots carry the same optional `preset` identity. These rows carry `preset`, optional `composition` state, and `readOnlyReason: 'preset-managed'`; they do not carry an editable root `entryId`. + +Clients that key rows only by bundle name and `rowId`, or treat every row as a root plugin switch, must distinguish preset declarations. Existing root rows retain their current identity and controls. + +## Migration + +1. Include `preset` in row identity, alongside the bundle name and `rowId`. Keep the absent-preset case distinct from every named preset. +2. Render `preset-managed` rows read-only in direct row controls. Use the bundle switch or an explicit scoped profile patch to configure them. Only pass a returned root `entryId` to `setPluginEnabled`. +3. Read preset row state from `composition`, instead of looking up a root Loader entry with the same `rowId`. +4. For YAML edits, use `preset` to address the outer declaration and `id` to address its child: + + ```yaml + - preset: preset-standard + id: optional-tool-str-replace-editor + disabled: true + ``` + +5. Verify that a bundle contributing to standard, cordis, and ptc shows three distinct rows and that root-row switches continue to work. The [profile patch reference](../../../../packages/boot/app-boot/README.md) describes ordering and validation. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.zh.md new file mode 100644 index 0000000000..d2800b9591 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.zh.md @@ -0,0 +1,29 @@ +--- +kind: upgrade-guide +description: "插件管理器的组合包清单包含预设作用域行,其行 ID 仅在所属预设内唯一。" +--- + +# 按所属预设区分组合包行 + +[English](guide.md) | 中文 + +## 变更 + +下一个版本在 `PluginManager.listBundles().rows` 中加入预设作用域声明。一个组合包可以在多个预设中声明相同的 `rowId`。详情插槽中的 `PluginRowRef` 也携带可选的 `preset` 标识。这些行包含 `preset`、可选的 `composition` 状态和 `readOnlyReason: 'preset-managed'`,不包含可编辑的根级 `entryId`。 + +仅用组合包名和 `rowId` 作为行标识,或将所有行都视为根插件开关的客户端,必须区分预设声明。已有根行保留当前标识和控制方式。 + +## 迁移 + +1. 将 `preset` 与组合包名、`rowId` 一起纳入行标识。区分没有预设的情况与每个具名预设。 +2. 在直接行控制中只读展示 `preset-managed` 行。通过组合包开关或显式的预设作用域 profile patch 配置这些行。只将实际返回的根级 `entryId` 传给 `setPluginEnabled`。 +3. 从 `composition` 读取预设行状态,不要按同一个 `rowId` 查找根 Loader 条目。 +4. 编辑 YAML 时,用 `preset` 定位外层声明,用 `id` 定位其子行: + + ```yaml + - preset: preset-standard + id: optional-tool-str-replace-editor + disabled: true + ``` + +5. 确认同时向 standard、cordis 和 ptc 贡献行的组合包显示三个不同的行,且根行开关仍可使用。[Profile patch 参考](../../../../packages/boot/app-boot/README.zh.md)说明顺序与校验规则。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.i18n.yaml new file mode 100644 index 0000000000..57b9ee6c34 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +/: + en: 37441fc2c29e3ee9 + zh: de434de0f9da7d40 +/python-ptc-applies-the-shared-file-sandbox: + en: 655754b417fa8699 + zh: 5571cb25b6621c07 +/python-ptc-applies-the-shared-file-sandbox/change: + en: a4575a117b53ce7c + zh: 2ea9f832b2179537 +/python-ptc-applies-the-shared-file-sandbox/migration: + en: faaefac69870d31f + zh: 08398580b7e3ac62 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md new file mode 100644 index 0000000000..a2545db4f8 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md @@ -0,0 +1,21 @@ +--- +kind: upgrade-guide +description: "Python PTC compositions require sandbox services, and direct Python file operations follow the resolved file policy." +--- + +# Python PTC applies the shared file sandbox + +English | [中文](guide.zh.md) + +## Change + +`@deepseek-ai/dsh-experimental-ptc-runtime-python` requires `sandbox` and `sandboxPolicy`. Previously, direct Python file operations ran without confinement and the provider rejected explicit `sandboxPolicy` inputs. It now applies the shared policy to each program and reports confinement in `PtcRunResult.sandbox`. A restricted run fails with `sandbox-unavailable` when confinement cannot be established. + +This affects custom compositions that select the Python runtime. Shipped profiles continue to use the Node runtime. Python remains local to macOS or Linux; its configured resource budgets and unsupported per-call timeout overrides remain unchanged. + +## Migration + +1. In the `cordis.yml` or profile patch that selects the Python runtime, retain the existing shared sandbox services. If they are absent, mount `@deepseek-ai/dsh-sandbox-local` and `@deepseek-ai/dsh-sandbox-policy` with its required `@deepseek-ai/dsh-session-projection` service. See the [Python runtime requirements](../../../../packages/experimental/ptc-runtime-python/README.md#use-this-package). +2. Set the shared `sandbox-policy` row's `mode` and `workspaceRoot` for agentless calls. Its default mode is `read-only`; `workspace-write` permits workspace writes. PTC tool calls use the Session's resolved policy and directory. Do not add a separate Python-only sandbox setting. +3. Direct service callers use `runtime.run(runtime.resolve(request))`; pass a trusted `sandboxPolicy` only when an explicit override is required. Model-facing `run_code` uses the existing approval flow for a one-call wider mode, without changing nested tools' permissions or replaying the program automatically. +4. Verify a program can write inside the intended workspace under `workspace-write`, fails to write outside it, and returns `result.sandbox.mode` and `result.sandbox.enforcement`. Under `read-only`, direct file writes fail. Inspect `sandbox-unavailable` separately from a denied file operation and repair the local sandbox installation before retrying. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md new file mode 100644 index 0000000000..9ed3b989b6 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md @@ -0,0 +1,21 @@ +--- +kind: upgrade-guide +description: "Python PTC 组合需要沙箱服务,且直接 Python 文件操作遵循已解析文件策略。" +--- + +# Python PTC 应用共享文件沙箱 + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-experimental-ptc-runtime-python` 需要 `sandbox` 和 `sandboxPolicy`。此前,直接 Python 文件操作不受约束,提供方拒绝显式 `sandboxPolicy` 输入。现在,它为每个程序应用共享策略,并通过 `PtcRunResult.sandbox` 报告约束情况。无法建立约束时,受限运行以 `sandbox-unavailable` 失败。 + +这影响选择 Python 运行时的自定义组合。已发布 profile 继续使用 Node 运行时。Python 仍限于本地 macOS 或 Linux;其配置的资源预算保持不变,仍不支持单次 timeout 覆盖。 + +## 迁移 + +1. 在选择 Python 运行时的 `cordis.yml` 或 profile patch 中,保留已有共享沙箱服务。若尚未提供,挂载 `@deepseek-ai/dsh-sandbox-local` 和 `@deepseek-ai/dsh-sandbox-policy`,以及后者所需的 `@deepseek-ai/dsh-session-projection` 服务。参见 [Python 运行时要求](../../../../packages/experimental/ptc-runtime-python/README.zh.md#use-this-package)。 +2. 为无 Agent 调用设置共享 `sandbox-policy` 行的 `mode` 和 `workspaceRoot`。默认模式是 `read-only`;`workspace-write` 允许工作区写入。PTC 工具调用使用 Session 的已解析策略和目录。不要新增 Python 专用沙箱设置。 +3. 直接服务调用方使用 `runtime.run(runtime.resolve(request))`;仅在需要显式覆盖时传入可信 `sandboxPolicy`。面向模型的 `run_code` 通过现有审批流程为一次调用扩大权限,不改变嵌套工具的权限,也不自动重放程序。 +4. 验证程序在 `workspace-write` 下可以写入预期工作区、写入工作区外时失败,并返回 `result.sandbox.mode` 和 `result.sandbox.enforcement`。在 `read-only` 下,直接文件写入失败。将 `sandbox-unavailable` 与文件操作被拒绝区分处理,修复本地沙箱安装后再重试。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.i18n.yaml new file mode 100644 index 0000000000..7167802a12 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.md +/: + en: 4f9ba8c8ed040a83 + zh: aabc458063878004 +/prepare-custom-title-routes: + en: cab86bb2cc427297 + zh: 7a14e268bf53fc73 +/prepare-custom-title-routes/change: + en: 278f593203887bbb + zh: 72949b90220f4a6f +/prepare-custom-title-routes/migration: + en: b7c439680f8a1ec8 + zh: 82b4243cb1b2ef0a diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.md new file mode 100644 index 0000000000..626851cf28 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "Custom title routes require registered adapters and reasoning efforts in increasing order." +--- + +# Prepare custom title routes + +English | [中文](guide.zh.md) + +## Change + +Model-backed session titles prepare their requests through `ctx.llm.prepareCall()`. Compositions that serve title routes only through `llm/stream` middleware must also register an adapter. Custom adapters must list selectable reasoning efforts from least to greatest, rather than in arbitrary display order. Title policy selects the first entry through a configuration function. Existing concrete `prepareCall()` inputs remain valid. + +## Migration + +1. Register the title route with `ctx.llm.registerAdapter()`. Replay compositions can configure the existing `llm-replay` plugin's `providers` list. +2. In the adapter's model metadata, order `reasoning.efforts` from least to greatest selectable effort. Apply the same order to replay models' `reasoningEfforts`. Keep the configured default independent of list order. Omit reasoning metadata when the route has no selectable controls. +3. Generate a title and check that `session/title-llm-request` records the selected effort and a provider title is saved. Existing Session records without that optional field remain readable. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.zh.md new file mode 100644 index 0000000000..bbb0b370e6 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/session-title-adapters/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "自定义标题路由要求注册适配器,并按递增顺序排列推理强度。" +--- + +# 准备自定义标题路由 + +[English](guide.md) | 中文 + +## 变更 + +模型生成的会话标题通过 `ctx.llm.prepareCall()` 准备请求。仅通过 `llm/stream` 中间件提供标题路由的组合还必须注册适配器。自定义适配器必须将可选推理强度从低到高排列,而不是使用任意显示顺序。标题策略通过配置函数选择首个条目。现有具体 `prepareCall()` 输入仍然有效。 + +## 迁移 + +1. 通过 `ctx.llm.registerAdapter()` 注册标题路由。回放组合可配置现有 `llm-replay` 插件的 `providers` 列表。 +2. 在适配器的模型元数据中,将 `reasoning.efforts` 按可选强度从低到高排列。回放模型的 `reasoningEfforts` 使用同一顺序。配置默认值保持独立于列表顺序。路由没有可选控制时省略推理元数据。 +3. 生成标题,确认 `session/title-llm-request` 记录了所选强度,且提供方标题已保存。不含该可选字段的现有 Session 记录仍可读取。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.i18n.yaml new file mode 100644 index 0000000000..c1938c0f53 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.md +/: + en: 9ae9e5682ed3cd7e + zh: 7423fcb7aed1938a +/migrate-delegation-to-managed-subagent-activations: + en: fcf5f06084f989d9 + zh: 5e3e6a05786390dd +/migrate-delegation-to-managed-subagent-activations/change: + en: 17add29d9f80674e + zh: 4d4050e99f019cca +/migrate-delegation-to-managed-subagent-activations/migration: + en: d4269b2c58484a3d + zh: 0ae39476ecd330e6 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.md new file mode 100644 index 0000000000..5a88ff2ee3 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.md @@ -0,0 +1,59 @@ +--- +kind: upgrade-guide +description: "Managed subagent activations replace foreground and Job-backed delegation, its configuration keys, and the published in-process driver." +--- + +# Migrate delegation to managed subagent activations + +English | [中文](guide.zh.md) + +## Change + +`@deepseek-ai/dsh-tool-subagent` removes `backgroundMode`, `enableRunInBackground`, and the model argument `run_in_background`. Every delegation now returns `{ kind: 'activation', subagentId }` after accepting the child, and the runtime delivers its final answer in a parent completion notice. Foreground results and `{ kind: 'background', jobId }` results are removed; subagents are no longer collected or stopped through `job_output` or `job_kill`. + +The published `@deepseek-ai/dsh-subagent-in-process-driver` package is removed. Local providers contribute creation inputs through `prepareContinuable()`; the subagent service owns execution. Spawn and Fork children can receive later messages, including after settlement when persistence and Session query are available. External providers execute once and do not accept follow-up messages. + +## Migration + +1. Remove `backgroundMode` and `enableRunInBackground` from `tool-subagent` rows in `cordis.yml`, profile patches, and overlays. Keep the provider and remaining configuration. For example: + + ```yaml + - name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + ``` + + Local tool delegation requires Session persistence. Compose it with the [subagent service and provider](../../../../packages/subagent/subagent/README.md#use-this-package). + +2. Remove `run_in_background` from tool calls and update result consumers to read `subagentId`. Collect the completion notice instead of foreground output or a Job result. Use `interrupt_agent` to stop the current execution; use `send_message` only for local children when those control tools are composed. + +3. For host code that must await a result, replace `ctx.subagents.start()` or `startContinuable()` with `startActivation()` and caller delivery. Given a composed service and live parent: + + ```ts + import type { Agent } from '@deepseek-ai/dsh-agent' + import type SubagentRuntime from '@deepseek-ai/dsh-subagent' + + async function collect(subagents: SubagentRuntime, parent: Agent, signal: AbortSignal) { + const activation = await subagents.startActivation({ + provider: 'spawn', + label: 'Review changes', + request: { parent, prompt: [{ type: 'text', text: 'Review these changes.' }] }, + signal, + delivery: 'caller', + }) + const result = await activation.result + await activation.dispose() + return result + } + ``` + + Caller delivery emits no parent completion notice. `result` settles after cleanup and includes `output`, `stopReason`, and optional `structured` and `diagnostic` values; inspect `stopReason` before treating it as success. The startup signal stops owning cancellation after publication: retain the receipt and call `dispose()` to cancel that exact activation. Cleanup failures are reported by `dispose()` separately from the captured result. + +4. Remove the driver dependency and imports, including `startInProcessRun`. Mount the existing Spawn or Fork provider and call the service as above. Custom local providers implement `prepareContinuable()` using the [provider API](../../../../docs/subsystems/subagent.md); there is no replacement standalone driver. + + External providers keep `start()`, but each returned `SubagentRun.id` must be unique across parents, providers, and local Sessions in the runtime. Generate a fresh id for each execution instead of using a parent-local counter. + + For `dsh-subagent-dsh-sdk`, update any separately configured `dshBin` runtime to support `session/wait`. For directory configuration, follow the [working-directory migration](../working-directory/guide.md). + +5. Confirm a delegation returns the activation result variant, its completion notice contains the final answer, and caller delivery returns the result without adding that notice. Confirm local children remain listed after completion and external children cannot be continued. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.zh.md new file mode 100644 index 0000000000..aa83610496 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/subagent-activations/guide.zh.md @@ -0,0 +1,59 @@ +--- +kind: upgrade-guide +description: "受管理的子 agent activation 替代前台与 Job 后台委派、相关配置键和已发布的进程内驱动包。" +--- + +# 将委派迁移到受管理的子 agent activation + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-tool-subagent` 移除了 `backgroundMode`、`enableRunInBackground` 和模型参数 `run_in_background`。每次委派现在都会在接纳子 agent(智能体)后返回 `{ kind: 'activation', subagentId }`,运行时通过父级完成通知交付最终答案。前台结果和 `{ kind: 'background', jobId }` 结果已移除;子 agent 不再通过 `job_output` 或 `job_kill` 收集结果或停止。 + +已发布的 `@deepseek-ai/dsh-subagent-in-process-driver` 包被移除。本地提供方通过 `prepareContinuable()` 提供创建输入,由子 agent 服务拥有执行过程。Spawn 和 Fork 子级可以接收后续消息;组合了持久化和 Session 查询服务时,结算后也能接收。外部提供方只执行一次,不接受后续消息。 + +## 迁移 + +1. 从 `cordis.yml`、配置文件补丁和覆盖文件中的 `tool-subagent` 配置项删除 `backgroundMode` 和 `enableRunInBackground`。保留提供方和其余配置。例如: + + ```yaml + - name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + ``` + + 本地工具委派需要 Session 持久化。将其与[子 agent 服务及提供方](../../../../packages/subagent/subagent/README.zh.md#use-this-package)一起组合。 + +2. 从工具调用中删除 `run_in_background`,并将结果消费方改为读取 `subagentId`。收集完成通知,替代前台输出或 Job 结果。组合了相应控制工具时,使用 `interrupt_agent` 停止当前执行;`send_message` 仅用于本地子级。 + +3. 对于必须等待结果的宿主代码,将 `ctx.subagents.start()` 或 `startContinuable()` 替换为 `startActivation()`,并选择调用方投递。以下示例假设服务已组合且父 agent 在线: + + ```ts + import type { Agent } from '@deepseek-ai/dsh-agent' + import type SubagentRuntime from '@deepseek-ai/dsh-subagent' + + async function collect(subagents: SubagentRuntime, parent: Agent, signal: AbortSignal) { + const activation = await subagents.startActivation({ + provider: 'spawn', + label: 'Review changes', + request: { parent, prompt: [{ type: 'text', text: 'Review these changes.' }] }, + signal, + delivery: 'caller', + }) + const result = await activation.result + await activation.dispose() + return result + } + ``` + + 调用方投递不会发送父级完成通知。`result` 在清理结束后结算,包含 `output`、`stopReason` 以及可选的 `structured` 和 `diagnostic` 值;将结果视为成功前先检查 `stopReason`。发布后,启动信号不再控制取消:保留回执,通过 `dispose()` 取消该次 activation。`dispose()` 单独报告清理失败,不替换已捕获的执行结果。 + +4. 删除驱动包依赖及其导入,包括 `startInProcessRun`。挂载现有的 Spawn 或 Fork 提供方,并按上例调用服务。自定义本地提供方按[提供方 API](../../../../docs/subsystems/subagent.zh.md)实现 `prepareContinuable()`;没有替代的独立驱动包。 + + 外部提供方保留 `start()`,但每次返回的 `SubagentRun.id` 必须在运行时内跨父级、提供方和本地 Session 保持唯一。为每次执行生成新的 id,替代父级独立计数器。 + + 使用 `dsh-subagent-dsh-sdk` 时,将单独配置的 `dshBin` 运行时更新到支持 `session/wait` 的版本。 目录配置按照[工作目录迁移指南](../working-directory/guide.zh.md)调整。 + +5. 确认委派返回 activation 结果变体,完成通知包含最终答案,且调用方投递返回结果而不添加该通知。确认本地子级在完成后仍可列出,外部子级不支持继续执行。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.i18n.yaml new file mode 100644 index 0000000000..fe08933d1f --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.md +/: + en: 0722f826c1c12360 + zh: 9b48ce197b6091f2 +/terminal-command-labels: + en: a75e371670f4ebc3 + zh: cbad7dd538376e70 +/terminal-command-labels/change: + en: 0e386cd5b5d349dc + zh: c456641a3aa938b3 +/terminal-command-labels/migration: + en: 2044b801804a40aa + zh: 0f98e7da80e2f066 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.md new file mode 100644 index 0000000000..fdebb0aba5 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "Custom TerminalBlock consumers must provide localized accessible names for overflowing command lines." +--- + +# Terminal command labels + +English | [中文](guide.zh.md) + +## Change + +`TerminalBlockLabels` from `@deepseek-ai/dsh-client-ui-primitives` requires a `commandLine(line: number): string` formatter. Previously, callers supplied no accessible name for command lines. Each horizontally overflowing line now uses this name for its keyboard-focusable group; lines that fit, including wrapped lines, add no Tab stop. This affects custom UI plugins that render `TerminalBlock`. + +## Migration + +1. Add `commandLine` to each labels object passed to `TerminalBlock`. The argument is the authored line number, starting at one. +2. Return a localized name, such as “Command line 1”, through the plugin's locale dictionary. Keep the original command text separate from this name. +3. Rebuild the plugin. Verify that a long command can receive keyboard focus and scroll with the arrow keys, and that a short or wrapped command adds no Tab stop. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.zh.md new file mode 100644 index 0000000000..39e63f5c69 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/terminal-command-labels/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "自定义 TerminalBlock 消费者必须为横向溢出的命令行提供本地化的无障碍名称。" +--- + +# 终端命令标签 + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-client-ui-primitives` 的 `TerminalBlockLabels` 要求提供 `commandLine(line: number): string` 格式化函数。此前,调用者不提供命令行的无障碍名称。现在,每条横向溢出的命令行使用这个名称标识可通过键盘聚焦的分组;能够完整显示的行(包括折行后的行)不会增加 Tab 停靠点。这会影响渲染 `TerminalBlock` 的自定义 UI 插件。 + +## 迁移 + +1. 为传给 `TerminalBlock` 的每个 labels 对象添加 `commandLine`。参数是原始命令文本的行号,从一开始。 +2. 通过插件的语言字典返回本地化名称,例如“命令第 1 行”。名称与原始命令文本分开保留。 +3. 重新构建插件。确认长命令可通过键盘聚焦并使用方向键滚动,短命令或折行后的命令则不会增加 Tab 停靠点。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.i18n.yaml new file mode 100644 index 0000000000..d50e824fe7 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.md +/: + en: 123b457f6ec93005 + zh: aa0366d7aae638ca +/select-native-or-ptc-tool-presentation: + en: 493e892beb7aecb2 + zh: 149d7678dde936bb +/select-native-or-ptc-tool-presentation/change: + en: a72ed2baec1e7e5b + zh: a54c9b0c1e88829f +/select-native-or-ptc-tool-presentation/migration: + en: 64379f963e886e64 + zh: 18b46f7287598a9e diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.md new file mode 100644 index 0000000000..c6a2ce9328 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.md @@ -0,0 +1,21 @@ +--- +kind: upgrade-guide +description: "Tool presentation accepts native or ptc; the mixed both mode is rejected." +--- + +# Select native or PTC tool presentation + +English | [中文](guide.zh.md) + +## Change + +The `mode` fields of `@deepseek-ai/dsh-tools` and `@deepseek-ai/dsh-agent-tool-presentation` accept only `native` or `ptc`. The `both` value is rejected. This also affects `DSH_TOOLS_MODE=both`, which supplies the tools configuration through the shipped profile patches. + +`native` exposes visible tools as direct function calls. `ptc` exposes `run_code`; programs call the visible tools through the generated SDK. Different agent presets can continue using different modes in the same application. + +## Migration + +1. In `cordis.yml`, profile `cordis.patch.yml`, or a custom overlay, replace `mode: both` with `mode: native` for direct calls or `mode: ptc` for program-based calls. +2. Replace `DSH_TOOLS_MODE=both` with `DSH_TOOLS_MODE=native` or `DSH_TOOLS_MODE=ptc`. For preset-specific selection, update the preset's `@deepseek-ai/dsh-agent-tool-presentation` row instead. +3. Update code that calls `ctx.tools.presentAs('both')` to select `native` or `ptc`. In PTC programs, keep calls inside `run_code`; do not emit the SDK function names as direct model tool calls. +4. Start the affected profile and confirm that configuration loads. Native requests expose the permitted direct tools; PTC requests expose `run_code` and its SDK instructions. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.zh.md new file mode 100644 index 0000000000..cd19e9a47f --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/tool-presentation-mode/guide.zh.md @@ -0,0 +1,21 @@ +--- +kind: upgrade-guide +description: "工具呈现方式仅接受 native 或 ptc;混合的 both 模式会被拒绝。" +--- + +# 选择原生或 PTC 工具呈现方式 + +[English](guide.md) | 中文 + +## 变更 + +`@deepseek-ai/dsh-tools` 和 `@deepseek-ai/dsh-agent-tool-presentation` 的 `mode` 字段仅接受 `native` 或 `ptc`。`both` 值会被拒绝。这也影响 `DSH_TOOLS_MODE=both`,该环境变量通过随附的 profile 补丁提供工具配置。 + +`native` 将可见工具公开为直接函数调用。`ptc` 公开 `run_code`,程序通过生成的 SDK 调用可见工具。同一应用中的不同 agent preset 仍可使用不同模式。 + +## 迁移 + +1. 在 `cordis.yml`、profile 的 `cordis.patch.yml` 或自定义覆盖文件中,将 `mode: both` 替换为用于直接调用的 `mode: native`,或用于程序调用的 `mode: ptc`。 +2. 将 `DSH_TOOLS_MODE=both` 替换为 `DSH_TOOLS_MODE=native` 或 `DSH_TOOLS_MODE=ptc`。若需按 preset 选择,请改为更新该 preset 的 `@deepseek-ai/dsh-agent-tool-presentation` 行。 +3. 将调用 `ctx.tools.presentAs('both')` 的代码改为选择 `native` 或 `ptc`。在 PTC 程序中,将调用保留在 `run_code` 内;不要将 SDK 函数名称作为模型的直接工具调用发出。 +4. 启动受影响的 profile,确认配置可加载。原生请求公开获准的直接工具;PTC 请求公开 `run_code` 及其 SDK 说明。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.i18n.yaml new file mode 100644 index 0000000000..9345a236bb --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.md +/: + en: bd716a636d3631a4 + zh: 7a3a4de1d3669d57 +/migrate-working-directory-configuration-and-tool-presentation: + en: 0c783661ea6973cb + zh: 087e487812b4933b +/migrate-working-directory-configuration-and-tool-presentation/change: + en: d2738fe9f819599d + zh: 4eaacaeadcd80adf +/migrate-working-directory-configuration-and-tool-presentation/migration: + en: 01ae6e89e3312de8 + zh: 503981500691b714 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.md new file mode 100644 index 0000000000..d51f48713c --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.md @@ -0,0 +1,25 @@ +--- +kind: upgrade-guide +description: "Session working directories replace built-in cwd configuration, and tool presentation metadata also runs for nested PTC calls." +--- + +# Migrate working-directory configuration and tool presentation + +English | [中文](guide.zh.md) + +## Change + +The next release removes the loop's built-in `{{cwd}}` prompt variable and the `cwd` configuration key from `@deepseek-ai/dsh-subagent-acp` and `@deepseek-ai/dsh-subagent-dsh-sdk`. Custom prompt templates that retain an unresolved `{{cwd}}` fail before a model request. A Session's current execution directory is supplied as required user context and is persisted separately from its original `Session.header.cwd`. + +New subagents use their start request's `cwd` when provided, otherwise they capture the parent's current directory. Existing processes keep their own directories. Changing the execution directory does not change the original project or grant additional write permissions. + +Tool `output.presentationMeta` also runs for successful nested PTC calls. Authors whose projection assumes only native top-level calls are affected. A projection error fails that nested call with `ToolOutputError`; canonical program values and model content remain unchanged. + +## Migration + +1. In home, profile, or invocation `personaSuffix` configuration and preset `suffix` fields, remove the directory clause that references `{{cwd}}`, retaining unrelated text. Omit or clear a suffix that becomes empty. The [working-directory service](../../../../packages/session/working-directory/README.md) supplies the directory context. +2. Remove `config.cwd` from ACP and DSH-SDK subagent provider rows in your profile patches. To select a directory for one child, pass `cwd` in its [start request](../../../subsystems/subagent.md). To change the parent's execution directory, use `working_directory({ cd: "/absolute/project" })` before starting the child. +3. Run `working_directory({})` in the affected Session and confirm the reported directory. Start a child without an override and check its execution directory; use an explicit start `cwd` when the child must use another checkout. Read `Session.header.cwd` only for original project identity or permission roots, rather than current-directory operations. +4. Keep custom `output.presentationMeta` projections pure and return JSON-safe metadata from their arguments and canonical result. Exercise both a native call and a nested PTC call, and confirm the recorded metadata identifies the operation's actual target without changing its result. + +Dynamic context providers are evaluated again at request admission, after pre-step listeners and route preparation. Return current state without relying on one evaluation per step. Transform registered context inside its provider; admission refresh replaces its cached text while preserving assembly-only context contributions and the accepted system prompt, tool schemas, and variables. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.zh.md new file mode 100644 index 0000000000..d8a985a9bd --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-alpha.1/working-directory/guide.zh.md @@ -0,0 +1,25 @@ +--- +kind: upgrade-guide +description: "Session 工作目录替代内置 cwd 配置,工具展示元数据也会在嵌套 PTC 调用中生成。" +--- + +# 迁移工作目录配置与工具展示 + +[English](guide.md) | 中文 + +## 变更 + +下一版本移除 loop 的内置 `{{cwd}}` 提示词变量,以及 `@deepseek-ai/dsh-subagent-acp` 和 `@deepseek-ai/dsh-subagent-dsh-sdk` 的 `cwd` 配置键。自定义提示词模板保留未解析的 `{{cwd}}` 时,会在模型请求发出前失败。Session 当前执行目录作为必需的 user context 提供,并与原始 `Session.header.cwd` 分开持久化。 + +新子代理使用启动请求中显式提供的 `cwd`,未提供时捕获父代理的当前目录。已有进程保留各自的目录。切换执行目录不会改变原始项目,也不会授予额外写权限。 + +工具 `output.presentationMeta` 也会在成功的嵌套 PTC 调用中运行。投影仅假设原生顶层调用的作者需要调整实现。投影异常会使该次嵌套调用以 `ToolOutputError` 失败;程序读取的规范值与模型内容不变。 + +## 迁移 + +1. 在 home、profile 或本次调用的 `personaSuffix` 配置,以及 preset 的 `suffix` 字段中,移除引用 `{{cwd}}` 的目录语句,保留其他文本。suffix 变为空时省略或清空该字段。[工作目录服务](../../../../packages/session/working-directory/README.zh.md)负责提供目录上下文。 +2. 从 profile patch 中的 ACP 与 DSH-SDK 子代理 provider 行移除 `config.cwd`。为单个子代理选择目录时,在其[启动请求](../../../subsystems/subagent.zh.md)中传入 `cwd`。改变父代理执行目录时,先调用 `working_directory({ cd: "/absolute/project" })`,再启动子代理。 +3. 在相关 Session 中调用 `working_directory({})` 并确认报告的目录。启动一个不带目录覆盖的子代理并检查其执行目录;子代理需要另一个 checkout 时,显式提供启动 `cwd`。仅用 `Session.header.cwd` 读取原始项目标识或权限根目录,当前目录操作使用工作目录服务。 +4. 保持自定义 `output.presentationMeta` 投影纯净,并从调用参数和规范结果返回可用于 JSON 的元数据。分别执行原生调用和嵌套 PTC 调用,确认记录的元数据指向操作的实际目标,同时不改变调用结果。 + +动态上下文 provider 会在请求准入时再次求值,晚于 pre-step 监听器与路由准备。返回当前状态,不依赖每个步骤只求值一次。对已注册上下文的转换应在其 provider 内完成;准入刷新会替换缓存文本,同时保留仅由组装添加的上下文,以及已接纳的系统提示词、工具 schema 与变量。 diff --git a/docs/user/guide/github-review.i18n.yaml b/docs/user/guide/github-review.i18n.yaml index c2c413d88c..921406a949 100644 --- a/docs/user/guide/github-review.i18n.yaml +++ b/docs/user/guide/github-review.i18n.yaml @@ -9,14 +9,14 @@ en: 6784d85f90231608 zh: 3e62e9cadfdc2c18 /create-review-sessions-from-github-webhooks/start-dsh: - en: 5751931f5c89b1c0 - zh: 867950515a152d47 + en: 6c2c8394bfaf5d11 + zh: a863fa2ad6bc48bb /create-review-sessions-from-github-webhooks/expose-the-dedicated-endpoint: en: f18a2969b3082da9 zh: 87d3d0e58d25a62f /create-review-sessions-from-github-webhooks/rule-behavior: - en: e90b3061a15fdedc - zh: a8e9549e732314f6 + en: 3306087b78170a99 + zh: 7c7b0baefa1dc303 /create-review-sessions-from-github-webhooks/programmatic-extensions: en: d448fc3e440a0ec6 zh: 18568898933ef054 diff --git a/docs/user/guide/github-review.md b/docs/user/guide/github-review.md index 69c61df95c..981458d36b 100644 --- a/docs/user/guide/github-review.md +++ b/docs/user/guide/github-review.md @@ -22,20 +22,17 @@ export DSH_GITHUB_WEBHOOK_SECRET="$(openssl rand -hex 32)" printf '%s\n' "$DSH_GITHUB_WEBHOOK_SECRET" ``` -From a development checkout: +Install both experimental packages into the Web profile first. Profiles do not install peers automatically; the GitHub adapter needs the webhook runtime explicitly: ```sh +dsh plugin --profile web add @deepseek-ai/dsh-webhook @deepseek-ai/dsh-webhook-github export DSH_GITHUB_REVIEW_WORKSPACE=/path/to/deepseek-harness -pnpm dsh web --patch apps/cli/config/examples/github-review/cordis.yml +dsh web --patch "${DSH_HOME:-$HOME/.dsh}/profiles/web/node_modules/@deepseek-ai/dsh-webhook-github/examples/github-review/cordis.yml" ``` -An installed DSH uses the same overlay through an absolute path: +The adapter distributes the example, which loads from the profile's installation directory so its rule module can resolve profile-installed peers. Do not run the rule from an arbitrary directory outside the profile. To customize it, copy both example files to `$DSH_HOME/profiles/web/github-review/`, edit the configuration, and point `--patch` at that copy of `cordis.yml`; use `~/.dsh` when `DSH_HOME` is unset. -```sh -dsh web --patch /absolute/path/to/github-review/cordis.yml -``` - -For a permanent profile, place `github-ready-review-rule.mjs` beside `$DSH_HOME/profiles/web/cordis.patch.yml`, append the rows from `cordis.yml` to that patch, and start with `dsh web`. The shipped CLI already contains both webhook packages; the overlay alone activates them. +The repository example lives under `packages/experimental/webhook-github/examples/github-review/`. The CLI does not install webhook packages or list them as Official plugins; installing the packages and supplying the overlay are separate steps. ## Expose the dedicated endpoint @@ -65,7 +62,7 @@ Active: yes ## Rule behavior -The rule accepts only source `primary-github`, repository `deepseek-harness/deepseek-harness`, event `pull_request`, and action `ready_for_review`. It passes the exact head SHA plus selected PR fields to the review prompt, labeling the JSON as untrusted metadata and forbidding file, branch, PR, or GitHub mutation. +The rule accepts only source `primary-github`, repository `deepseek-ai/deepseek-harness`, event `pull_request`, and action `ready_for_review`. It passes the exact head SHA plus selected PR fields to the review prompt, labeling the JSON as untrusted metadata and forbidding file, branch, PR, or GitHub mutation. The Session request selects the `standard` agent preset and `read-only` permission preset. `workspacePath` is canonicalized through `WorkspaceRegistry.create()`, so the first matching delivery creates the Web Workspace when absent and later deliveries reuse it. @@ -89,7 +86,7 @@ It can also map repositories to different local paths: ```js const workspacePath = { - 'deepseek-harness/deepseek-harness': '/path/to/deepseek-harness', + 'deepseek-ai/deepseek-harness': '/path/to/deepseek-harness', 'deepseek-harness/dsh-sdk': '/path/to/dsh-sdk', }[payload.repository.full_name] if (workspacePath === undefined) return null diff --git a/docs/user/guide/github-review.zh.md b/docs/user/guide/github-review.zh.md index 114aa6546d..4ad240f91a 100644 --- a/docs/user/guide/github-review.zh.md +++ b/docs/user/guide/github-review.zh.md @@ -22,20 +22,17 @@ export DSH_GITHUB_WEBHOOK_SECRET="$(openssl rand -hex 32)" printf '%s\n' "$DSH_GITHUB_WEBHOOK_SECRET" ``` -在开发 checkout 中运行: +先把两个实验包安装到 Web profile。Profile 不会自动安装 peer;GitHub 适配器需要显式安装 webhook 运行时: ```sh +dsh plugin --profile web add @deepseek-ai/dsh-webhook @deepseek-ai/dsh-webhook-github export DSH_GITHUB_REVIEW_WORKSPACE=/path/to/deepseek-harness -pnpm dsh web --patch apps/cli/config/examples/github-review/cordis.yml +dsh web --patch "${DSH_HOME:-$HOME/.dsh}/profiles/web/node_modules/@deepseek-ai/dsh-webhook-github/examples/github-review/cordis.yml" ``` -安装版 DSH 通过绝对路径使用同一 overlay: +示例随 GitHub 适配器发布,并从 profile 的安装目录加载,因此规则模块能解析该 profile 安装的 peer。不要把规则单独放到 profile 之外的任意目录运行。需要定制时,将示例的两个文件复制到 `$DSH_HOME/profiles/web/github-review/`,修改其配置并把 `--patch` 指向复制后的 `cordis.yml`;未设置 `DSH_HOME` 时使用 `~/.dsh`。 -```sh -dsh web --patch /absolute/path/to/github-review/cordis.yml -``` - -对于永久 profile,把 `github-ready-review-rule.mjs` 放在 `$DSH_HOME/profiles/web/cordis.patch.yml` 旁边,把 `cordis.yml` 中的行追加到该 patch,然后运行 `dsh web`。随附 CLI 已经包含两个 webhook 包;只需 overlay 即可激活它们。 +仓库中的示例位于 `packages/experimental/webhook-github/examples/github-review/`。Webhook 包不随 CLI 安装,也不出现在官方插件列表中;安装包与提供 overlay 是两个独立步骤。 ## 暴露专用端点 @@ -65,7 +62,7 @@ Active: yes ## 规则行为 -规则只接受来源 `primary-github`、仓库 `deepseek-harness/deepseek-harness`、事件 `pull_request` 与动作 `ready_for_review`。它会把精确 head SHA 和选定 PR 字段传给评审提示词,把 JSON 标为不受信任的元数据,并禁止修改文件、分支、PR 或 GitHub 状态。 +规则只接受来源 `primary-github`、仓库 `deepseek-ai/deepseek-harness`、事件 `pull_request` 与动作 `ready_for_review`。它会把精确 head SHA 和选定 PR 字段传给评审提示词,把 JSON 标为不受信任的元数据,并禁止修改文件、分支、PR 或 GitHub 状态。 Session 请求选择 `standard` agent preset 与 `read-only` permission preset。`workspacePath` 通过 `WorkspaceRegistry.create()` 规范化,因此第一次匹配交付会在 Workspace 不存在时创建它,后续交付会复用它。 @@ -89,7 +86,7 @@ if (!response.ok || (await response.json()).automaticReview !== true) return nul ```js const workspacePath = { - 'deepseek-harness/deepseek-harness': '/path/to/deepseek-harness', + 'deepseek-ai/deepseek-harness': '/path/to/deepseek-harness', 'deepseek-harness/dsh-sdk': '/path/to/dsh-sdk', }[payload.repository.full_name] if (workspacePath === undefined) return null diff --git a/docs/user/guide/index.i18n.yaml b/docs/user/guide/index.i18n.yaml index 618d4d762b..1a71676270 100644 --- a/docs/user/guide/index.i18n.yaml +++ b/docs/user/guide/index.i18n.yaml @@ -15,5 +15,5 @@ en: e1cb5a05bf6e109d zh: 8b181b0fecbc0517 /use-the-web-ui/continue: - en: 0e74e287e009b401 - zh: 932f5475154c202c + en: 6b244134309ca89c + zh: 971fa14939d88c6b diff --git a/docs/user/guide/index.md b/docs/user/guide/index.md index fb97f8cec8..988a4bf9bc 100644 --- a/docs/user/guide/index.md +++ b/docs/user/guide/index.md @@ -26,5 +26,6 @@ The agent can read and edit workspace files, run commands, delegate work, and ma - [Configure models](./providers.md) - [Use the Python SDK](./python-sdk.md) +- [Publish the Web UI behind a reverse proxy](./public-deployments.md) - [Use other CLI modes](../../../apps/cli/README.md) - [Develop a plugin](../develop/basic/index.md) diff --git a/docs/user/guide/index.zh.md b/docs/user/guide/index.zh.md index 6cbfce0b88..81c73a420d 100644 --- a/docs/user/guide/index.zh.md +++ b/docs/user/guide/index.zh.md @@ -26,5 +26,6 @@ Agent(智能体)可以读取和编辑工作区文件、运行命令、委派 - [配置模型](./providers.zh.md) - [使用 Python SDK](./python-sdk.zh.md) +- [在反向代理之后发布 Web UI](./public-deployments.zh.md) - [使用其他 CLI 模式](../../../apps/cli/README.zh.md) - [开发插件](../develop/basic/index.zh.md) diff --git a/docs/user/guide/public-deployments.i18n.yaml b/docs/user/guide/public-deployments.i18n.yaml new file mode 100644 index 0000000000..7bd0d94673 --- /dev/null +++ b/docs/user/guide/public-deployments.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for public-deployments.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/user/guide/public-deployments.md +/publish-the-web-ui-behind-a-reverse-proxy: + en: 15b92e92d2526f37 + zh: b58079cf6eee78cc +/publish-the-web-ui-behind-a-reverse-proxy/what-public-url-advertises: + en: 576292b93ccf299a + zh: ded5adc55396869c +/publish-the-web-ui-behind-a-reverse-proxy/what-the-proxy-must-do: + en: 8dd9d910a93fe54d + zh: 1e9e21d9949c8ab2 +/publish-the-web-ui-behind-a-reverse-proxy/trust-the-authority-browsers-use: + en: 907b5f90c9860733 + zh: dcbffd3f00d6c64f +/publish-the-web-ui-behind-a-reverse-proxy/secure-the-external-leg: + en: a833880da5c8bbf4 + zh: 6f9bd565b7266241 diff --git a/docs/user/guide/public-deployments.md b/docs/user/guide/public-deployments.md new file mode 100644 index 0000000000..22bf5f8b0c --- /dev/null +++ b/docs/user/guide/public-deployments.md @@ -0,0 +1,33 @@ +# Publish the Web UI behind a reverse proxy + +English | [中文](public-deployments.zh.md) + +`dsh --profile web` serves the GUI over plain HTTP on a loopback port, so a browser on another machine cannot reach it, and the process knows nothing about the address you do use. A reverse proxy in front of it owns that external leg — the public host name, TLS, and the path prefix it strips before forwarding to the listener — and `--public-url` tells DSH which address browsers use: + +```sh +dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example +``` + +## What `--public-url` advertises + +`--public-url` accepts an `http://` or `https://` root with an optional mount prefix and normalizes it to end in `/`. It supplies the printed and opened startup URL, `DSH_WEB_URL`, and the web-surface orientation. The webserver keeps serving origin-root routes and never learns the mount. The `publicUrl` config field publishes the same advertisement. + +## What the proxy must do + +- **Preserve the browser-facing `Host`.** The fence compares the received `Host` against the accepted authorities, so the proxy forwards it unchanged instead of rewriting it to the listener's address. +- **Strip the mount prefix.** The listener answers origin-root routes, so a request for `/ui/api/...` must arrive as `/api/...`. +- **Forward upgrades.** Every request and WebSocket upgrade the page opens must reach the listener with its `Upgrade` and `Connection` headers intact; TLS terminates on the proxy's external leg when it serves HTTPS. +- **Rewrite cookie scope.** The backend always issues host-only `Path=/` cookies without `Secure`; the proxy rewrites `Path` to the mount (`/ui/`) and adds `Secure` on its HTTPS leg. +- **Redirect the bare mount.** `/ui/` is the only entry: it alone exchanges the launch token for the session cookie, and it or `/ui/index.html` serves the document to a browser that already holds that cookie, because the served document resolves URLs against its own directory. A request to `/ui` must arrive as `/ui/`, and the stripped backend cannot reconstruct that external path. + +## Trust the authority browsers use + +The fence accepts loopback plus every authority `--trusted-host` names. A browser that reaches the deployment under any other authority gets 403 for every API call, however correct the proxy is, so name the browser-visible authority with `--trusted-host`; advertising it with `--public-url` is display only and does not admit it. An entry without a port matches any port, which suits a tunnel that binds a different one each time. The fence only admits the request; the launch token in the printed URL and the signed session cookie authenticate it. + +Neither the advertised URL nor the fence protects the listening port itself, so restrict the port to the trusted proxy or network. + +## Secure the external leg + +Terminate TLS at the proxy. An `http://` advertised root sends the launch token and the session cookie unencrypted, and an `https://` root encrypts only the browser-to-proxy leg. The printed URL carries a process credential, so share it only with intended users. + +The [Web app reference](../../../packages/bundle/web-app/README.md#public-deployments) owns the `--public-url` and `--trusted-host` command-line contract and the `publicUrl` and `trustedHosts` fields. diff --git a/docs/user/guide/public-deployments.zh.md b/docs/user/guide/public-deployments.zh.md new file mode 100644 index 0000000000..54abc1e30a --- /dev/null +++ b/docs/user/guide/public-deployments.zh.md @@ -0,0 +1,33 @@ +# 在反向代理之后发布 Web UI + +[English](public-deployments.md) | 中文 + +`dsh --profile web` 在 loopback 端口上以明文 HTTP 提供 GUI,因此其他机器上的浏览器无法访问它,进程也无从得知你实际使用的地址。它前面的反向代理拥有这条外部链路——公开主机名、TLS,以及转发到监听器之前剥离的路径前缀——`--public-url` 则告诉 DSH 浏览器使用的是哪个地址: + +```sh +dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example +``` + +## `--public-url` 公告什么 + +`--public-url` 接受可带挂载前缀的 `http://` 或 `https://` 根,并把它归一化为以 `/` 结尾。它提供打印与打开的启动 URL、`DSH_WEB_URL` 与 web 表层定位。webserver 继续提供 origin-root 路由,且从不了解挂载。`publicUrl` 配置字段发布同样的公告。 + +## 代理必须做什么 + +- **保留浏览器可见的 `Host`。** 栅栏把收到的 `Host` 与被接受的 authority 比对,因此代理应原样转发,而不要改写成监听器的地址。 +- **剥离挂载前缀。** 监听器应答的是 origin-root 路由,因此对 `/ui/api/...` 的请求必须以 `/api/...` 抵达。 +- **转发升级请求。** 页面发出的每个请求与 WebSocket 升级都必须连同其 `Upgrade` 与 `Connection` 头抵达监听器;代理对外提供 HTTPS 时,TLS 在其外部链路上终止。 +- **改写 cookie 作用域。** 后端始终签发不带 `Secure` 的 host-only `Path=/` cookie;代理把 `Path` 改写为挂载(`/ui/`),并仅在其 HTTPS 链路上添加 `Secure`。 +- **重定向裸挂载。** `/ui/` 是唯一入口:只有它把启动 token 换成会话 cookie;已持有该 cookie 的浏览器可由它或 `/ui/index.html` 取得文档,因为所提供的文档以自身目录解析 URL。对 `/ui` 的请求必须以 `/ui/` 抵达,而被剥离路径的后端无法重建该外部路径。 + +## 信任浏览器使用的 authority + +栅栏接受 loopback,以及每个由 `--trusted-host` 点名的 authority。浏览器若以其他任何 authority 访问部署,无论代理多么正确,每个 API 调用都会得到 403,因此请用 `--trusted-host` 点名浏览器可见的 authority;用 `--public-url` 公告它只是展示,并不等于接纳。不带端口的条目匹配任意端口,适合每次绑定不同端口的隧道。栅栏只放行请求;打印 URL 中的启动 token 与签名会话 cookie 才完成认证。 + +无论是公告 URL 还是栅栏都不保护监听端口本身,因此请把端口限制在可信代理或网络内。 + +## 保护外部链路 + +在代理处终止 TLS。`http://` 公告根会以明文发送启动 token 与会话 cookie,而 `https://` 根只加密浏览器到代理这一段。打印的 URL 携带进程凭据,只应与预期用户分享。 + +[Web 应用参考](../../../packages/bundle/web-app/README.zh.md#public-deployments)负责 `--public-url` 与 `--trusted-host` 的命令行约定,以及 `publicUrl` 与 `trustedHosts` 字段。 diff --git a/docs/web-styling.i18n.yaml b/docs/web-styling.i18n.yaml index 277ae53c32..26a4c7ade1 100644 --- a/docs/web-styling.i18n.yaml +++ b/docs/web-styling.i18n.yaml @@ -9,11 +9,11 @@ en: dc8c41fbf9869179 zh: e45976a585cb9c86 /web-ui-style-reference/component-rules: - en: e6072b2fcbf69de6 - zh: bc9ed31961812451 + en: 3a35065f2be9de06 + zh: 41291bba391fb515 /web-ui-style-reference/corner-radii-and-settings-cards: en: 118972f6c2bd6ce4 zh: 85f8416b03e4dd8b /web-ui-style-reference/changing-the-system: - en: ca70c6f962c290c8 - zh: 800d8289116c1535 + en: 71501119492a3ad4 + zh: 87bf6c9406d0d9ed diff --git a/docs/web-styling.md b/docs/web-styling.md index 12f036a764..564801d476 100644 --- a/docs/web-styling.md +++ b/docs/web-styling.md @@ -13,7 +13,7 @@ Global style sheets belong in `ui-theme/src/styles/`. Component styles live besi ## Component rules -- Reuse the control before restyling one: the [ui-primitives component catalog](../packages/client/ui-primitives/README.md#component-catalog) is the only channel that crosses feature packages, and a deliberate visual difference belongs in a prop there rather than in a second copy ([decision](../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.md)). +- Reuse the control before restyling one: the [ui-primitives component catalog](../packages/client/ui-primitives/README.md#component-catalog) is the only channel that crosses feature packages, and a deliberate visual difference belongs in a prop there rather than in a second copy ([reference](../packages/client/ui-primitives/README.md)). - Use CSS Modules and `clsx`; do not add a component library or Tailwind. - Use `--dsw-alias-*` semantic tokens in feature components. Do not copy static palette values or write literal colors there. - Keep theme selectors out of feature component CSS. Light/dark overrides belong to the theme owner. @@ -25,9 +25,9 @@ Global style sheets belong in `ui-theme/src/styles/`. Component styles live besi - Elevated surfaces (menus, popovers, modals, panels, floating buttons, the composer) set `border: 0` and take `box-shadow: var(--dsw-elevation-panel)`, `var(--dsw-elevation-prominent)`, or the composer's `var(--dsw-elevation-soft)` (larger blur at lower alpha): the 0.5px hairline stroke is the first shadow layer, and `--dsw-elevation-stroke-color` rebinds or suppresses it per surface or state. Never pair a `--dsw-alias-border-*` border with an lv/elevation shadow — the ui-theme elevation spec rejects the pairing; state-colored borders (warn panels) stay real borders. - Existing and new dropdown, context, submenu, and selection menus use `Menu` or wrap custom content in `MenuSurface`. The material is the theme-owned `--dsw-menu-surface-fill` plus `--dsw-menu-backdrop-filter`; feature and platform CSS must not override either. The ui-theme menu spec rejects unwrapped menu/listbox renderers, material overrides, and token redefinitions. The schedule-owned `TaskMenu` and `ClockPicker` retain their existing containers and material as explicit exceptions ([owner](../packages/client/ui-schedule/README.md)). - Modal backdrops, including settings and confirmation dialogs, retain their translucent dark mask without blur. The theme sets `--dsw-mask-blur` to `none`; Desktop-owned modal overlays also leave the parent page unfiltered. -- Menu background filtering stays on an isolated layer so nested menus and fixed overlays retain their positioning and backdrop. On macOS, `MenuSurface` adds an opaque backing behind page content only within each menu’s bounds, allowing Chromium to blur content over native vibrancy; CSS anchors follow placement and size, and unmounting removes the backing. Overlays without this backing use `--dsw-specific-menu`, whose theme-owned macOS fill is 94% opaque to keep underlying text from bleeding through. Other elevated surfaces using that fill pair it with the same filter on their own isolated background when they contain fixed overlays ([decision](../.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md)). +- Menu background filtering stays on an isolated layer so nested menus and fixed overlays retain their positioning and backdrop. On macOS, `MenuSurface` adds an opaque backing behind page content only within each menu’s bounds, allowing Chromium to blur content over native vibrancy; CSS anchors follow placement and size, and unmounting removes the backing. Overlays without this backing use `--dsw-specific-menu`, whose theme-owned macOS fill is 94% opaque to keep underlying text from bleeding through. Other elevated surfaces using that fill pair it with the same filter on their own isolated background when they contain fixed overlays. - Flat borders and separators that use a neutral `--dsw-alias-border-*` token draw at `0.5px` — buttons, inputs, cards, row dividers, and separators drawn as filled boxes (menu separators, the conversation header seam, markdown `hr`, vertical rails) share the hairline weight, which Chromium paints as one device pixel. Dashed affordances and state-colored borders keep 1px; spinner ring tracks keep their width through the spec's explicit allowlist. The ui-theme elevation spec rejects wider neutral solid borders. -- Clickable artifact links (markdown anchors, prose file mentions, web source and fetch links, produced-file chips, workflow member links) color through `--dsw-alias-link` at `font-weight: 500`, with no underline at rest and a dotted 3px-offset underline on hover/focus. Compact Thinking Markdown keeps tertiary text color and a resting dotted underline ([compact presentation](../.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.md)). Text-leading anchors also lead with the ui-primitives `LinkIconMedium` category glyph riding `currentColor`, which for a well-known external host is that site's own mark instead of the globe; workflow member links and image-only anchors carry no glyph, and tool-row file links keep their grey dotted affordance. +- Clickable artifact links (markdown anchors, prose file mentions, web source and fetch links, produced-file chips, workflow member links) color through `--dsw-alias-link` at `font-weight: 500`, with no underline at rest and a dotted 3px-offset underline on hover/focus. Compact Thinking Markdown keeps tertiary text color and a resting dotted underline ([compact presentation](../packages/client/ui-primitives/README.md#rendering-agent-output)). Text-leading anchors also lead with the ui-primitives `LinkIconMedium` category glyph riding `currentColor`, which for a well-known external host is that site's own mark instead of the globe; workflow member links and image-only anchors carry no glyph, and tool-row file links keep their grey dotted affordance. ## Corner radii and settings cards @@ -36,4 +36,4 @@ The [DSH unified corner-radius standard](ui-radius.md) defines the radius scale, ## Changing the system -Add or change a shared token in the owning `ui-theme` sheet, then consume its semantic alias from feature packages. Update the owning package reference when a public styling contract changes. Visual behavior follows the [testing policy](testing.md); the [styling-system Agent Note](../.agents/notes/implemented/process/2026-07-19-web-styling-system.md) records framework rationale. +Add or change a shared token in the owning `ui-theme` sheet, then consume its semantic alias from feature packages. Update the owning package reference when a public styling contract changes. Visual behavior follows the [testing policy](testing.md); the [archived styling-system Agent Note](../.agents/notes/archived/process/2026-07-19-web-styling-system.md) records framework rationale. diff --git a/docs/web-styling.zh.md b/docs/web-styling.zh.md index 1d1238cb39..e75def975c 100644 --- a/docs/web-styling.zh.md +++ b/docs/web-styling.zh.md @@ -13,7 +13,7 @@ ## 组件规则 -- 重新设计控件样式之前先复用控件:[ui-primitives 组件目录](../packages/client/ui-primitives/README.zh.md#component-catalog)是唯一跨功能包的通道,有意的视觉差异应作为那里的一个 prop,而不是另起一份拷贝([决定](../.agents/notes/implemented/architecture/2026-09-05-shared-client-control-primitives.zh.md))。 +- 重新设计控件样式之前先复用控件:[ui-primitives 组件目录](../packages/client/ui-primitives/README.zh.md#component-catalog)是唯一跨功能包的通道,有意的视觉差异应作为那里的一个 prop,而不是另起一份拷贝([参考](../packages/client/ui-primitives/README.zh.md))。 - 使用 CSS Modules 和 `clsx`;不得添加组件库或 Tailwind。 - 功能组件使用 `--dsw-alias-*` 语义 token。不得复制静态色板值或在其中写入颜色字面量。 - 功能组件 CSS 不得包含主题选择器。明暗主题覆盖属于主题所有方。 @@ -25,9 +25,9 @@ - 高层级表面(菜单、浮层、对话框、面板、悬浮按钮、输入框)设 `border: 0` 并使用 `box-shadow: var(--dsw-elevation-panel)`、`var(--dsw-elevation-prominent)` 或输入框专用的 `var(--dsw-elevation-soft)`(更大模糊、更低透明度):0.5px 发丝描边是第一层投影,`--dsw-elevation-stroke-color` 可按表面或状态重绑或抑制描边。不得将 `--dsw-alias-border-*` border 与 lv/elevation 投影配对——ui-theme 的 elevation spec 会拒绝;状态色 border(warn 面板)保持真 border。 - 存量和新增的下拉菜单、上下文菜单、子菜单及选择菜单使用 `Menu`,自定义内容用 `MenuSurface` 包裹。材质由主题的 `--dsw-menu-surface-fill` 和 `--dsw-menu-backdrop-filter` 组成;功能及平台 CSS 不得覆盖两者。ui-theme 菜单检查拒绝未使用共享容器的 menu/listbox 渲染、材质覆盖和 token 重定义。计划任务自有的 `TaskMenu` 与 `ClockPicker` 是明确例外,保留现有容器和材质([所属包](../packages/client/ui-schedule/README.zh.md))。 - 设置、确认框等模态弹窗保留黑色半透明遮罩,不模糊背景。主题将 `--dsw-mask-blur` 设为 `none`;Desktop 自有模态浮层也不对父页面施加模糊。 -- 菜单背景模糊绘制在独立图层上,保证嵌套菜单和 fixed 浮层的定位及背景采样。macOS 上,`MenuSurface` 仅在各菜单范围内、页面内容后方添加实色底层,使 Chromium 能在原生透明材质上模糊页面内容;CSS 锚点跟随位置及尺寸,卸载时移除底层。没有该底层的浮层使用 `--dsw-specific-menu`,主题在 macOS 上将其设为 94% 不透明度,防止下方文字透出。其他使用该填充的浮起表面也须配套同一模糊;包含 fixed 浮层时,将两者绘制在独立背景层上([决策](../.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.zh.md))。 +- 菜单背景模糊绘制在独立图层上,保证嵌套菜单和 fixed 浮层的定位及背景采样。macOS 上,`MenuSurface` 仅在各菜单范围内、页面内容后方添加实色底层,使 Chromium 能在原生透明材质上模糊页面内容;CSS 锚点跟随位置及尺寸,卸载时移除底层。没有该底层的浮层使用 `--dsw-specific-menu`,主题在 macOS 上将其设为 94% 不透明度,防止下方文字透出。其他使用该填充的浮起表面也须配套同一模糊;包含 fixed 浮层时,将两者绘制在独立背景层上。 - 使用中性 `--dsw-alias-border-*` token 的平面边框与分割线一律 `0.5px`——按钮、输入框、卡片、行分割线,以及以填充盒绘制的分隔线(菜单分隔、对话标题栏接缝、markdown `hr`、竖向轨道线)共用发丝线粗细,Chromium 将其绘制为一个设备像素。dashed 记号与状态色 border 保持 1px;spinner 圆环经 spec 的显式豁免保留原宽度。更宽的中性 solid border 会被 ui-theme elevation spec 拒绝。 -- 可点击产物链接(Markdown 锚点、正文文件引用、网页来源与抓取链接、产物 chips、workflow 成员链接)经 `--dsw-alias-link` 着色、`font-weight: 500`,默认无下划线,hover/focus 时为 3px offset 的点状下划线。紧凑 Thinking Markdown 保持 tertiary 文字色和默认点状下划线([紧凑展示](../.agents/notes/implemented/bug-fix/2026-09-17-thinking-markdown.zh.md))。带文字的锚点另以 ui-primitives 的 `LinkIconMedium` 分类图形(随 `currentColor`)作前置;目的地是已知外部站点时改用该站点自己的标记而非地球;workflow 成员链接与只包图片的锚点不带图形,工具行文件链接保持其灰色点线示能。 +- 可点击产物链接(Markdown 锚点、正文文件引用、网页来源与抓取链接、产物 chips、workflow 成员链接)经 `--dsw-alias-link` 着色、`font-weight: 500`,默认无下划线,hover/focus 时为 3px offset 的点状下划线。紧凑 Thinking Markdown 保持 tertiary 文字色和默认点状下划线([紧凑展示](../packages/client/ui-primitives/README.zh.md#rendering-agent-output))。带文字的锚点另以 ui-primitives 的 `LinkIconMedium` 分类图形(随 `currentColor`)作前置;目的地是已知外部站点时改用该站点自己的标记而非地球;workflow 成员链接与只包图片的锚点不带图形,工具行文件链接保持其灰色点线示能。 ## 圆角与设置卡片 @@ -36,4 +36,4 @@ ## 变更系统 -在所属 `ui-theme` 样式表中添加或修改共享 token,然后在功能包中使用其语义别名。公共样式约定发生变化时,更新所属包的参考文档。视觉行为遵循[测试策略](testing.zh.md);[样式系统 Agent Note](../.agents/notes/implemented/process/2026-07-19-web-styling-system.zh.md) 记录框架依据。 +在所属 `ui-theme` 样式表中添加或修改共享 token,然后在功能包中使用其语义别名。公共样式约定发生变化时,更新所属包的参考文档。视觉行为遵循[测试策略](testing.zh.md);[已归档的样式系统 Agent Note](../.agents/notes/archived/process/2026-07-19-web-styling-system.md) 记录框架依据。 diff --git a/native/system/package.json b/native/system/package.json index a4fd9bcf0c..3fd10861dd 100644 --- a/native/system/package.json +++ b/native/system/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "license": "BSD-3-Clause", - "packageManager": "pnpm@11.7.0", + "packageManager": "pnpm@11.28.5", "scripts": { "build": "pnpm build:ts", "build:ts": "tsc -b", @@ -27,8 +27,8 @@ }, "devDependencies": { "@deepseek-ai/node-addon-system": "workspace:~", - "@types/node": "^26.0.1", - "tsx": "^4.20.6", + "@types/node": "^26.4.1", + "tsx": "^4.23.13", "typescript": "^6.0.3" } } diff --git a/native/system/scripts/pack-release.mjs b/native/system/scripts/pack-release.mjs index 17290d669a..4a6e5b116f 100644 --- a/native/system/scripts/pack-release.mjs +++ b/native/system/scripts/pack-release.mjs @@ -95,12 +95,8 @@ try { const publishOrder = []; for (const dir of dirs) { const manifest = readJson(path.join(root, dir, 'package.json')); - // Platform packages are packed with npm: pnpm pack (observed on 11.7.0) - // normalizes file modes and STRIPS the executable bit, which ships a - // launcher no consumer can spawn; npm pack preserves it. Platform packages - // have no dependencies by construction, so they need none of pnpm's - // workspace-protocol conversion — the entry packages do, and carry no - // executables, so they keep pnpm pack. + // Platform packages have no workspace dependencies; npm preserves their + // executable modes. Entry packages need pnpm's workspace version conversion. if (platformSet.has(dir)) { run('npm', ['pack', `./${dir}`, '--pack-destination', destination], packRoot); } else { diff --git a/native/system/test/release-packing.test.js b/native/system/test/release-packing.test.js index 86ccf03078..4ffd3f79e8 100644 --- a/native/system/test/release-packing.test.js +++ b/native/system/test/release-packing.test.js @@ -15,7 +15,7 @@ function fixture(t) { t.after(() => fs.rmSync(workspace, { recursive: true, force: true })); const dir = path.join(workspace, 'native/system'); fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(path.join(workspace, 'package.json'), `${JSON.stringify({ private: true, packageManager: 'pnpm@11.7.0' })}\n`); + fs.writeFileSync(path.join(workspace, 'package.json'), `${JSON.stringify({ private: true, packageManager: 'pnpm@11.28.5' })}\n`); fs.writeFileSync(path.join(workspace, 'pnpm-workspace.yaml'), 'packages:\n - native/system\n - native/system/packages/*\n'); fs.writeFileSync(path.join(workspace, 'LICENSE'), workspaceLicense); const scratch = path.join(workspace, 'scratch'); @@ -26,7 +26,7 @@ function fixture(t) { fs.writeFileSync(path.join(dir, file), `${JSON.stringify(value, null, 2)}\n`); }; const repository = (name) => ({ type: 'git', url: publicRepository, directory: `native/system/packages/${name}` }); - writeJson('package.json', { name: 'native-packing-fixture', private: true, version: '1.2.3', packageManager: 'pnpm@11.7.0' }); + writeJson('package.json', { name: 'native-packing-fixture', private: true, version: '1.2.3', packageManager: 'pnpm@11.28.5' }); writeJson('packages/linux-x64/package.json', { name: '@fixture/native-linux-x64', version: '1.2.3', repository: repository('linux-x64'), os: ['linux'], cpu: ['x64'], files: ['bin/', 'prebuilds.json'], diff --git a/package.json b/package.json index f5ce66dc9e..837bf1d905 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@deepseek-ai/dsh-root", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "license": "MIT", "private": true, "type": "module", - "packageManager": "pnpm@11.7.0", + "packageManager": "pnpm@11.28.5", "engines": { "node": "^22.19.0 || >=24.0.0" }, @@ -19,11 +19,11 @@ "scripts": { "prepare:primary-runtime": "tsx scripts/primary-runtime/prepare.ts", "build": "tsx scripts/build.ts", - "build:bench": "npm run build:native-system && npm run build:lib && tsdown --config benchmarks/tsdown.config.ts", + "build:bench": "npm run build:native-system && npm run build:lib && tsdown --config-loader native --config benchmarks/tsdown.config.ts", "build:official": "tsx scripts/build.ts --profile official", "build:lib": "pnpm run build:lib:host && pnpm run build:lib:client", - "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --env.DSH_BUILD_FACE host && pnpm --filter @deepseek-ai/dsh-desktop run bundle", - "build:lib:client": "tsc -b tsconfig.client.json && tsdown --env.DSH_BUILD_FACE client", + "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --config-loader native --env.DSH_BUILD_FACE host && pnpm --filter @deepseek-ai/dsh-desktop run bundle", + "build:lib:client": "tsc -b tsconfig.client.json && tsdown --config-loader native --env.DSH_BUILD_FACE client", "build:web": "pnpm --filter @deepseek-ai/dsh-web-frontend run build", "build:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run build", "dev:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run dev", @@ -32,8 +32,10 @@ "package:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run package", "package:desktop:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:dir", "package:desktop:mac:arm64": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:arm64", + "package:desktop:mac:arm64:unsigned": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:arm64:unsigned", "package:desktop:mac:arm64:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:arm64:dir", "package:desktop:mac:x64": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:x64", + "package:desktop:mac:x64:unsigned": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:x64:unsigned", "package:desktop:mac:x64:dir": "pnpm --filter @deepseek-ai/dsh-desktop run package:mac:x64:dir", "package:desktop:win:x64": "pnpm --filter @deepseek-ai/dsh-desktop run package:win:x64", "package:desktop:win:x64:unsigned": "pnpm --filter @deepseek-ai/dsh-desktop run package:win:x64:unsigned", @@ -60,6 +62,7 @@ "test:e2e": "vitest run --config vitest.e2e.config.ts", "test:bench": "npm run build:bench && npm run build:web && npm run test:bench:built", "test:bench:built": "vitest run --config vitest.bench.config.ts", + "test:bench:memory": "tsx scripts/run-memory-pressure-bench.ts", "test:expected": "vitest run --config vitest.expected.config.ts", "test:expected:refresh": "DSH_SNAPSHOT=refresh vitest run --config vitest.expected.config.ts", "test:approval-policy": "node --test .github/review-ownership/check-approval.test.mjs .github/review-ownership/blame-ownership.test.mjs .github/review-ownership/author-weight.test.mjs", @@ -110,9 +113,7 @@ "verify-package-paths": "tsx scripts/verify-package-paths.ts", "verify-dsh-package-licenses": "tsx scripts/verify-dsh-package-licenses.ts", "verify-config-source-ownership": "tsx scripts/verify-config-source-ownership.ts", - "verify-package-invariants": "tsx scripts/verify-package-invariants.ts", "verify-package-meta": "tsx scripts/verify-package-meta.ts", - "verify-built-package-invariants": "node scripts/verify-built-package-invariants.mjs", "verify-package-readme-model-experience": "tsx scripts/verify-package-readme-model-experience.ts", "verify-package-readme-summaries": "tsx scripts/verify-package-readme-summaries.ts", "verify-mermaid": "tsx scripts/verify-mermaid.ts", @@ -146,12 +147,15 @@ "verify-client-ui-i18n": "tsx scripts/verify-client-ui-i18n.ts", "verify-client-route-resolution": "tsx scripts/verify-client-route-resolution.ts", "verify-no-bare-dispatcher": "tsx scripts/verify-no-bare-dispatcher.ts", + "verify-plugin-record-callers": "tsx scripts/verify-plugin-record-callers.ts", "verify-cordis-config": "tsx scripts/verify-cordis-config.ts", "rescope-vendor": "tsx scripts/rescope-vendor.ts", "rescope-vendor:check": "tsx scripts/rescope-vendor.ts --check", "verify-client-domain-graph": "tsx scripts/verify-client-domain-graph.ts", "gen-tsconfig-paths": "tsx scripts/gen-tsconfig-paths.ts", "verify-tsconfig-paths": "tsx scripts/gen-tsconfig-paths.ts --check", + "gen-cli-help": "tsx scripts/gen-cli-help.ts", + "verify-cli-help": "tsx scripts/gen-cli-help.ts --check", "gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts", "verify-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts --check", "gen-cordis-api": "tsx scripts/gen-cordis-api.ts", @@ -186,8 +190,6 @@ "gen-third-party-notices": "tsx scripts/gen-third-party-notices.ts", "verify-third-party-notices": "tsx scripts/gen-third-party-notices.ts --check", "gen-module-graph": "tsx scripts/gen-module-graph.ts", - "gen-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts", - "verify-scoped-events": "node --max-old-space-size=4096 --import tsx/esm scripts/gen-scoped-events.ts --check", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", "doc-sync": "tsx scripts/run-gates.ts doc-sync", @@ -205,9 +207,17 @@ "mock:llm": "node --import tsx packages/test-support/llm-mock-server/src/bin.ts", "start:web": "node --import tsx/esm apps/cli/src/bin.ts web", "dev:web": "tsx scripts/dev-web.ts --poll", - "postinstall": "node scripts/install-lefthook.mjs" + "dev:bundle-registry": "tsx scripts/dev-bundle-registry.ts", + "postinstall": "node scripts/install-lefthook.mjs", + "gen-official-bundle-catalog": "tsx scripts/gen-official-bundle-catalog.ts", + "verify-official-bundle-catalog": "tsx scripts/gen-official-bundle-catalog.ts --check", + "verify-product-use": "tsx scripts/verify-product-use.ts" }, "devDependencies": { + "@deepseek-ai/dsh-hooks-claude-code": "workspace:*", + "@deepseek-ai/dsh-hooks-codex": "workspace:*", + "@deepseek-ai/dsh-tool-ralph": "workspace:*", + "@deepseek-ai/dsh-tool-terminal": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-browser-use": "workspace:*", "@deepseek-ai/dsh-experimental-browser-use-runtime": "workspace:*", @@ -217,49 +227,57 @@ "@deepseek-ai/dsh-computer-use": "workspace:*", "@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp": "workspace:*", "@deepseek-ai/dsh-experimental-computer-use-cua-driver-native": "workspace:*", + "@deepseek-ai/dsh-experimental-claude-code-mods": "workspace:*", "@deepseek-ai/dsh-llm-replay": "workspace:*", "@deepseek-ai/dsh-mcp-client": "workspace:*", "@deepseek-ai/dsh-tool-session-query": "workspace:*", "@deepseek-ai/dsh-web-fetch-http": "workspace:*", "@stylistic/eslint-plugin": "^5.10.0", "@testing-library/dom": "^10.4.1", - "@testing-library/react": "^16.3.2", + "@testing-library/react": "^16.3.3", "@types/js-yaml": "^4.0.9", "@types/jsdom": "^28.0.3", "@types/mdast": "^4.0.4", - "@types/node": "^22.20.0", + "@types/node": "^22.20.1", + "@types/semver": "^7.8.0", "@types/spdx-expression-parse": "^4.0.0", - "@vitest/coverage-v8": "^4.1.8", + "@vitest/coverage-v8": "^4.1.11", "@yao-pkg/pkg": "6.21.0", - "@yarnpkg/cli-dist": "4.17.1", - "eslint-plugin-sonarjs": "^4.1.0", - "execa": "^10.0.0", + "@yarnpkg/cli-dist": "4.18.0", + "eslint-plugin-sonarjs": "^4.2.0", + "execa": "^10.0.1", "extract-zip": "^2.0.1", - "fast-check": "^4.8.0", + "fast-check": "^4.9.0", "fflate": "^0.8.2", "istanbul-lib-report": "^3.0.1", - "js-yaml": "^4.2.0", - "jscpd": "^5.0.12", + "js-yaml": "^4.3.2", + "jscpd": "^5.1.2", "jsdom": "29.1.1", - "lefthook": "^2.1.9", - "lightningcss": "^1.32.0", + "lefthook": "^2.1.12", + "lightningcss": "^1.33.0", "mdast-util-from-markdown": "^2.0.3", "mdast-util-gfm": "^3.1.0", - "mermaid": "11.16.0", + "mermaid": "11.17.2", "micromark-extension-gfm": "^3.0.0", - "oxlint": "1.76.0", + "oxlint": "1.81.0", "oxlint-tsgolint": "7.0.2001", - "pnpm": "11.7.0", - "publint": "^0.3.21", - "smol-toml": "^1.7.1", + "pnpm": "11.28.5", + "publint": "^0.3.24", + "semver": "^7.8.5", + "smol-toml": "^1.9.0", "spdx-expression-parse": "^5.0.0", - "tar": "^7.5.0", + "tar": "^7.5.22", "tsdown": "^0.22.2", - "tsx": "^4.22.4", + "tsx": "^4.23.13", "typescript": "^6.0.3", + "vite": "8.2.2", "vite-tsconfig-paths": "^6.1.1", - "vitest": "^4.1.8", + "vitest": "^4.1.11", "@deepseek-ai/dsh-skill-office": "workspace:*", - "@deepseek-ai/dsh-tool-workspace-dependencies": "workspace:*" + "@deepseek-ai/dsh-tool-workspace-dependencies": "workspace:*", + "@deepseek-ai/dsh-tool-working-directory": "workspace:*", + "@deepseek-ai/dsh-working-directory": "workspace:*", + "@deepseek-ai/dsh-experimental-worktree": "workspace:*", + "@deepseek-ai/dsh-experimental-tool-worktree": "workspace:*" } } diff --git a/packages/AGENTS.md b/packages/AGENTS.md index f0c49d55e5..8c071dac2e 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -16,11 +16,10 @@ These package-specific rules supplement the repo-wide [conventions](../AGENTS.md - **Apply bounds to the complete result.** Enforce byte, token, item, and time limits where the complete emitted or retained value, including wrappers and metadata, is known; test tiny and exact limits, oversized single chunks, and multibyte byte limits. - **Registry contributions prove disposal** through the HMR-safety test required by [testing policy](../docs/testing.md): dispose the fiber and observe removal. - **Specs run concurrently** in forked workers beside other gate processes. Own each acquired port, path, and child process through teardown; a spec that passes only when run alone is a defect in the spec ([execution model](../docs/testing.md#how-specs-execute)). -- **Publish `./invariant` only for diverging observations.** Check an owned relation under the manifest name. Otherwise omit wiring and give the package-specific README reason. Empty companions and ignored reporters fail [`verify-package-invariants`](../.agents/notes/implemented/simplification/2026-08-28-omit-unneeded-invariant-companions.md). [Naming rules](../docs/cookbook/adding-a-package.md#name-the-role-that-exists): -- **Package tsconfig:** extends `tsconfig.base.json` (Client: `tsconfig.base.client.json`), sets `rootDir: src` and `outDir: lib/types`, references workspace dependencies, references `runtime-diagnostics/invariants` only when the package publishes `./invariant`, and registers in one aggregate. Packages with distinct Host and Client compiler faces use `tsconfig.host.json` and `tsconfig.client.json` leaves plus a solution-only root; ordinary two-entry Client plugins do not split ([layout](../docs/development.md#typescript-project-layout)). +- **Package tsconfig:** extends `tsconfig.base.json` (Client: `tsconfig.base.client.json`), sets `rootDir: src` and `outDir: lib/types`, references workspace dependencies, and registers in one aggregate. Packages with distinct Host and Client compiler faces use `tsconfig.host.json` and `tsconfig.client.json` leaves plus a solution-only root; ordinary two-entry Client plugins do not split ([layout](../docs/development.md#typescript-project-layout)). - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - Update package README and JSDoc contracts in the same commit as behavior, and verify them against code with [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md). Group READMEs declare subsystem ownership through a canonical English page link or justified [exemption](../scripts/verify-subsystem-pages.ts). diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index f9879d6dcd..4412f95938 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -15,17 +15,17 @@ en: ffc55792d6e922e1 zh: 83bd759638857e2d /packages/package-groups: - en: d865abbfa65d1d4f - zh: ea079b795b51dd55 + en: ff0b0b8388457f47 + zh: b9ace22bb3cbad48 /packages/release-expectations: - en: 1ab880da3642bde1 - zh: 6e168322f026a9cf + en: 236081c1659b25ab + zh: 809110adf003802e /packages/dependencies: en: 9576e7c486bf123f zh: fedd18da3e27ad00 /packages/package-readme-contracts: - en: b36505dacbfe2ea1 - zh: 51e25517fae092ba + en: 9f35ef77d1b4c388 + zh: 39cdc988e87a96c2 /packages/dev-note: en: 5de1d6304e00c58f zh: 8d8225361cfa29e5 diff --git a/packages/README.md b/packages/README.md index cfb5ea6c74..9485cc1001 100644 --- a/packages/README.md +++ b/packages/README.md @@ -40,7 +40,7 @@ Every package lives in exactly one group; new packages join existing groups, and | [`subprocess/`](subprocess/README.md) | Subprocess capability family: Service Definition + local process-tree provider | | [`ssh/`](ssh/README.md) | POSIX remote connection with paired filesystem, subprocess and sandbox providers | | [`shell/`](shell/README.md) | Bash capability family: executor seam, local impl, model-facing tools | -| [`terminal/`](terminal/README.md) | Persistent PTY capability family: owner-scoped sessions, local implementation, model-facing tools | +| [`terminal/`](terminal/README.md) | Persistent PTY service with owner-scoped sessions and a local shell backend | | [`ptc-runtime/`](ptc-runtime/README.md) | PTC execution capability family: Service Definition + sandboxed Node provider + PTC mode Consumer | | [`computer-use/`](computer-use/README.md) | Exclusive named desktop-provider registration | | [`browser-use/`](browser-use/README.md) | Exclusive named browser-provider registration | @@ -53,9 +53,8 @@ Every package lives in exactly one group; new packages join existing groups, and | [`context/`](context/README.md) | Model-visible request context: workspace instructions, time context, references | | [`subagent/`](subagent/README.md) | Subagent capability family: provider-registry contract and model-facing delegation tools | | [`jobs/`](jobs/README.md) | Generic background-job runtime and model-facing job control tools | -| [`experimental/`](experimental/README.md) | Pre-stable prototypes with explicit private exceptions | -| [`workflow/`](workflow/README.md) | Workflow seam, PTC process engine, and model-facing `workflow`/`ralph` tools | -| [`webhook/`](webhook/README.md) | Verified external events, trusted rules, and fire-and-forget Workspace Sessions | +| [`experimental/`](experimental/README.md) | Capabilities under evaluation; [status and support policy](experimental/README.md#status) | +| [`workflow/`](workflow/README.md) | Workflow seam, PTC process engine, and the model-facing `workflow` tool | | [`web/`](web/README.md) | Web capability family: seam, search/fetch providers, model-facing web tools | | [`document/`](document/README.md) | Shared Host Office-to-PDF conversion | | [`attachment/`](attachment/README.md) | Durable attachment identity, validation, local content-addressed storage | @@ -67,7 +66,6 @@ Every package lives in exactly one group; new packages join existing groups, and | [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | | [`extensions/`](extensions/README.md) | Agent runtime self-modification: live plugin/service inspection and model-written mount/unmount | | [`mcp/`](mcp/README.md) | External Model Context Protocol servers exposed as native tools | -| [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | | [`session/`](session/README.md) | Durable session data plane: persistence seam + backends, projection seam, log-backed titles, session reporting | | [`session-query/`](session-query/README.md) | Session retrieval family: logical corpus, bounded reads, lineage, semantic filtering, SQLite full-text search | | [`settings/`](settings/README.md) | User-settings seam + file-backed provider | @@ -81,7 +79,6 @@ Every package lives in exactly one group; new packages join existing groups, and | [`host/`](host/README.md) | Web GUI host services, directory picking, application launch, plugin inventory, and product telemetry | | [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | | [`test-support/`](test-support/README.md) | Test infrastructure (testkits, replay, Loader smokes) | -| [`runtime-diagnostics/`](runtime-diagnostics/README.md) | Runtime diagnostics: package-owned invariant checks and reports | | [`util/`](util/README.md) | Low-level zero-dependency utilities shared across groups (`Branded`, home/path helpers, timeout, retention) | ----- @@ -89,7 +86,7 @@ Every package lives in exactly one group; new packages join existing groups, and ## Release expectations -Most groups are product — stable API. The exceptions: `experimental/` publishes without stability or support promises, and `test-support/`, `runtime-diagnostics/`, and `util/` are support with lower compatibility expectations. +Product groups have maintained product roles, while their public APIs remain pre-stable. [Experimental status](experimental/README.md#status) is independent of publication, installation, and optional delivery. Nonexperimental runtime packages must have product use or an explicit [package classification](../scripts/product-package-policy.ts); SDK, build, test, and declaration infrastructure have separate classifications. ----- @@ -105,7 +102,7 @@ The dependency graph is generated: [docs/module-graph.md](../docs/module-graph.m ## Package README contracts -Every package README covers purpose, configuration, extension points, and [Model Experience](../docs/cookbook/adding-a-package.md#4-write-the-package-readme) unless the model-agnostic [omission allowlist](../scripts/verify-package-readme-model-experience.ts) exempts it. It also carries `## Known Limitations and Deferred Work` or uses its [allowlist](../scripts/verify-package-readme-limitations.ts). Package conventions — exports, service access, invariants, tests — live in [packages/AGENTS.md](AGENTS.md). +Every package README covers purpose, configuration, extension points, and [Model Experience](../docs/cookbook/adding-a-package.md#4-write-the-package-readme) unless the model-agnostic [omission allowlist](../scripts/verify-package-readme-model-experience.ts) exempts it. It also carries `## Known Limitations and Deferred Work` or uses its [allowlist](../scripts/verify-package-readme-limitations.ts). Package conventions — exports, service access, tests — live in [packages/AGENTS.md](AGENTS.md). ----- diff --git a/packages/README.zh.md b/packages/README.zh.md index 2edb62d8cd..ec2a9b81f8 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -40,7 +40,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`subprocess/`](subprocess/README.zh.md) | 子进程能力系列:Service Definition + 本地进程树提供方 | | [`ssh/`](ssh/README.zh.md) | POSIX 远端连接及配套文件系统、子进程与沙箱提供方 | | [`shell/`](shell/README.zh.md) | Bash 能力系列:执行器 seam、本地实现、面向模型的工具 | -| [`terminal/`](terminal/README.zh.md) | 持久 PTY 能力系列:限定所有者范围的会话、本地实现、面向模型的工具 | +| [`terminal/`](terminal/README.zh.md) | 持久 PTY 服务:限定所有者范围的会话与本地 shell 后端 | | [`ptc-runtime/`](ptc-runtime/README.zh.md) | PTC 执行能力族:Service Definition + 沙箱 Node 提供方 + PTC mode Consumer | | [`computer-use/`](computer-use/README.zh.md) | 按名称独占注册桌面提供方 | | [`browser-use/`](browser-use/README.zh.md) | 按名称独占注册浏览器提供方 | @@ -53,9 +53,8 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`context/`](context/README.zh.md) | 模型可见请求上下文:workspace 指令、时间上下文、引用 | | [`subagent/`](subagent/README.zh.md) | subagent 能力系列:提供方注册表约定和面向模型的委托工具 | | [`jobs/`](jobs/README.zh.md) | 通用后台任务运行时和面向模型的作业控制工具 | -| [`experimental/`](experimental/README.zh.md) | 预稳定原型,包含显式私有例外 | -| [`workflow/`](workflow/README.zh.md) | 工作流 seam、PTC 进程引擎、面向模型的 `workflow`/`ralph` 工具 | -| [`webhook/`](webhook/README.zh.md) | 已验证外部事件、受信规则与即发即弃 Workspace 会话 | +| [`experimental/`](experimental/README.zh.md) | 仍在评估中的能力;[状态与支持策略](experimental/README.zh.md#status) | +| [`workflow/`](workflow/README.zh.md) | 工作流 seam、PTC 进程引擎、面向模型的 `workflow` 工具 | | [`web/`](web/README.zh.md) | Web 能力系列:seam、搜索/获取提供方、面向模型的 Web 工具 | | [`document/`](document/README.zh.md) | 共享宿主 Office 到 PDF 转换 | | [`attachment/`](attachment/README.zh.md) | 持久附件标识、校验、本地内容寻址存储 | @@ -67,7 +66,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`bundle/`](bundle/README.zh.md) | 可安装的 `dsh --profile` 补丁层 | | [`extensions/`](extensions/README.zh.md) | agent 运行时自修改:实时插件/服务检查与模型所写挂载/卸载 | | [`mcp/`](mcp/README.zh.md) | 将外部 Model Context Protocol 服务器的工具接入为原生工具 | -| [`hooks/`](hooks/README.zh.md) | 钩子桥接 + 共享的 Claude Code/Codex 线协议库 | | [`session/`](session/README.zh.md) | 持久会话数据平面:持久化 seam + 后端、投影 seam、基于日志的标题、会话上报 | | [`session-query/`](session-query/README.zh.md) | 会话检索系列:逻辑语料库、有界读取、血缘、语义过滤、SQLite 全文搜索 | | [`settings/`](settings/README.zh.md) | 用户设置 seam + 基于文件的提供方 | @@ -81,7 +79,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`host/`](host/README.zh.md) | Web GUI 宿主服务、目录选择、应用启动、插件清单和产品遥测 | | [`client/`](client/README.zh.md) | web GUI 浏览器半侧:shell、协议层、对象服务、slot、`ui-*` 插件 | | [`test-support/`](test-support/README.zh.md) | 测试基础设施(testkit、回放、Loader 冒烟测试) | -| [`runtime-diagnostics/`](runtime-diagnostics/README.zh.md) | 运行时诊断:按包归属的运行时不变式检查与报告 | | [`util/`](util/README.zh.md) | 组间共享的低层零依赖工具(`Branded`、home/路径辅助函数、超时、留存) | ----- @@ -89,7 +86,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 发布预期 -大多数组属于产品组,提供稳定 API。例外:`experimental/` 发布时不提供稳定性或支持承诺,`test-support/`、`runtime-diagnostics/` 与 `util/` 是兼容性预期较低的支持组。 +产品组承担维护职责,但其公开 API 仍处于预稳定阶段。[实验性状态](experimental/README.zh.md#status)独立于发布、安装和可选交付。非实验运行时包必须被产品使用或在[包分类策略](../scripts/product-package-policy.ts)中明确列出;SDK、构建、测试与声明基础设施分别分类。 ----- @@ -105,7 +102,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 ## 包 README 约定 -每个包 README 都覆盖用途、配置、扩展点与[模型体验](../docs/cookbook/adding-a-package.zh.md#4-write-the-package-readme),列入模型无关[省略允许清单](../scripts/verify-package-readme-model-experience.ts)的包除外。它还要包含 `## Known Limitations and Deferred Work`,或列入其[允许清单](../scripts/verify-package-readme-limitations.ts)。包约定——导出、服务访问、不变式、测试——见 [packages/AGENTS.md](AGENTS.md)。 +每个包 README 都覆盖用途、配置、扩展点与[模型体验](../docs/cookbook/adding-a-package.zh.md#4-write-the-package-readme),列入模型无关[省略允许清单](../scripts/verify-package-readme-model-experience.ts)的包除外。它还要包含 `## Known Limitations and Deferred Work`,或列入其[允许清单](../scripts/verify-package-readme-limitations.ts)。包约定——导出、服务访问、测试——见 [packages/AGENTS.md](AGENTS.md)。 ----- diff --git a/packages/acp/acp/README.i18n.yaml b/packages/acp/acp/README.i18n.yaml index 1b3bf620a1..68c18de182 100644 --- a/packages/acp/acp/README.i18n.yaml +++ b/packages/acp/acp/README.i18n.yaml @@ -27,8 +27,8 @@ en: 4ed8e37834267edb zh: a32d5dedb4e8e919 /deepseek-ai-dsh-acp/use-this-package/protocol-contract: - en: 804b87d8ebed9f8d - zh: 1b0c1c7269cdf040 + en: 29099e63f9a173c6 + zh: 80d98e87f442d335 /deepseek-ai-dsh-acp/understand-the-implementation: en: ea8afe89dbdd1934 zh: 6567979f92f34afd @@ -36,8 +36,8 @@ en: ad90a93d87b6caa4 zh: d7e90af3ea7a678c /deepseek-ai-dsh-acp/understand-the-implementation/source-map: - en: 837330574e01d196 - zh: 46f29717d175f7bb + en: 9f85ae4ecdb5d022 + zh: dc2b911360768b9e /deepseek-ai-dsh-acp/understand-the-implementation/admission-and-prompt-settlement: en: 6485d449b5945b4a zh: 90e56a9b410c9c42 diff --git a/packages/acp/acp/README.md b/packages/acp/acp/README.md index a303967330..fa713b872f 100644 --- a/packages/acp/acp/README.md +++ b/packages/acp/acp/README.md @@ -68,11 +68,15 @@ One connection can run several sessions at once, each independent. The calls a c | `session/resume` | A persisted inactive session whose canonical workspace is verified before composition; its log is restored without replaying old updates. | | `session/close` | Quiescent cancellation, update draining, descendant disposal, persistence flush, and disposal of only the addressed Agent scope. | | `session/set_config_option` | A serialized update to the advertised `model` or `reasoning_effort`, returning the complete resulting state. | -| `session/prompt` | Ordered text, resource links, and supported images, one prompt at a time per session; settlement follows Agent idle and ordered update delivery. | +| `session/prompt` | Ordered text, resource links, and supported images, one prompt at a time per session; settlement follows root and descendant completion plus ordered update delivery. | | `session/cancel` / `$/cancel_request` | The prompt-owned cancellation path; without an ACP prompt in flight it cancels autonomous work, while unknown session ids are no-ops. | | `session/update` | Committed assistant messages and thoughts, generic tool lifecycle, configuration changes, and context usage, serialized per session. | | `session/request_permission` | A permission prompt with one-shot allow/reject choices; your client can answer automatically. | +A prompt remains in flight while managed descendants run, while their results trigger later root turns, and while committed output drains. Root work accepted during that output drain joins the same activity interval before completion. Its completion includes the latest root stop reason and any assistant output delivery failure; only the original prompt turn pins the model selection captured at admission. Cancellation stops the root activity present at that call and waits for that activity and its queued output. Later child messages may start autonomous work and do not delay the cancelled response; the session remains available for later prompts. Idle descendants with parked inbox messages remain resident but do not delay prompt completion. Closing the session also drains its descendants. + +A failure to append `turn/start` or `turn/end` rejects the prompt even when a preceding turn completed. + Session configuration offers opaque provider/model choices from the live LLM service catalog and a `reasoning_effort` selector when the exact model declares one. A prompt snapshots that selection before asynchronous image admission and pins it across every model step in that turn; a concurrent option change applies to the next turn. ACP clients are trusted controllers: stdio MCP entries authorize their absolute commands and environment, HTTP entries authorize their absolute HTTP(S) URLs and headers, and any initial connection or discovery failure rolls back the unpublished Agent. Unsupported surfaces are omitted or rejected: `session/load`, deletion, fork, additional directories, SSE or ACP-transport MCP, modes, commands, plans, terminals, client filesystem operations, and elicitation. ----- @@ -102,7 +106,6 @@ The decision history lives in the [ACP as an automation-only protocol note](../. | [`src/index.ts`](src/index.ts) | Plugin entry: `Config` schema, `AgentSideConnection` wiring, per-session records, admission and settlement, teardown | | [`src/content.ts`](src/content.ts) | Wire-content admission and projection: image validation, route recheck, prompt reconstruction, assistant block conversion | | [`src/codec.ts`](src/codec.ts) | Pure turn-ending to ACP `stopReason` mapping | -| — | No runtime invariant companion is published; this transport owns no durable package-local event stream; protocol and lifecycle tests cover its mapping. | ### Admission and prompt settlement diff --git a/packages/acp/acp/README.zh.md b/packages/acp/acp/README.zh.md index 7f7f2f43ac..e62e154191 100644 --- a/packages/acp/acp/README.zh.md +++ b/packages/acp/acp/README.zh.md @@ -68,11 +68,15 @@ kind: "package-reference" | `session/resume` | 恢复一个已持久化且非活跃的会话;组合前校验其规范工作区,并恢复日志但不回放旧更新。 | | `session/close` | 停稳式取消、更新 drain、后代释放、持久化 flush,并且只释放指定 Agent 作用域。 | | `session/set_config_option` | 串行更新公布的 `model` 或 `reasoning_effort`,并返回完整结果状态。 | -| `session/prompt` | 有序文本、资源链接与受支持图片,每个会话一次一个提示词;Agent 空闲且有序更新交付后才结算。 | +| `session/prompt` | 有序文本、资源链接与受支持图片,每个会话一次一个提示词;根 Agent 与后代完成且有序更新交付后才结算。 | | `session/cancel` / `$/cancel_request` | 提示词所拥有的取消路径;没有进行中的 ACP 提示词时取消自主工作,未知会话 id 则为空操作。 | | `session/update` | 已提交 assistant 消息与 thought、通用工具生命周期、配置变化与上下文用量,按会话串行交付。 | | `session/request_permission` | 带一次性允许/拒绝选项的权限提示;你的客户端可以自动回答。 | +受管理后代运行期间、它们的结果触发根 Agent 后续轮次期间,以及已提交输出仍在交付期间,提示词保持进行中。输出交付期间接受的根 Agent 工作会加入同一活动区间,并在该区间完成后结算。最终结果包含最新根轮次的停止原因与所有助手输出交付错误;只有原始提示词轮次固定使用接受时捕获的模型选择。取消会停止调用时已有的根 Agent 活动,并等待该活动及其已排队输出完成。之后到达的子 Agent 消息可以启动自主工作,但不会延迟取消响应;会话仍可接受后续提示词。收件箱消息处于停放状态的空闲后代继续驻留,但不延迟提示词完成。关闭会话还会释放其后代。 + +即使前一轮已经完成,追加 `turn/start` 或 `turn/end` 失败仍会使提示词请求失败。 + 会话配置从实时 LLM(大语言模型)服务目录提供不透明的提供方/模型选项,并在确切模型声明推理选项时提供 `reasoning_effort`。提示词会在异步图片准入前快照该选择,并在该轮次的每个模型步骤中固定它;并发选项变更从下一轮次开始生效。ACP 客户端是受信控制器:stdio MCP 条目授权其绝对命令与环境,HTTP 条目授权其绝对 HTTP(S) URL 与 header;初始连接或发现失败会回滚尚未发布的 Agent。不支持的界面会被省略或拒绝:`session/load`、删除、fork、附加目录、SSE(Server-Sent Events)或 ACP 传输 MCP、mode、命令、计划、终端、客户端文件系统操作与 elicitation。 ----- @@ -102,7 +106,6 @@ kind: "package-reference" | [`src/index.ts`](src/index.ts) | 插件入口:`Config` schema、`AgentSideConnection` 接线、按会话记录、准入与结算、清理 | | [`src/content.ts`](src/content.ts) | 协议内容准入与投影:图片校验、路由重查、提示词重建、assistant 块转换 | | [`src/codec.ts`](src/codec.ts) | 轮次结束到 ACP `stopReason` 的纯映射 | -| — | 不发布运行时不变式伴生入口;本传输不拥有持久包内事件流;协议与生命周期测试覆盖其映射。 | ### 准入与提示词结算 diff --git a/packages/acp/acp/package.json b/packages/acp/acp/package.json index 69174fc2bb..5a9e332ce9 100644 --- a/packages/acp/acp/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -59,6 +59,7 @@ "@deepseek-ai/dsh-session-persistence": "workspace:*", "@deepseek-ai/dsh-session-persistence-jsonl": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", + "@deepseek-ai/dsh-subagent": "workspace:*", "@deepseek-ai/dsh-token-meter": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", "@deepseek-ai/dsh-user-approval": "workspace:*" diff --git a/packages/acp/acp/src/index.ts b/packages/acp/acp/src/index.ts index 44ef4741f1..0754df3562 100644 --- a/packages/acp/acp/src/index.ts +++ b/packages/acp/acp/src/index.ts @@ -141,8 +141,12 @@ export function apply(ctx: Context, config: AcpConfig): void { ownedRecord(agent)?.onInboxClaimed(message, turn) }) - ctx.on('agent/error', ({ agent, turn, error }) => { - ownedRecord(agent)?.onAgentError(turn, error) + ctx.on('agent/error', ({ agent, error }) => { + ownedRecord(agent)?.onAgentError(error) + }) + + ctx.on('agent/status', ({ agent, status }) => { + ownedRecord(agent)?.onAgentStatus(status) }) ctx.on('llm/adapters-updated', () => { diff --git a/packages/acp/acp/src/session.ts b/packages/acp/acp/src/session.ts index 3fff075359..9b6ee8fd10 100644 --- a/packages/acp/acp/src/session.ts +++ b/packages/acp/acp/src/session.ts @@ -10,7 +10,7 @@ import { type SessionNotification, type StopReason, } from '@agentclientprotocol/sdk' -import type { Agent, AgentHandle, AgentOptions, ModelSelection } from '@deepseek-ai/dsh-agent' +import type { Agent, AgentHandle, AgentOptions, AgentStatus, ModelSelection } from '@deepseek-ai/dsh-agent' import { createUserMessage, errorChain, type UserMessage } from '@deepseek-ai/dsh-llm' import { type Session, type SessionEvent, type SessionId, type TurnEndReason } from '@deepseek-ai/dsh-session' import { AcpContentError, admitAcpPrompt } from './content.ts' @@ -19,10 +19,12 @@ import { mountAcpMcpServers } from './mcp.ts' import { AcpModelControl } from './model-control.ts' import { assistantUpdates, toolCallUpdate, toolResultUpdate } from './updates.ts' -/** The continuable-subagent teardown used without depending on the subagent package. */ -interface ContinuableDrain { - /** Dispose continuable descendants below exact host-owned parents child-first. */ - drainContinuableDescendants(parents: readonly Agent[]): Promise +/** Subagent completion and teardown without depending on the subagent package. */ +interface SubagentLifecycle { + /** Dispose subagent descendants below exact host-owned parents child-first. */ + drainDescendants(parents: readonly Agent[]): Promise + /** Observe currently managed descendants without closing future admission. */ + waitForChildren(parent: Agent): Promise } /** Inputs shared by fresh and resumed ACP session construction. */ @@ -55,7 +57,8 @@ interface InflightPrompt { admissionDone: Promise finishAdmission: () => void admissionController: AbortController - cancelRequested: boolean + cancellationDone: PromiseWithResolvers + cancelStartedIdle: boolean settlementStarted: boolean outputError: Error | undefined agentError: Error | undefined @@ -234,7 +237,7 @@ export class AcpSession { } /** - * Admit, enqueue, and settle one prompt at whole-Agent quiescence. + * Admit one prompt and settle after the root, descendants, and updates finish. * @param params - standard ACP prompt request for this session. * @param imageEnabled - connection capability advertised at initialization. * @param requestSignal - JSON-RPC request cancellation signal. @@ -260,7 +263,8 @@ export class AcpSession { admissionDone: admission.promise, finishAdmission: admission.resolve, admissionController, - cancelRequested: false, + cancellationDone: Promise.withResolvers(), + cancelStartedIdle: false, settlementStarted: false, outputError: undefined, agentError: undefined, @@ -308,7 +312,7 @@ export class AcpSession { inflight.finishAdmission() } - if (inflight.cancelRequested) { + if (inflight.admissionController.signal.aborted) { this.settleAfterQuiescence(inflight) return { stopReason: await completion.promise } } @@ -343,6 +347,10 @@ export class AcpSession { * @param event - committed durable event. */ onSessionEvent(session: Session, event: SessionEvent): void { + if (event.type === 'turn/start' && this.inflight?.turn !== undefined) { + this.inflight.turn = event.data.turn + this.inflight.endReason = undefined + } try { if (event.type === 'assistant/message') { const inflight = this.inflight?.turn === event.data.turn ? this.inflight : undefined @@ -383,6 +391,7 @@ export class AcpSession { const inflight = this.inflight if (inflight !== undefined && event.type === 'turn/end' && inflight.turn === event.data.turn) { inflight.endReason = event.data.reason + inflight.agentError = undefined } if (event.type === 'turn/end') this.modelControl.releaseTurn(event.data.turn) } @@ -402,19 +411,29 @@ export class AcpSession { /** * Correlate an Agent interval failure with the active ACP prompt. - * @param turn - failed turn number. * @param error - original same-process failure. */ - onAgentError(turn: number, error: unknown): void { + onAgentError(error: unknown): void { const inflight = this.inflight if (inflight === undefined || !inflight.messageQueued) return - // AgentLoop balances an in-turn failure with durable turn/end; settlement - // reads that exact error reason. This slot records interval failures outside it. - if (inflight.turn === turn) return + // A later committed turn/end supersedes this live failure. Failed turn + // start/end appends have no terminal record and keep the failure observable. inflight.agentError = new Error(errorChain(error)) this.settleAfterQuiescence(inflight) } + /** + * Complete cancelled activity without following a later root driver. + * @param status - the emitted transition, which may precede a reentrant wake. + */ + onAgentStatus(status: AgentStatus): void { + const inflight = this.inflight + if (inflight === undefined || !inflight.messageQueued || !inflight.admissionController.signal.aborted) return + // Maintenance remains publicly idle; its next running transition belongs + // to work released after the maintenance task finished. + if (status === 'idle' || inflight.cancelStartedIdle) inflight.cancellationDone.resolve(this.outputTail) + } + /** Await every update queued before this call. */ drainUpdates(): Promise { return this.outputTail @@ -439,12 +458,12 @@ export class AcpSession { } catch (error: unknown) { failures.push(new Error('ACP session activity drain failed', { cause: error })) } - const subagents = this.ctx.get('subagents') as ContinuableDrain | undefined + const subagents = this.ctx.get('subagents') as SubagentLifecycle | undefined try { - await subagents?.drainContinuableDescendants([this.agent]) + await subagents?.drainDescendants([this.agent]) } catch (error: unknown) { - this.ctx.logger.warn(`acp: continuable subagent teardown failed: ${errorChain(error)}`) - failures.push(new Error('continuable subagent teardown failed', { cause: error })) + this.ctx.logger.warn(`acp: subagent teardown failed: ${errorChain(error)}`) + failures.push(new Error('subagent teardown failed', { cause: error })) } try { await this.ctx.sessions.flush(this.agent.session) @@ -474,10 +493,20 @@ export class AcpSession { private cancelPrompt(detail: string): void { const inflight = this.inflight if (inflight === undefined) return - inflight.cancelRequested = true + const firstCancellation = !inflight.admissionController.signal.aborted + if (firstCancellation) inflight.cancelStartedIdle = this.agent.status === 'idle' inflight.admissionController.abort(new Error(detail)) this.settleAfterQuiescence(inflight) - if (inflight.messageQueued) this.agent.cancel({ kind: 'user' }) + if (!inflight.messageQueued) { + inflight.cancellationDone.resolve() + return + } + this.agent.cancel({ kind: 'user' }) + if (firstCancellation) { + const done = inflight.cancellationDone + // Plugin disposal can detach status observers before this activity retires. + void this.agent.whenIdle().then(() => { done.resolve(this.outputTail) }, done.reject) + } } private settleAfterQuiescence(inflight: InflightPrompt): void { @@ -486,13 +515,26 @@ export class AcpSession { void (async () => { await inflight.admissionDone if (inflight.messageQueued) { - await this.agent.whenIdle() - await this.outputTail + const subagents = this.ctx.get('subagents') as SubagentLifecycle | undefined + const cancelled = inflight.cancellationDone.promise + const isCancelled = (): boolean => inflight.admissionController.signal.aborted + const idleAt = (seq: Session['seq']): boolean => this.agent.status === 'idle' && this.agent.session.seq === seq + while (true) { + await Promise.race([this.agent.whenIdle(), cancelled]) + if (isCancelled()) break + const idleSeq = this.agent.session.seq + const children = await Promise.race([subagents?.waitForChildren(this.agent), cancelled]) + if (isCancelled()) break + if (children || !idleAt(idleSeq)) continue + await this.outputTail + if (isCancelled() || idleAt(idleSeq)) break + } + if (isCancelled()) await cancelled } /* v8 ignore next -- this prompt owns the slot until this exact settlement clears it. */ if (this.inflight !== inflight) return this.inflight = undefined - if (inflight.cancelRequested) { + if (inflight.admissionController.signal.aborted) { inflight.resolve('cancelled') return } diff --git a/packages/acp/acp/tests/bridge.spec.ts b/packages/acp/acp/tests/bridge.spec.ts index cd9ede1641..c0f5d73f89 100644 --- a/packages/acp/acp/tests/bridge.spec.ts +++ b/packages/acp/acp/tests/bridge.spec.ts @@ -938,13 +938,13 @@ describe('automation-only ACP bridge', () => { }) it('renders the deployment persona for an ACP-created agent', async () => { - harness = await makeBridgeHarness({ persona: 'Automation persona for {{model}} in {{cwd}}.', script: [textResponse('ok')] }) + harness = await makeBridgeHarness({ persona: 'Automation persona for {{model}}.', script: [textResponse('ok')] }) await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] }) await harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'go' }] }) const head = harness.adapter.requests[0]?.messages[0] expect(head?.role).toBe('system') - expect(head?.content).toContainEqual({ type: 'text', text: expect.stringContaining(`Automation persona for mock in ${process.cwd()}.`) as unknown }) + expect(head?.content.some(part => part.type === 'text' && part.text.includes('Automation persona for mock.'))).toBe(true) }) it('requires one absolute primary workspace', async () => { diff --git a/packages/acp/acp/tests/dispose.spec.ts b/packages/acp/acp/tests/dispose.spec.ts index 325255fb81..f607eabd18 100644 --- a/packages/acp/acp/tests/dispose.spec.ts +++ b/packages/acp/acp/tests/dispose.spec.ts @@ -69,7 +69,7 @@ describe('ACP connection ownership', () => { // A continuable Activation outlives the turn that started it, so the bridge // must release that forest before the agents whose runtime it depends on. harness.ctx.provide('subagents', { - drainContinuableDescendants: (parents: readonly Agent[]) => { + drainDescendants: (parents: readonly Agent[]) => { drainedParents = parents order.push('drained') return Promise.resolve() @@ -92,7 +92,7 @@ describe('ACP connection ownership', () => { const order: string[] = [] const release = Promise.withResolvers() harness.ctx.provide('subagents', { - drainContinuableDescendants: async () => { + drainDescendants: async () => { order.push('drain started') await release.promise order.push('drain finished') @@ -128,7 +128,7 @@ describe('ACP connection ownership', () => { const warnings: string[] = [] harness.ctx.logger.warn = (message: string) => { warnings.push(message) } harness.ctx.provide('subagents', { - drainContinuableDescendants: () => Promise.reject(new Error('activation teardown failed')), + drainDescendants: () => Promise.reject(new Error('activation teardown failed')), } as never) await harness.client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} }) const { sessionId } = await harness.client.newSession({ cwd: process.cwd(), mcpServers: [] }) @@ -136,7 +136,7 @@ describe('ACP connection ownership', () => { await harness.acpFiber.dispose() // A stuck descendant must not strand the bridge's own teardown. - expect(warnings.some(warning => warning.includes('continuable subagent teardown failed'))).toBe(true) + expect(warnings.some(warning => warning.includes('subagent teardown failed'))).toBe(true) expect(harness.ctx.agents.get(SessionId(sessionId))).toBeUndefined() }) diff --git a/packages/acp/acp/tests/turns.spec.ts b/packages/acp/acp/tests/turns.spec.ts index 0dffdc5836..1ef2bc94ce 100644 --- a/packages/acp/acp/tests/turns.spec.ts +++ b/packages/acp/acp/tests/turns.spec.ts @@ -1,8 +1,13 @@ +import { mountWorkingDirectoryFixture } from '../../../subagent/subagent/tests/working-directory-fixture.ts' import { createUserMessage, type StreamChunk } from '@deepseek-ai/dsh-llm' import type { ContextFormed } from '@deepseek-ai/dsh-llm' import { afterEach, describe, expect, it, vi } from 'vitest' import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk' import { SessionId } from '@deepseek-ai/dsh-session' +import { setImmediate } from 'node:timers/promises' +import SubagentRuntime, { NO_START_CAPABILITIES } from '@deepseek-ai/dsh-subagent' +import { defineContentToolFixture } from '@deepseek-ai/dsh-tools' +import { toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import { errorResponse, makeBridgeHarness, @@ -30,6 +35,30 @@ function messageText(harness: BridgeHarness): string { )).join('') } +async function installDelegationTool(ctx: BridgeHarness['ctx']): Promise<() => boolean> { + await mountWorkingDirectoryFixture(ctx) + await ctx.plugin(SubagentRuntime) + ctx.subagents.registerProvider({ + name: 'local-test', capabilities: NO_START_CAPABILITIES, inheritsParentContext: false, + prepareContinuable: () => Promise.resolve({}), + }) + let delegated = false + ctx.tools.register(defineContentToolFixture({ + name: 'delegate_again', description: 'Run a later child.', parameters: {}, + execute: async (_args, exec) => { + if (exec.agent === undefined) throw new Error('delegation fixture requires an Agent') + const child = await ctx.subagents.startActivation({ + provider: 'local-test', label: 'later child', delivery: 'caller', signal: exec.signal, + request: { parent: exec.agent, prompt: [{ type: 'text', text: 'later child task' }] }, + }) + const result = await child.result + delegated = result.stopReason === 'completed' + return [...result.output] + }, + })) + return () => delegated +} + describe('ACP prompt lifecycle', () => { let harness: BridgeHarness | undefined @@ -177,6 +206,33 @@ describe('ACP prompt lifecycle', () => { vi.restoreAllMocks() }) + it.each(['turn/start', 'turn/end'] as const)('rejects a later %s append failure after an earlier completed turn', async (failedEvent) => { + harness = await makeBridgeHarness({ script: [textResponse('waiting'), textResponse('summary')] }) + const sessionId = await newSession(harness) + const parent = harness.ctx.agents.get(SessionId(sessionId))! + const append = parent.session.append.bind(parent.session) + const appendSpy = vi.spyOn(parent.session, 'append') + const failures: number[] = [] + harness.ctx.on('agent/error', ({ turn }) => { failures.push(turn) }) + const waitForChildren = vi.fn().mockImplementationOnce(() => { + appendSpy.mockImplementation(((type: string, ...rest: never[]) => { + if (type === failedEvent) throw new Error(`summary ${failedEvent} unavailable`) + return (append as (...args: never[]) => unknown)(type as never, ...rest) + }) as never) + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'child result' }], source: { kind: 'test' } })) + return Promise.resolve(true) + }).mockResolvedValue(false) + harness.ctx.provide('subagents', { waitForChildren, drainDescendants: vi.fn() } as never) + try { + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate' }] })) + .rejects.toThrow(`summary ${failedEvent} unavailable`) + expect(failures).toEqual([failedEvent === 'turn/start' ? 1 : 2]) + expect(harness.adapter.requests).toHaveLength(failedEvent === 'turn/start' ? 1 : 2) + } finally { + appendSpy.mockRestore() + } + }) + it('settles even when an earlier turn observer throws', async () => { harness = await makeBridgeHarness({ script: [textResponse('answer')] }) harness.ctx.on('session/event', (_session, event) => { @@ -525,6 +581,370 @@ describe('ACP prompt lifecycle', () => { await vi.waitFor(() => { expect(messageText(harness!)).toBe('recovered') }) }) + it.each(['completed', 'error'] as const)('waits for descendants and reports the later %s summary turn', async (outcome) => { + harness = await makeBridgeHarness({ script: [ + textResponse('waiting'), + outcome === 'completed' ? textResponse('summary') : errorResponse('summary failed'), + ] }) + const sessionId = await newSession(harness) + const parent = harness.ctx.agents.get(SessionId(sessionId))! + const waiting = Promise.withResolvers() + const children = Promise.withResolvers() + const waitForChildren = vi.fn() + .mockImplementationOnce(() => { waiting.resolve(undefined); return children.promise }) + .mockResolvedValue(false) + harness.ctx.provide('subagents', { waitForChildren, drainDescendants: vi.fn() } as never) + let settled = false + const prompt = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate' }] }) + const observed = prompt.then(() => { settled = true }, () => { settled = true }) + await waiting.promise + expect(settled).toBe(false) + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'child result' }], source: { kind: 'test' } })) + children.resolve(true) + if (outcome === 'completed') { + await expect(prompt).resolves.toEqual({ stopReason: 'end_turn' }) + expect(messageText(harness)).toBe('waitingsummary') + } else { + await expect(prompt).rejects.toThrow('summary failed') + } + await observed + }) + + it('cancels the child wait without closing the session to its next prompt', async () => { + harness = await makeBridgeHarness({ script: [textResponse('waiting'), textResponse('next')] }) + const sessionId = await newSession(harness) + const waiting = Promise.withResolvers() + const children = Promise.withResolvers() + const waitForChildren = vi.fn() + .mockImplementationOnce(() => { waiting.resolve(undefined); return children.promise }) + .mockResolvedValue(false) + const drainDescendants = vi.fn() + harness.ctx.provide('subagents', { waitForChildren, drainDescendants } as never) + const first = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate' }] }) + await waiting.promise + await harness.client.cancel({ sessionId }) + await expect(first).resolves.toEqual({ stopReason: 'cancelled' }) + expect(drainDescendants).not.toHaveBeenCalled() + children.resolve(true) + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'again' }] })) + .resolves.toEqual({ stopReason: 'end_turn' }) + expect(messageText(harness)).toBe('waitingnext') + }) + + it('releases a cancelled prompt before a late child message finishes and permits another delegation', async () => { + harness = await makeBridgeHarness({ script: [ + textResponse('waiting'), textResponse('late autonomous answer'), + toolCallResponse('delegate-again', 'delegate_again', {}), + textResponse('later child answer'), textResponse('later parent answer'), + ] }) + const ctx = harness.ctx + const delegated = await installDelegationTool(ctx) + const sessionId = await newSession(harness) + const parent = ctx.agents.get(SessionId(sessionId))! + const waiting = Promise.withResolvers() + const children = Promise.withResolvers() + vi.spyOn(ctx.subagents, 'waitForChildren').mockImplementationOnce(() => { + waiting.resolve(undefined) + return children.promise + }) + const lateStarted = Promise.withResolvers() + const releaseLate = Promise.withResolvers() + const stream = harness.adapter.stream.bind(harness.adapter) + vi.spyOn(harness.adapter, 'stream').mockImplementation(async function* (options) { + if (options.messages.at(-1)?.content.some(block => block.type === 'text' && block.text === 'late child result')) { + lateStarted.resolve(undefined) + await releaseLate.promise + } + yield* stream(options) + }) + const cancel = parent.cancel.bind(parent) + let delivered = false + vi.spyOn(parent, 'cancel').mockImplementation((...args) => { + cancel(...args) + if (delivered) return + delivered = true + queueMicrotask(() => { + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'late child result' }], source: { kind: 'test' } })) + children.resolve(true) + }) + }) + let result: { stopReason: string } | undefined + const first = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate' }] }) + .then((value) => { result = value; return value }) + try { + await waiting.promise + await harness.client.cancel({ sessionId }) + await lateStarted.promise + await setImmediate() + expect(result).toEqual({ stopReason: 'cancelled' }) + expect(parent.status).toBe('running') + releaseLate.resolve(undefined) + await parent.whenIdle() + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate again' }] })) + .resolves.toEqual({ stopReason: 'end_turn' }) + expect(delegated()).toBe(true) + expect(messageText(harness)).toContain('later parent answer') + } finally { + releaseLate.resolve(undefined) + await first + } + }) + + it('settles an initially cancelled prompt after its output without waiting for a later summary', async () => { + const script: StreamChunk[][] = [] + harness = await makeBridgeHarness({ script }) + const ctx = harness.ctx + const delegated = await installDelegationTool(ctx) + const ref = await harness.attachments!.saveImage({ data: Uint8Array.of(6), mediaType: 'image/png' }) + ctx.tools.register(defineContentToolFixture({ + name: 'continue_stream', description: 'Continue the interrupted response.', parameters: {}, + execute: () => Promise.resolve([{ type: 'text', text: 'continue until cancelled' }]), + })) + // Interrupted streams retain only text/reasoning, so a completed step supplies the queued image. + script.push([ + { type: 'block-start', index: 1, blockType: 'image' }, + { type: 'block-end', index: 1, block: { type: 'image', attachment: ref } }, + ...toolCallResponse('continue-stream', 'continue_stream', {}), + ], textResponse('late autonomous answer'), toolCallResponse('delegate-again', 'delegate_again', {}), + textResponse('later child answer'), textResponse('later parent answer')) + const streaming = Promise.withResolvers() + const cleanupStarted = Promise.withResolvers() + const releaseCleanup = Promise.withResolvers() + const reading = Promise.withResolvers() + const releaseImage = Promise.withResolvers() + const lateStarted = Promise.withResolvers() + const releaseLate = Promise.withResolvers() + harness.attachments!.beforeRead = () => { + reading.resolve(undefined) + return releaseImage.promise + } + const stream = harness.adapter.stream.bind(harness.adapter) + vi.spyOn(harness.adapter, 'stream').mockImplementation(async function* (options) { + const prompt = options.messages.at(-1)?.content + if (prompt?.some(block => block.type === 'text' && block.text === 'continue until cancelled')) { + const signal = options.signal + if (signal === undefined) throw new Error('interrupted stream requires a signal') + try { + yield { type: 'block-start', index: 0, blockType: 'text' } + yield { type: 'text-delta', index: 0, text: 'partial' } + streaming.resolve(undefined) + await new Promise((_resolve, reject) => { + if (signal.aborted) { + reject(new Error('aborted')) + return + } + signal.addEventListener('abort', () => { reject(new Error('aborted')) }, { once: true }) + }) + } finally { + cleanupStarted.resolve(undefined) + await releaseCleanup.promise + } + return + } + if (prompt?.some(block => block.type === 'text' && block.text === 'late child result')) { + lateStarted.resolve(undefined) + await releaseLate.promise + } + yield* stream(options) + }) + const sessionId = await newSession(harness) + const parent = ctx.agents.get(SessionId(sessionId))! + let result: { stopReason: string } | undefined + const first = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'interrupted image' }] }) + .then((value) => { result = value; return value }) + try { + await streaming.promise + await harness.client.cancel({ sessionId }) + await cleanupStarted.promise + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'late child result' }], source: { kind: 'test' } })) + expect(result).toBeUndefined() + releaseCleanup.resolve(undefined) + await Promise.all([reading.promise, lateStarted.promise]) + await setImmediate() + expect(result).toBeUndefined() + expect(messageText(harness)).toBe('') + releaseImage.resolve(undefined) + await vi.waitFor(() => { expect(result).toEqual({ stopReason: 'cancelled' }) }) + expect(parent.status).toBe('running') + expect(harness.updates.filter(update => update.sessionUpdate === 'agent_message_chunk')).toEqual([ + expect.objectContaining({ content: { type: 'image', data: 'Bg==', mimeType: 'image/png' } }), + expect.objectContaining({ content: { type: 'text', text: 'partial' } }), + ]) + releaseLate.resolve(undefined) + await parent.whenIdle() + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate again' }] })) + .resolves.toEqual({ stopReason: 'end_turn' }) + expect(delegated()).toBe(true) + expect(messageText(harness)).toContain('later parent answer') + } finally { + releaseCleanup.resolve(undefined) + releaseImage.resolve(undefined) + releaseLate.resolve(undefined) + await first + await parent.whenIdle() + } + }) + + it('waits for cancelled maintenance cleanup without following its later summary', async () => { + harness = await makeBridgeHarness({ script: [ + textResponse('late autonomous answer'), + toolCallResponse('delegate-again', 'delegate_again', {}), + textResponse('later child answer'), textResponse('later parent answer'), + ] }) + const ctx = harness.ctx + const delegated = await installDelegationTool(ctx) + const sessionId = await newSession(harness) + const parent = ctx.agents.get(SessionId(sessionId))! + const queued = Promise.withResolvers() + ctx.on('agent/inbox/inserted', ({ agent, message }) => { + if (agent === parent && message.content.some(block => block.type === 'text' && block.text === 'queued prompt')) { + queued.resolve(undefined) + } + }) + const cleanupStarted = Promise.withResolvers() + const releaseCleanup = Promise.withResolvers() + const lateStarted = Promise.withResolvers() + const releaseLate = Promise.withResolvers() + const stream = harness.adapter.stream.bind(harness.adapter) + vi.spyOn(harness.adapter, 'stream').mockImplementation(async function* (options) { + if (options.messages.at(-1)?.content.some(block => block.type === 'text' && block.text === 'late child result')) { + lateStarted.resolve(undefined) + await releaseLate.promise + } + yield* stream(options) + }) + const maintenance = parent.runMaintenance(async (signal) => { + await new Promise((resolve) => { + signal.addEventListener('abort', () => { resolve() }, { once: true }) + }) + cleanupStarted.resolve(undefined) + await releaseCleanup.promise + }) + let result: { stopReason: string } | undefined + const first = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'queued prompt' }] }) + .then((value) => { result = value; return value }) + try { + await queued.promise + await harness.client.cancel({ sessionId }) + await cleanupStarted.promise + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'late child result' }], source: { kind: 'test' } })) + await setImmediate() + expect(parent.status).toBe('idle') + expect(result).toBeUndefined() + releaseCleanup.resolve(undefined) + await lateStarted.promise + await maintenance + await vi.waitFor(() => { expect(result).toEqual({ stopReason: 'cancelled' }) }) + expect(parent.status).toBe('running') + releaseLate.resolve(undefined) + await parent.whenIdle() + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate again' }] })) + .resolves.toEqual({ stopReason: 'end_turn' }) + expect(delegated()).toBe(true) + expect(messageText(harness)).toContain('later parent answer') + } finally { + releaseCleanup.resolve(undefined) + releaseLate.resolve(undefined) + await maintenance + await first + await parent.whenIdle() + } + }) + + it.each([false, true])('waits for the later summary output and reports its delivery failure (%s)', async (fail) => { + const script: StreamChunk[][] = [textResponse('waiting')] + harness = await makeBridgeHarness({ script }) + const ref = await harness.attachments!.saveImage({ data: Uint8Array.of(4), mediaType: 'image/png' }) + script.push([ + { type: 'block-start', index: 0, blockType: 'image' }, + { type: 'block-end', index: 0, block: { type: 'image', attachment: ref } }, + { type: 'finish', reason: { kind: 'stop' } }, + ]) + const sessionId = await newSession(harness) + const parent = harness.ctx.agents.get(SessionId(sessionId))! + const reading = Promise.withResolvers() + const delivery = Promise.withResolvers() + harness.attachments!.beforeRead = async () => { + reading.resolve(undefined) + await delivery.promise + if (fail) throw new Error('summary image unavailable') + } + const waitForChildren = vi.fn().mockImplementationOnce(() => { + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'child result' }], source: { kind: 'test' } })) + return Promise.resolve(true) + }).mockResolvedValue(false) + harness.ctx.provide('subagents', { waitForChildren, drainDescendants: vi.fn() } as never) + let settled = false + const prompt = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate' }] }) + const observed = prompt.then(() => { settled = true }, () => { settled = true }) + await reading.promise + expect(settled).toBe(false) + delivery.resolve(undefined) + if (fail) await expect(prompt).rejects.toThrow('assistant output delivery failed') + else await expect(prompt).resolves.toEqual({ stopReason: 'end_turn' }) + await observed + }) + + it('includes root work started during output draining and permits the next prompt to delegate', async () => { + const script: StreamChunk[][] = [] + harness = await makeBridgeHarness({ script }) + const ctx = harness.ctx + const delegated = await installDelegationTool(ctx) + const ref = await harness.attachments!.saveImage({ data: Uint8Array.of(5), mediaType: 'image/png' }) + script.push([ + { type: 'block-start', index: 0, blockType: 'image' }, + { type: 'block-end', index: 0, block: { type: 'image', attachment: ref } }, + { type: 'finish', reason: { kind: 'stop' } }, + ], textResponse('late autonomous answer'), toolCallResponse('delegate-again', 'delegate_again', {}), + textResponse('later child answer'), textResponse('later parent answer')) + const reading = Promise.withResolvers() + const releaseImage = Promise.withResolvers() + harness.attachments!.beforeRead = () => { + reading.resolve(undefined) + return releaseImage.promise + } + const sessionId = await newSession(harness) + const parent = ctx.agents.get(SessionId(sessionId))! + const noChildren = Promise.withResolvers() + vi.spyOn(ctx.subagents, 'waitForChildren').mockImplementationOnce(() => { + noChildren.resolve(undefined) + return Promise.resolve(false) + }) + const lateStarted = Promise.withResolvers() + const releaseLate = Promise.withResolvers() + const stream = harness.adapter.stream.bind(harness.adapter) + vi.spyOn(harness.adapter, 'stream').mockImplementation(async function* (options) { + if (options.messages.at(-1)?.content.some(block => block.type === 'text' && block.text === 'late autonomous turn')) { + lateStarted.resolve(undefined) + await releaseLate.promise + } + yield* stream(options) + }) + let result: { stopReason: string } | undefined + const first = harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'render image' }] }) + .then((value) => { result = value; return value }) + try { + await Promise.all([reading.promise, noChildren.promise]) + await setImmediate() + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'late autonomous turn' }], source: { kind: 'test' } })) + await lateStarted.promise + releaseImage.resolve(undefined) + await setImmediate() + expect(result).toBeUndefined() + expect(parent.status).toBe('running') + releaseLate.resolve(undefined) + await expect(first).resolves.toEqual({ stopReason: 'end_turn' }) + expect(messageText(harness)).toContain('late autonomous answer') + await expect(harness.client.prompt({ sessionId, prompt: [{ type: 'text', text: 'delegate again' }] })) + .resolves.toEqual({ stopReason: 'end_turn' }) + expect(delegated()).toBe(true) + } finally { + releaseImage.resolve(undefined) + releaseLate.resolve(undefined) + await first + } + }) + it('a failed turn with no retry still rejects', async () => { harness = await makeBridgeHarness({ script: [errorResponse('terminal boom')] }) let offered = 0 diff --git a/packages/api/account-controller/README.i18n.yaml b/packages/api/account-controller/README.i18n.yaml index 352b915310..f678c0c9f0 100644 --- a/packages/api/account-controller/README.i18n.yaml +++ b/packages/api/account-controller/README.i18n.yaml @@ -18,8 +18,8 @@ en: 5ad12300bbc151ef zh: 3ccfb6046ab423ef /deepseek-ai-dsh-api-account-controller/understand-the-implementation: - en: 3d899b3480391175 - zh: 803808ca9691366e + en: d76d0fa4dc48033d + zh: e193caafd78250e9 /deepseek-ai-dsh-api-account-controller/further-exploration: en: e5d7405e19fb6d00 zh: f92bdfe5247d758b diff --git a/packages/api/account-controller/README.md b/packages/api/account-controller/README.md index dabbb13b52..3e7ff7b0bc 100644 --- a/packages/api/account-controller/README.md +++ b/packages/api/account-controller/README.md @@ -31,7 +31,7 @@ Every operation that reaches Platform takes the calling UI's `AccountClientMetad ## Understand the implementation -The controller forwards operations to the account service and maintains no independent account state; no invariant companion is published. +The controller forwards operations to the account service and maintains no independent account state. ## Further Exploration diff --git a/packages/api/account-controller/README.zh.md b/packages/api/account-controller/README.zh.md index 63c05a43ff..7007cfdd6f 100644 --- a/packages/api/account-controller/README.zh.md +++ b/packages/api/account-controller/README.zh.md @@ -31,7 +31,7 @@ account 命名空间提供 getState、getProfile / getBalance、getUnnotifiedBon ## 理解实现 -控制器向账号服务转发操作,不维护独立的账号状态,因此不发布 invariant。 +控制器向账号服务转发操作,不维护独立的账号状态。 ## 深入探索 diff --git a/packages/api/account-controller/package.json b/packages/api/account-controller/package.json index 0bda49aa57..ae3bfe24a1 100644 --- a/packages/api/account-controller/package.json +++ b/packages/api/account-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-account-controller", "description": "Expose safe account operations over authenticated Remote", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/gateway/README.i18n.yaml b/packages/api/gateway/README.i18n.yaml index 7305ddc267..e8cf3b7f4b 100644 --- a/packages/api/gateway/README.i18n.yaml +++ b/packages/api/gateway/README.i18n.yaml @@ -30,5 +30,5 @@ en: 3ade2903f40a0a98 zh: 9af1d557fefdbd89 /deepseek-ai-dsh-api-gateway/known-limitations-and-deferred-work/dev-note: - en: 365f5c32056ff39a - zh: 9556fd36702f5ecd + en: 10727471030aee01 + zh: e6bee82abd12127d diff --git a/packages/api/gateway/README.md b/packages/api/gateway/README.md index 6bb4f02cf9..41cfc3a324 100644 --- a/packages/api/gateway/README.md +++ b/packages/api/gateway/README.md @@ -100,6 +100,4 @@ None. -**Runtime invariant:** No companion is published. Host calls re-read authoritative Cordis and Typert state, while Client methods, descriptors, and `$on` subscriptions mutate in one owned effect. - The ./stream-protocol export supplies the shared Remote stream framing and parser to native Desktop callers. These callers use the same authenticated WebSocket endpoint as the browser client. diff --git a/packages/api/gateway/README.zh.md b/packages/api/gateway/README.zh.md index 97fe9897c9..c73b0f45b6 100644 --- a/packages/api/gateway/README.zh.md +++ b/packages/api/gateway/README.zh.md @@ -100,6 +100,4 @@ Client waterfall 的 Context 解析保持同步。解析器可以返回借用的 -**运行时不变式:** 不发布伴生入口。Host 调用会重新读取权威的 Cordis 与 Typert 状态,Client 方法、描述符与 `$on` 订阅的变更则统一归属同一个 effect。 - ./stream-protocol 导出为原生桌面调用方提供共享的 Remote 流帧格式和解析器。它们与浏览器客户端使用同一个经过认证的 WebSocket 端点。 diff --git a/packages/api/gateway/package.json b/packages/api/gateway/package.json index d2bd53e59f..0ee56662b0 100644 --- a/packages/api/gateway/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -58,7 +58,7 @@ "@deepseek-ai/dsh-deque": "workspace:*", "@deepseek-ai/dsh-timeout": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", - "ws": "^8.21.0", + "ws": "^8.21.3", "@deepseek-ai/dsh-typert-protocol": "workspace:*" }, "peerDependencies": { diff --git a/packages/api/gateway/tests/gateway.host.spec.ts b/packages/api/gateway/tests/gateway.host.spec.ts index 24522ce846..e0fcbec649 100644 --- a/packages/api/gateway/tests/gateway.host.spec.ts +++ b/packages/api/gateway/tests/gateway.host.spec.ts @@ -454,6 +454,23 @@ describe('TypertGatewayService', () => { expect(service.lastSignal).toBe(abort.signal) }) + it('skips declared Services without an active provider during SRC resolution', async () => { + const { ctx } = await setup() + const unbound = ctx.plugin(class UnboundService extends Service { + constructor(ctx: Context) { super(ctx, 'unboundFixture') } + }) + await unbound + await unbound.dispose() + registerAgentLookup(ctx, { id: 'agent-1' }) + + await expect(ctx.extend({ fixtureScope: 'unbound' }).typertGateway.invoke({ + namespace: 'goals', + method: 'create', + args: { agentId: 'agent-1', request: { title: 'ship' } }, + signal: new AbortController().signal, + })).resolves.toEqual({ agentId: 'agent-1', title: 'ship', scope: 'unbound' }) + }) + it('does not downgrade an observed SRC lookup after its provider unloads', async () => { const { ctx, service } = await setup() const dispose = registerAgentLookup(ctx, { id: 'agent-1' }) diff --git a/packages/api/job-controller/README.i18n.yaml b/packages/api/job-controller/README.i18n.yaml index b89d3a11c2..44db98b258 100644 --- a/packages/api/job-controller/README.i18n.yaml +++ b/packages/api/job-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: c0f98753f7ba6b9b zh: 4022148512051bfe /job-controller/known-limitations-and-deferred-work/dev-note: - en: a1f297405fd3bf23 - zh: 7323c5216ec30800 + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/api/job-controller/README.md b/packages/api/job-controller/README.md index ea343a8a20..6c7f5c9af3 100644 --- a/packages/api/job-controller/README.md +++ b/packages/api/job-controller/README.md @@ -65,5 +65,3 @@ No direct effect; observation reads never touch model requests. None. - -**Runtime invariant:** No companion is published. The controller is a stateless projection of `ctx.jobs` reads; the registry's own `@deepseek-ai/dsh-jobs/invariant` owns the event protocol and event-versus-read relations these streams forward. diff --git a/packages/api/job-controller/README.zh.md b/packages/api/job-controller/README.zh.md index ce8f166cec..7366dec51b 100644 --- a/packages/api/job-controller/README.zh.md +++ b/packages/api/job-controller/README.zh.md @@ -65,5 +65,3 @@ Client 入口安装 `ctx.jobs`(`IJobs`),由包内部的 `ClientJobsModel` 无。 - -**运行时不变式:** 不发布伴生入口。控制器是 `ctx.jobs` 读取的无状态投影;这些流转发的事件协议与事件对读取的关系由注册表自己的 `@deepseek-ai/dsh-jobs/invariant` 拥有。 diff --git a/packages/api/job-controller/package.json b/packages/api/job-controller/package.json index 99c3b80395..f9fd59429b 100644 --- a/packages/api/job-controller/package.json +++ b/packages/api/job-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-job-controller", "description": "Job Remote observation stream and the reference-counted client job-output service", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/remotes/README.i18n.yaml b/packages/api/remotes/README.i18n.yaml index 93d585f2c4..c8686dd4e2 100644 --- a/packages/api/remotes/README.i18n.yaml +++ b/packages/api/remotes/README.i18n.yaml @@ -15,8 +15,8 @@ en: a7a78c6d1aeebeb3 zh: 4bb69f32d8b05ba3 /deepseek-ai-dsh-api-remotes/use-this-package: - en: b85e651d75ad3930 - zh: 586ab15f54aefed6 + en: a80d44e0683768a5 + zh: 379833ac180e5418 /deepseek-ai-dsh-api-remotes/forwarded-host-events: en: d6d82bf0e55ae5e9 zh: c3a0c80f8b115039 @@ -33,5 +33,5 @@ en: 00a837c9e7b5a800 zh: 1b86e789327b7798 /deepseek-ai-dsh-api-remotes/known-limitations-and-deferred-work/dev-note: - en: 947ba019d2a6a7e7 - zh: ad4d687f3bc77dbb + en: d60df1790fac729c + zh: cfc4e027025639bf diff --git a/packages/api/remotes/README.md b/packages/api/remotes/README.md index 20d818b659..f479777030 100644 --- a/packages/api/remotes/README.md +++ b/packages/api/remotes/README.md @@ -27,7 +27,7 @@ Two-sided BFF for Host Remote capabilities selected by this application. The Hos ## Use this package -[`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.md) owns Agent and Session identity policy, including the Typert lookup resolvers used by other namespaces. This package only selects and mounts that generated Session contribution; it does not duplicate activation policy. +[`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.md) owns Agent and Session identity policy, including the Typert lookup resolvers used by other namespaces. This package selects and mounts that generated Session contribution without a direct runtime dependency on `@deepseek-ai/dsh-session`; activation policy stays with Session Controller. The Client assembly mounts Office conversion, Commands, credentials, settings, Goal, Schedule, dynamic Cordis, file and Session references, read-only Host plugin inventory, plugin-installation registry response comparison, message feedback, permission presets, Session Controller, subagents, and Workspace Controller contributions. The `permissionPresets` namespace returns the complete process-level catalog used by current-session controls. Cordis effect ownership withdraws every contribution when this assembly unloads, while `@deepseek-ai/dsh-api-gateway/client` owns descriptor validation, traced namespace Services, direct and scoped methods, invocation, streams, and cancellation. The Client entry consumes the shared `TypertClientRemote` interface through Cordis and does not import the concrete Gateway. It re-exports the Gateway Client face's declaration merges type-only, so a consumer reaching the forwarded-event vocabulary through this facade gains no runtime edge to the Gateway implementation. @@ -87,6 +87,4 @@ None. -**Runtime invariant:** No companion is published. Typert and the Agent/Session registries own the observed relationships. - The Client assembly includes the account namespace for login commands and reconnectable account snapshots. diff --git a/packages/api/remotes/README.zh.md b/packages/api/remotes/README.zh.md index b3bda48bac..b130e28b76 100644 --- a/packages/api/remotes/README.zh.md +++ b/packages/api/remotes/README.zh.md @@ -27,7 +27,7 @@ kind: "package-reference" ## 使用本包 -[`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.zh.md) 拥有 agent(智能体)与会话身份策略,包括供其他 namespace 使用的 Typert lookup 解析器。本包只选择并挂载生成的会话 contribution,不复制激活策略。 +[`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.zh.md) 拥有 agent(智能体)与会话身份策略,包括供其他 namespace 使用的 Typert lookup 解析器。本包选择并挂载生成的会话 contribution,无需直接依赖 `@deepseek-ai/dsh-session` 的运行时;激活策略由会话控制器负责。 Client 组合挂载 Office 转换、Commands、凭据、settings、Goal、Schedule、动态 Cordis、文件与会话引用、只读 Host 插件清单、插件安装源响应比较、消息反馈、权限预设、会话控制器、subagents 和 Workspace 控制器 contribution。`permissionPresets` namespace 返回 current-session 控件使用的完整进程级目录。该组合卸载时,Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-api-gateway/client` 负责描述符校验、可追踪的 namespace 服务、直接与作用域方法、调用、流与取消。Client 入口通过 Cordis 消费共享的 `TypertClientRemote` 接口,不导入具体 Gateway;它只以 type-only 形式重新导出 Gateway Client face 的声明合并,因此消费端经由本外观取到转发事件词汇时,运行时不会多出一条通往 Gateway 实现的边。 @@ -87,6 +87,4 @@ Host entry 为每条 Client 流独立注册一组 allowlist listener 和一个 -**运行时不变式:** 不发布伴生入口。被观察的关系由 Typert、agent 注册表和会话注册表负责。 - 客户端组装包含 account 命名空间,用于登录操作和可重连的账号快照。 diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index 54e6eeb967..f09b9a96ae 100644 --- a/packages/api/remotes/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -51,7 +51,6 @@ ], "dependencies": { "@deepseek-ai/dsh-deque": "workspace:*", - "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-util-values": "workspace:*" }, "peerDependencies": { diff --git a/packages/api/session-controller/README.i18n.yaml b/packages/api/session-controller/README.i18n.yaml index 5878d2f71b..18627b3c99 100644 --- a/packages/api/session-controller/README.i18n.yaml +++ b/packages/api/session-controller/README.i18n.yaml @@ -15,8 +15,8 @@ en: 1fdf707322fb6996 zh: 0204f7e9d4aa3cce /session-controller/use-this-package: - en: b1a66e01de414924 - zh: 7c01f66b94b01ff9 + en: ae2715276ec70675 + zh: 0937d1335c21eff2 /session-controller/client-references: en: 41bdda1ac32df558 zh: aa5d0e325924066b @@ -24,8 +24,8 @@ en: 266ab2b1e2fec542 zh: b3450ae28a1e0baf /session-controller/configuration: - en: 5c4c4224074e38a8 - zh: e0b0edac65c88ff4 + en: 5ae2b340c630897f + zh: 1ffbd61ecf302ec2 /session-controller/model-experience: en: 30b917bf822bb4df zh: b0b023b5b526c317 @@ -33,8 +33,8 @@ en: 22fc0c9dbedd3bb6 zh: fad040ca55fd0e0a /session-controller/known-limitations-and-deferred-work: - en: e896bfcaf38b4f6c - zh: 55a4eba838f166de + en: 5db0f9f69a8be926 + zh: 6ba1cc4a3850fcbc /session-controller/known-limitations-and-deferred-work/dev-note: - en: 68e0dfe4588c5b88 - zh: db51b435611bded1 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/session-controller/README.md b/packages/api/session-controller/README.md index be3f9a0bf5..d5a7f2a7a7 100644 --- a/packages/api/session-controller/README.md +++ b/packages/api/session-controller/README.md @@ -31,7 +31,9 @@ History pages and follow opening snapshots carry one `{ type: 'event', event: Se The Client journal validates current Session event envelopes before publishing follow snapshots, live entries, or history pages. It reuses the browser-safe Session validators for required surface markers, exact replacement endpoints, earlier unique source seqs, embedded Assistant provider metadata, request-header omissions, and tool-error consistency. Invalid records fail without field stripping or normalization; range membership and source existence remain durable-log checks on the Host. -Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Cancellation requires live state; queue mutation, model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent when present or the recorded preset's standing scope when cold, so listing never starts an Agent. The authenticated delivery routes use `workspaceDesktop()` for the serving Host name and file-manager behavior. `openWorkspacePath({ path, action: "reveal" })` delegates file-manager navigation to the native adapter; omitting `action` opens the file-type association, including HTML and SVG. Both operations require the composed filesystem to map the requested Host path to the same canonical process path; unmapped remote paths are refused before a native command runs. `session.projections` reads one complete baseline through a live-preferred Session observation without activating an Agent. It returns null for a missing Session and serves any registered projection keys. The Client exposes shared values and explicit-read state through `projectionsBySession`; domains select their own keys. Session-list summaries carry `agentAvailable`, updated through existing summary and status events independently of durable projections. Initial reads and live projection frames use the same sequence ordering. +Each endpoint states its activation policy. List reads only stored headers and projection-cache rows: it never calls per-session stat or opens a cold Session body. A current-format cache identity may supply every list hint; a lifecycle-matching predecessor cache may supply only its version-compatible title as a stale display fact, never as an authoritative fold seed. Search, attachment, history pages, log following, skill discovery, and workspace-path opening can inspect persistence without activating an Agent; `canOpenWorkspacePath()` reports native-opening availability without addressing a Session. Cancellation requires live state; queue mutation, model, rename, prompt, and file-reference operations may resolve or resume an ordinary Session. Prompt rejects content with neither non-whitespace text nor an attachment before resolving the Agent or appending Session events; queue edits accept only non-empty text content. Prompt admission consumes opaque receipts from the injected [`fileUploads`](../../client/file-upload/README.md) Host service and resolves every same-Agent receipt before sending the complete ordered content list through `ctx.attachments`. Prompt retries whose `requestId` is already queued or logged return the original acceptance without inserting another message. Create and fork are the only operations that create a new Agent directly. The service applies one preset-aware resume policy and subagent ownership fence to its own methods and to the Typert Agent and Session lookups used by other Remote namespaces. Queue mutation has one narrow exception: a live child whose current projected identity is continuable and comes from its own non-seed suffix accepts the ordinary Edit, Remove, and QueueDock Steer actions across both inbox destinations. One-shot, missing, unknown, corrupt, seed-only, or cold children remain rejected without resume. The skill catalog uses a live Agent's effective working directory and scoped registry when present. Cold listing uses the projected current directory, falling back to the recorded origin and then the configured directory default. It retains the preset standing scope without activating an Agent. The authenticated delivery routes use `workspaceDesktop()` for the serving Host name and file-manager behavior. `openWorkspacePath({ path, action: "reveal" })` delegates file-manager navigation to the native adapter; omitting `action` opens the file-type association, including HTML and SVG. Both operations require the composed filesystem to map the requested Host path to the same canonical process path; unmapped remote paths are refused before a native command runs. `session.projections` reads one complete baseline through a live-preferred Session observation without activating an Agent. It returns null for a missing Session and serves any registered projection keys. The Client exposes shared values and explicit-read state through `projectionsBySession`; domains select their own keys. Session-list summaries carry `agentAvailable`, updated through existing summary and status events independently of durable projections. Initial reads and live projection frames use the same sequence ordering. + +Host list generation yields between complete rows only when its configured work slice is exhausted; cheap remaining work returns without a forced yield. Request cancellation is checked per row and after each yield and rejects without partial results. Each row is synchronous; the list does not promise one cross-Session snapshot. A Session already queued as cold remains a cached row if it attaches during a yield: Client in-flight mutation replay corrects its availability, and sequenced projections supersede cached hints. Client list rows and resident Sessions use the current `sessionListMetadata` projection to reject stale blank-session hints; recency is the later of the summary timestamp and the projected last user prompt. When SessionManager creates an instance before its list row arrives, it reconciles blankness with metadata already retained for that Session. An opened conversation is therefore not reused by New Session even when an older list response still marks it blank. @@ -92,6 +94,7 @@ A successful `selectModel` response acknowledges the Session-local selection wit | Field | Default | Meaning | |---|---:|---| | `nativeOpen` | platform-detected | Whether Session workspace paths can be handed to a native desktop opener | +| `listWorkSliceMs` | `16` | Positive integral list-work time slice in milliseconds; checks occur between complete rows | The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-api-session-controller) is the exhaustive source for accepted fields and their JSDoc. @@ -110,6 +113,7 @@ No direct effect; model requests remain owned by the Agent and LLM packages. +- A single summary, the query provider's own enumeration, final sorting, and response serialization can exceed the list work-slice target; it is not a hard Host latency bound. - The image byte cap does not validate decoded dimensions or pixel count. - A failed follow resumption remains visible to the caller instead of retrying indefinitely. - The raw browser upload is one streaming HTTP request without resumable offsets; a retry sends the file again from byte zero. @@ -127,5 +131,3 @@ No direct effect; model requests remain owned by the Agent and LLM packages. None. - -**Runtime invariant:** No companion is published. Every page and frame is checked against the addressed durable Session. diff --git a/packages/api/session-controller/README.zh.md b/packages/api/session-controller/README.zh.md index de40067e39..c6c7f51633 100644 --- a/packages/api/session-controller/README.zh.md +++ b/packages/api/session-controller/README.zh.md @@ -31,7 +31,9 @@ kind: "package-reference" Client journal 在发布 follow 快照、live entry 或历史页之前验证当前 Session 事件 envelope。它复用浏览器安全的 Session validator,检查必需的 surface marker、精确的 replacement endpoint、更早且唯一的 source seq、内嵌 Assistant 提供方元数据、request header 可选字段的省略规则以及工具错误一致性。无效 record 直接失败,不删除字段或归一化;范围成员与来源存在性仍由 Host 的持久日志检查。 -每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。取消要求 live 状态;queue 变更、模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent,否则使用所记录 preset 的常驻 scope,因此列表查询绝不会启动 Agent。经过鉴权的文件交付路由通过 `workspaceDesktop()` 获取提供服务的 Host 名称和文件管理器行为。`openWorkspacePath({ path, action: "reveal" })` 将文件管理器导航委托给原生适配器;省略 `action` 时按文件类型关联打开,包括 HTML 和 SVG。两种操作都要求当前文件系统将请求的 Host 路径映射到同一个规范进程路径;无法映射的远端路径会在执行原生命令之前被拒绝。 `session.projections` 通过一次 live-preferred Session observation 读取完整基线,不激活 Agent。Session 不存在时返回 null,并可提供任意已注册的 projection key。Client 通过 `projectionsBySession` 暴露共享值和显式读取状态,由领域选择自身的 key。Session 列表摘要携带 `agentAvailable`,通过已有摘要与状态事件更新,与持久化 projection 相互独立。初始读取与实时 projection 帧使用相同的序号排序规则。 +每个 endpoint 都声明自己的激活策略。列表只读取持久化 header 与 projection cache row,绝不调用逐 Session stat 或打开冷 Session body。当前格式 cache identity 可以提供全部列表 hint;生命周期匹配的 predecessor cache 只能提供版本兼容的 title,作为可能过时的展示事实,绝不能作为权威 fold seed。搜索、附件、历史页、日志跟随、skill 发现和工作区路径打开可以在不激活 Agent 的情况下检查 persistence;`canOpenWorkspacePath()` 无需指定 Session 即可报告原生打开能力。取消要求 live 状态;queue 变更、模型、重命名、prompt 和文件引用操作可以解析或恢复普通 Session。提示词会在解析 Agent 或追加 Session 事件前,拒绝既没有非空白文本也没有附件的 content;queue edit 只接受非空文本 content。prompt 准入从注入的 [`fileUploads`](../../client/file-upload/README.zh.md) Host 服务取得不透明凭证,在把完整有序内容列表交给 `ctx.attachments` 前解析每个属于同一 Agent 的凭证。`requestId` 已进入 queue 或日志时,prompt 重试直接返回原来的接受结果,不会重复插入消息。只有 create 与 fork 会直接创建新 Agent。该服务把同一套感知 preset 的恢复策略和 subagent ownership fence 同时用于自身方法,以及其他 Remote namespace 使用的 Typert Agent 与 Session lookup。Queue 变更只有一个狭窄例外:当前 projection identity 为 continuable 且来自自身非 seed suffix 的在线 child,可以在两个 inbox 目标上使用普通 Edit、Remove 与 QueueDock Steer action。One-shot、缺失、未知、损坏、仅含 seed identity 或冷 child 继续被拒绝,且不会恢复。skill 目录优先使用已有 live Agent 的有效工作目录与限定范围内的注册表。冷态列表使用投影中的当前目录,依次回退到记录的起始目录与配置的默认目录。它保留 preset 常驻 scope,不会激活 Agent。经过鉴权的文件交付路由通过 `workspaceDesktop()` 获取提供服务的 Host 名称和文件管理器行为。`openWorkspacePath({ path, action: "reveal" })` 将文件管理器导航委托给原生适配器;省略 `action` 时按文件类型关联打开,包括 HTML 和 SVG。两种操作都要求当前文件系统将请求的 Host 路径映射到同一个规范进程路径;无法映射的远端路径会在执行原生命令之前被拒绝。 `session.projections` 通过一次 live-preferred Session observation 读取完整基线,不激活 Agent。Session 不存在时返回 null,并可提供任意已注册的 projection key。Client 通过 `projectionsBySession` 暴露共享值和显式读取状态,由领域选择自身的 key。Session 列表摘要携带 `agentAvailable`,通过已有摘要与状态事件更新,与持久化 projection 相互独立。初始读取与实时 projection 帧使用相同的序号排序规则。 + +Host 仅在配置的工作时间片耗尽时,于完整条目之间让出事件循环;剩余工作较少时直接返回,不强制让出。每行及每次让出之后检查请求取消,取消时拒绝返回部分结果。每行同步计算;列表不承诺跨 Session 的统一快照。已经进入冷态队列的 Session 若在让出期间接入,仍返回缓存行:Client 对进行中请求的变更重放会纠正其可用性,带序号的投影会覆盖缓存提示。 Client 列表行和驻留 Session 使用当前 `sessionListMetadata` 投影纠正过期的空白会话提示;最近活动时间取摘要时间戳与投影中最后一次用户提示词时间的较晚值。当 SessionManager 在列表行到达前创建实例时,会使用已保留的该 Session 元数据对账空白状态。因此,即使旧列表响应仍将已有对话标为空白,新会话操作也不会复用已经打开过的对话。 @@ -92,6 +94,7 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 | 字段 | 默认值 | 含义 | |---|---:|---| | `nativeOpen` | 平台探测 | 是否能把 Session 工作区路径交给原生桌面打开器 | +| `listWorkSliceMs` | `16` | 列表工作时间片,单位为毫秒,取正整数;在完整条目之间检查 | 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-api-session-controller)是所有受支持字段及其 JSDoc 的完整来源。 @@ -110,6 +113,7 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 +- 单个摘要、查询提供方自身的枚举、最终排序和响应序列化都可能超出列表时间片目标;它不是 Host 延迟的硬上限。 - 图片字节上限不校验解码后的尺寸或像素数。 - follow 恢复失败会对调用方可见,而不会无限重试。 - 浏览器原始字节上传使用一次不带断点续传偏移的流式 HTTP 请求;重试会从第零字节重新传输整个文件。 @@ -127,5 +131,3 @@ GUI 模型选择要求确切提供方/模型对出现在可用目录中;不 无。 - -**运行时不变式:** 不发布伴生入口。每个分页与帧都会对照其指向的持久 Session 校验。 diff --git a/packages/api/session-controller/package.json b/packages/api/session-controller/package.json index dc50269e26..3f421fe675 100644 --- a/packages/api/session-controller/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -105,7 +105,8 @@ "@deepseek-ai/dsh-util-time": "workspace:*", "@deepseek-ai/dsh-util-values": "workspace:*", "@deepseek-ai/dsh-util-workspace-path": "workspace:*", - "@deepseek-ai/dsh-workspace": "workspace:*" + "@deepseek-ai/dsh-workspace": "workspace:*", + "@deepseek-ai/dsh-working-directory": "workspace:*" }, "peerDependenciesMeta": { "@deepseek-ai/dsh-session-persistence": { @@ -160,6 +161,7 @@ "@deepseek-ai/dsh-util-values": "workspace:*", "@deepseek-ai/dsh-util-workspace-path": "workspace:*", "@deepseek-ai/dsh-workspace": "workspace:*", - "@types/mime-types": "^3.0.1" + "@types/mime-types": "^3.0.1", + "@deepseek-ai/dsh-working-directory": "workspace:*" } } diff --git a/packages/api/session-controller/src/client/sessions/manager.ts b/packages/api/session-controller/src/client/sessions/manager.ts index 50af09df13..db7236c1b2 100644 --- a/packages/api/session-controller/src/client/sessions/manager.ts +++ b/packages/api/session-controller/src/client/sessions/manager.ts @@ -162,7 +162,7 @@ export class SessionManager { for (const parentSessionId of this.projectionStores.keys()) { const child = this.projectionStores.get(parentSessionId)?.values().subagentCatalog ?.find(entry => entry.id === sessionId) - if (child !== undefined) { + if (child !== undefined && child.mode !== 'external') { return { parentSessionId, childSessionId: sessionId, @@ -248,7 +248,7 @@ export class SessionManager { const address = this.addresses.get(sessionId) const child = address === undefined ? undefined : this.projectionStores.get(address.parentSessionId)?.values().subagentCatalog ?.find(entry => entry.id === sessionId) - if (child !== undefined) { + if (child !== undefined && child.mode !== 'external') { // A catalogued child exists only after its delegated session has // durable history, even though child rows do not carry `blank`. session.handleBlank(false) diff --git a/packages/api/session-controller/src/client/sessions/service.ts b/packages/api/session-controller/src/client/sessions/service.ts index 8166b6f32d..964105a486 100644 --- a/packages/api/session-controller/src/client/sessions/service.ts +++ b/packages/api/session-controller/src/client/sessions/service.ts @@ -40,7 +40,7 @@ export interface SessionSummary { /** * New Session presentation and reuse eligibility, derived from the Host * summary, `sessionListMetadata`, and client acceptance/running observations. - * New Session reuses a blank one targeting the same workspace. Filtering + * Workspace reconnection may reuse a blank in the same workspace. Filtering * stays with the consumer: the store carries every row, while the Workspace * browser shows only the selected blank entry. */ @@ -633,6 +633,7 @@ export class ClientSessions implements ISessions { } for (const [parentId, projection] of Object.entries(projectionsBySession)) { for (const child of projection.values.subagentCatalog ?? []) { + if (child.mode === 'external') continue const childId = child.id const summary = byId[childId] const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId) diff --git a/packages/api/session-controller/src/index.ts b/packages/api/session-controller/src/index.ts index 3fe15221fb..8e38822fbc 100644 --- a/packages/api/session-controller/src/index.ts +++ b/packages/api/session-controller/src/index.ts @@ -79,8 +79,13 @@ declare module '@deepseek-ai/cordis' { export interface Config { /** Override platform desktop-opener detection. */ readonly nativeOpen?: boolean + /** Positive integral milliseconds of list work before yielding between complete rows. */ + readonly listWorkSliceMs?: number } +/** Deployment policy after schema defaults have been applied. */ +type ResolvedConfig = Config & { readonly listWorkSliceMs: number } + /** Host integrations replaceable by direct unit tests. */ export interface SessionControllerInternals { /** Native default-application handoff. */ @@ -111,8 +116,9 @@ export class SessionController extends TypertRemoteService { 'workspaceRegistry', ] - static Config: z = z.object({ + static Config: z = z.object({ nativeOpen: z.boolean(), + listWorkSliceMs: z.natural().min(1).default(16), }) private readonly agents: ApiSessionAgentController @@ -129,11 +135,12 @@ export class SessionController extends TypertRemoteService { /** * @param ctx - Host context containing the Session capability assembly. - * @param config - native-opener deployment policy. + * @param config - native-opener and list-scheduling deployment policy. * @param internals - host integrations replaceable by direct unit tests. */ constructor(ctx: Context, config: Config, internals: SessionControllerInternals = {}) { super(ctx, 'sessionController', { namespace: 'session' }) + const resolved = SessionController.Config(config) installModelSelectionProjection(ctx) this.agents = new ApiSessionAgentController(ctx) this.commands = new SessionCommandController(ctx, this.agents, process.cwd()) @@ -149,7 +156,7 @@ export class SessionController extends TypertRemoteService { await Promise.allSettled([...this.promotions]) }, 'session-controller.promotions') this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation) }) - this.listState = new ApiSessionList(ctx) + this.listState = new ApiSessionList(ctx, resolved.listWorkSliceMs) this.fileApplications = internals.fileApplications ?? nativeFileApplications this.openFileApplication = internals.openFileApplication ?? openNativeFileApplication this.openPath = internals.openPath ?? openNativeAssociatedPath @@ -245,7 +252,7 @@ export class SessionController extends TypertRemoteService { /** * Read all visible Session rows without resuming an Agent. * @param _request - reserved empty list request. - * @param signal - cancellation for persistence reads. + * @param signal - cancellation for persistence reads and summary generation. * @returns visible Session summaries ordered by activity. */ @Remote('list') diff --git a/packages/api/session-controller/src/list.ts b/packages/api/session-controller/src/list.ts index 54ebe1b334..c51d119805 100644 --- a/packages/api/session-controller/src/list.ts +++ b/packages/api/session-controller/src/list.ts @@ -1,5 +1,7 @@ /** Cold-safe Session list and search projection. */ +import { performance } from 'node:perf_hooks' +import { scheduler } from 'node:timers/promises' import type { Context } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-agent-preset-registry' import type { ImageAttachmentLimits } from '@deepseek-ai/dsh-attachment' @@ -74,8 +76,11 @@ export function truncateUnicodeCodePoints(value: string, maximum: number): strin /** Owns list projection registration, bounded cold summaries, and authorized search. */ export class ApiSessionList { - /** @param ctx - Host context carrying Session, query, persistence, and projection services. */ - constructor(private readonly ctx: Context) { + /** + * @param ctx - Host context carrying Session, query, persistence, and projection services. + * @param workSliceMs - Resolved positive integral list-work budget in milliseconds. + */ + constructor(private readonly ctx: Context, private readonly workSliceMs: number) { ctx.sessionProjections.register<'sessionListMetadata', SessionListMetadata>({ key: 'sessionListMetadata', stateSchema: sessionListMetadataSchema, @@ -120,7 +125,7 @@ export class ApiSessionList { /** * Read every visible attached and persisted Session without activating an Agent. - * @param signal - optional cancellation for persistence reads. + * @param signal - optional cancellation for persistence reads and summary generation. * @returns visible Session summaries ordered by activity. */ async list(signal?: AbortSignal): Promise { @@ -129,16 +134,31 @@ export class ApiSessionList { signal?.throwIfAborted() const items: SessionSummary[] = [] const cold: SessionHeader[] = [] + let yieldDeadline = performance.now() + this.workSliceMs for (const record of records) { + signal?.throwIfAborted() const live = this.ctx.sessions.get(record.header.id) if (live !== undefined) { items.push(this.summaryFor(live)) - continue + } else if (record.header.cwd !== undefined) { + cold.push(record.header) + } + if (performance.now() >= yieldDeadline) { + await scheduler.yield() + signal?.throwIfAborted() + yieldDeadline = performance.now() + this.workSliceMs } - if (record.header.cwd === undefined) continue - cold.push(record.header) } - for (const header of cold) items.push(this.summarizeCold(header)) + for (const header of cold) { + signal?.throwIfAborted() + items.push(this.summarizeCold(header)) + if (performance.now() >= yieldDeadline) { + await scheduler.yield() + signal?.throwIfAborted() + yieldDeadline = performance.now() + this.workSliceMs + } + } + signal?.throwIfAborted() items.sort((left, right) => right.updatedAt - left.updatedAt) return items } diff --git a/packages/api/session-controller/src/skill-catalog.ts b/packages/api/session-controller/src/skill-catalog.ts index d384052b3b..c9865b0e6b 100644 --- a/packages/api/session-controller/src/skill-catalog.ts +++ b/packages/api/session-controller/src/skill-catalog.ts @@ -4,6 +4,7 @@ import type { Context } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-agent-preset-registry/types' import { SessionQueryError } from '@deepseek-ai/dsh-session-query' import { isUserInvocable } from '@deepseek-ai/dsh-skill' +import type {} from '@deepseek-ai/dsh-working-directory' import type { ScopeKey } from '@deepseek-ai/dsh-scope' import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' import type { SkillListRequest, SkillListValue } from './types.ts' @@ -17,7 +18,7 @@ declare module '@deepseek-ai/cordis' { /** Host service backing `ctx.remote.skills` without activating a cold Agent. */ export class SessionSkillCatalog extends TypertRemoteService { - static inject = ['agents', 'sessionQuery', 'typert'] + static inject = ['agents', 'sessionQuery', 'typert', 'workingDirectory'] /** @param ctx - Host context carrying Session reads and optional skill/preset services. */ constructor(ctx: Context) { @@ -33,17 +34,20 @@ export class SessionSkillCatalog extends TypertRemoteService { */ @Remote async list(request: SkillListRequest, signal: AbortSignal): Promise { - void signal const { sessionId } = request - let cwd: string | undefined + let cwd: string let agentPreset: string | undefined try { using observation = await this.ctx.sessionQuery.observeSession(sessionId) if (observation.projections === undefined) { throw new Error('skill catalog requires a projected Session observation') } - cwd = observation.header.cwd + cwd = observation.projections.values.workingDirectory + ?? observation.header.cwd + ?? this.ctx.workingDirectory.defaultDirectory agentPreset = observation.projections.values.agentPreset ?? undefined + const live = this.ctx.agents.get(sessionId) + if (live !== undefined) cwd = await this.ctx.workingDirectory.ensure(live, signal) } catch (error: unknown) { if (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') { @@ -55,9 +59,6 @@ export class SessionSkillCatalog extends TypertRemoteService { {}, ) } - if (cwd === undefined) { - throw new RemoteError('gateway/internal', `session "${sessionId}" has no project cwd`, {}) - } const live = this.ctx.agents.get(sessionId) const presets = this.ctx.get('agentPresets') diff --git a/packages/api/session-controller/tests/commands-queue-attachment.host.spec.ts b/packages/api/session-controller/tests/commands-queue-attachment.host.spec.ts index a22e35cee4..5a4d946bf0 100644 --- a/packages/api/session-controller/tests/commands-queue-attachment.host.spec.ts +++ b/packages/api/session-controller/tests/commands-queue-attachment.host.spec.ts @@ -67,9 +67,9 @@ async function commandHarness( mode: 'continuable', provider: 'test', label: 'child', })) } else if (childMode === 'one-shot') { - session.append('subagent/descriptor', snapshotSubagentDescriptor({ - mode: 'one-shot', provider: 'test', label: 'child', - })) + session.append('subagent/descriptor', { + version: SUBAGENT_DESCRIPTOR_VERSION, mode: 'one-shot', provider: 'test', label: 'child', + }) } else if (childMode === 'corrupt') { session.append('subagent/descriptor', { version: SUBAGENT_DESCRIPTOR_VERSION, diff --git a/packages/api/session-controller/tests/commands-upload-file.host.spec.ts b/packages/api/session-controller/tests/commands-upload-file.host.spec.ts index 14527cccec..53d1137628 100644 --- a/packages/api/session-controller/tests/commands-upload-file.host.spec.ts +++ b/packages/api/session-controller/tests/commands-upload-file.host.spec.ts @@ -13,7 +13,7 @@ import CommandRuntime from '@deepseek-ai/dsh-commands' import { createScope } from '@deepseek-ai/dsh-scope' import FileUploads from '@deepseek-ai/dsh-client-file-upload' import type { FileUploadReceiptId } from '@deepseek-ai/dsh-client-file-upload/types' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' import type { ApiSessionAgentController } from '../src/agent.ts' import { SessionCommandController } from '../src/commands.ts' import type { SessionRequestId } from '../src/types.ts' @@ -33,6 +33,7 @@ async function uploadHarness(origin?: 'subagent'): Promise<{ uploadRoute: (request: Request) => Promise }> { const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) await ctx.plugin(SessionStore) await ctx.plugin(AgentRegistry) await ctx.plugin(CommandRuntime) @@ -266,7 +267,7 @@ describe('Session file uploads', () => { })).rejects.toMatchObject({ code: 'session/not-found' }) }) - it('rejects subagent uploads and access outside the receiving Agent scope', async () => { + it('rejects subagent uploads before storing bytes', async () => { const child = await uploadHarness('subagent') await expect(child.uploads.upload( child.agent, @@ -277,16 +278,24 @@ describe('Session file uploads', () => { details: { reason: 'SUBAGENT_FILE_UNSUPPORTED' }, }) expect(child.saveFile).not.toHaveBeenCalled() + }) - const ordinary = await uploadHarness() - const receipt = await ordinary.uploads.upload( - ordinary.agent, - { data: 'AAAA' }, - new AbortController().signal, - ) - const foreignScope = { ...ordinary.agent, ctx: ordinary.ctx } as Agent - expect(() => ordinary.uploads.resolve(foreignScope, receipt.receiptId)) - .toThrow('operation requires the Agent\'s own scope') + it('isolates receipts by Session object even when Session ids match', async () => { + const owner = await uploadHarness() + const other = await uploadHarness() + expect(other.agent.session.id).toBe(owner.agent.session.id) + expect(other.agent.session).not.toBe(owner.agent.session) + const receipt = await owner.uploads.upload(owner.agent, { data: 'AAAA' }, new AbortController().signal) + const binding = owner.uploads.bindPrompt(owner.agent, [receipt.receiptId], 'owner-prompt') + binding.commit() + + expect(owner.uploads.resolve(other.agent, receipt.receiptId)).toBeUndefined() + expect(() => owner.uploads.bindPrompt(other.agent, [receipt.receiptId], 'other-prompt')) + .toThrow(expect.objectContaining({ code: 'session/attachment-invalid', details: { reason: 'FILE_NOT_STAGED' } })) + owner.uploads.retirePrompt(other.agent, 'owner-prompt') + expect(owner.uploads.resolve(owner.agent, receipt.receiptId)).toEqual(receipt.file) + owner.uploads.retirePrompt(owner.agent, 'owner-prompt') + expect(owner.uploads.resolve(owner.agent, receipt.receiptId)).toBeUndefined() }) it('retires accepted receipts after their rpcId becomes observable', async () => { diff --git a/packages/api/session-controller/tests/list-scheduling.host.spec.ts b/packages/api/session-controller/tests/list-scheduling.host.spec.ts new file mode 100644 index 0000000000..eefea76713 --- /dev/null +++ b/packages/api/session-controller/tests/list-scheduling.host.spec.ts @@ -0,0 +1,338 @@ +/** Event-loop fairness and cancellation of synchronous Session-list summaries. */ + +import { Context } from '@deepseek-ai/cordis' +import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent' +import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session' +import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' +import type { SessionRecord } from '@deepseek-ai/dsh-session-query' +import { resolve } from 'node:path' +import { performance } from 'node:perf_hooks' +import { scheduler } from 'node:timers/promises' +import SessionController from '../src/index.ts' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { ApiSessionList } from '../src/list.ts' + +type Phase = 'live' | 'cold' +interface Row { + id: string + phase: Phase + cwd?: false +} + +const contexts = new Set() +const immediates = new Set>() + +afterEach(async () => { + for (const handle of immediates) clearImmediate(handle) + immediates.clear() + vi.restoreAllMocks() + try { + await Promise.all([...contexts].map(ctx => ctx.fiber.dispose())) + } finally { + contexts.clear() + } +}) + +function betweenRows(action: () => void): void { + const handle = setImmediate(() => { + immediates.delete(handle) + action() + }) + immediates.add(handle) +} + +async function harness(rows: readonly Row[], summaryMs = 8, workSliceMs = 8) { + const ctx = new Context() + contexts.add(ctx) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + await ctx.plugin(AgentRegistry) + const list = new ApiSessionList(ctx, workSliceMs) + const clock = { now: 0 } + const prepared = new Map() + const agents = new Map() + const detach = new Map void>() + const trace: string[] = [] + const observer: { summary: (phase: Phase, id: string) => void } = { summary: () => {} } + const recordSummary = (phase: Phase, id: string): void => { + clock.now += summaryMs + trace.push(`${phase}:${id}`) + observer.summary(phase, id) + } + const attach = (id: string): void => { + const session = prepared.get(id) + if (session === undefined) throw new Error(`missing prepared Session ${id}`) + const detachSession = ctx.sessions.enter(session) + ctx.effect(() => detachSession, 'list-scheduling.session') + ctx.sessions.announce(session) + const agent = { id: session.id, session, ctx, status: 'idle' as Agent['status'] } + // Only registry lookup and status are consumed; no Agent execution occurs in this fixture. + const detachAgent = ctx.agents.enter(agent as Agent, undefined) + ctx.effect(() => detachAgent, 'list-scheduling.agent') + agents.set(id, agent) + detach.set(id, () => { detachAgent(); detachSession() }) + } + const records: SessionRecord[] = rows.map((row) => { + const session = ctx.sessions.prepare(SessionId(row.id), { + meta: { createdAt: 100, ...(row.cwd === false ? {} : { cwd: resolve('list-scheduling-fixture') }) }, + }) + prepared.set(row.id, session) + if (row.phase === 'live') attach(row.id) + return { header: session.header, live: row.phase === 'live', persisted: true } + }) + const listSessions = vi.fn(async (_signal?: AbortSignal) => records) + ctx.provide('sessionQuery', { listSessions } as never) + ctx.provide('sessionProjectionCache', { + cachedSnapshot: (header: SessionRecord['header']) => { + recordSummary('cold', header.id) + return { asOfSeq: -1, values: { sessionListMetadata: { blank: false, lastPromptAt: null } } } + }, + cachedPredecessorTitle: () => undefined, + } as never) + const summaryFor = list.summaryFor.bind(list) + vi.spyOn(list, 'summaryFor').mockImplementation((session) => { + const summary = summaryFor(session) + recordSummary('live', session.id) + return summary + }) + vi.spyOn(performance, 'now').mockImplementation(() => clock.now) + return { ctx, list, listSessions, trace, observer, attach, agents, detach, clock } +} + +describe('Session-list work slices', () => { + it.each(['live', 'cold'])('keeps cheap %s summaries in one synchronous slice', async (phase) => { + const h = await harness([{ id: 'first', phase }, { id: 'middle', phase }, { id: 'last', phase }], 2) + const yieldWork = vi.spyOn(scheduler, 'yield') + h.observer.summary = (_phase, id) => { + if (id !== 'first') return + queueMicrotask(() => { h.trace.push('microtask') }) + betweenRows(() => { h.trace.push('immediate') }) + } + + await h.list.list() + expect(yieldWork).not.toHaveBeenCalled() + expect(h.trace).toEqual([`${phase}:first`, `${phase}:middle`, `${phase}:last`, 'microtask']) + await new Promise((resolve) => { betweenRows(resolve) }) + expect(h.trace.at(-1)).toBe('immediate') + }) + + it('accumulates work across both phases and resets the deadline after waiting', async () => { + const h = await harness([{ id: 'first', phase: 'live' }, { id: 'second', phase: 'cold' }, { id: 'last', phase: 'cold' }], 4) + const yieldWork = vi.spyOn(scheduler, 'yield') + h.observer.summary = (_phase, id) => { + if (id !== 'first') return + betweenRows(() => { + h.clock.now += 1_000 + h.trace.push('other-work') + }) + } + + await h.list.list() + expect(h.trace).toEqual(['live:first', 'cold:second', 'other-work', 'cold:last']) + expect(yieldWork).toHaveBeenCalledTimes(1) + }) + + it('honors an overridden budget without yielding early', async () => { + const h = await harness([{ id: 'first', phase: 'cold' }, { id: 'last', phase: 'cold' }], 10, 16) + const yieldWork = vi.spyOn(scheduler, 'yield') + h.observer.summary = (_phase, id) => { + if (id === 'first') betweenRows(() => { h.trace.push('other-work') }) + } + await h.list.list() + expect(h.trace).toEqual(['cold:first', 'cold:last', 'other-work']) + expect(yieldWork).toHaveBeenCalledTimes(1) + }) + + it('includes skipped-record classification in the work budget', async () => { + const h = await harness([ + { id: 'first', phase: 'cold', cwd: false }, + { id: 'second', phase: 'cold', cwd: false }, + { id: 'last', phase: 'cold', cwd: false }, + ], 0) + const controller = new AbortController() + const reason = new Error('stop classification') + const get = h.ctx.sessions.get.bind(h.ctx.sessions) + const lookup = vi.spyOn(h.ctx.sessions, 'get').mockImplementation((id) => { + h.clock.now += 4 + if (id === SessionId('first')) betweenRows(() => { controller.abort(reason) }) + return get(id) + }) + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(lookup).toHaveBeenCalledTimes(2) + expect(h.trace).toEqual([]) + }) + + it.each(['first', 'last'])('honors synchronous cancellation in the %s cheap row without yielding', async (abortAt) => { + const h = await harness([{ id: 'first', phase: 'cold' }, { id: 'last', phase: 'cold' }], 0) + const yieldWork = vi.spyOn(scheduler, 'yield') + const controller = new AbortController() + const reason = new Error('stop without waiting') + h.observer.summary = (_phase, id) => { if (id === abortAt) controller.abort(reason) } + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(yieldWork).not.toHaveBeenCalled() + expect(h.trace).toEqual(abortAt === 'first' ? ['cold:first'] : ['cold:first', 'cold:last']) + }) + + it('resolves default and overridden budgets without changing native-opening input', () => { + expect(SessionController.Config({}).listWorkSliceMs).toBe(16) + expect(SessionController.Config({ nativeOpen: false, listWorkSliceMs: 32 })).toMatchObject({ nativeOpen: false, listWorkSliceMs: 32 }) + }) + + it('routes an overridden budget through the public Session Controller', async () => { + const h = await harness([{ id: 'first', phase: 'cold' }, { id: 'middle', phase: 'cold' }, { id: 'last', phase: 'cold' }], 4) + const dispose = (): void => {} + h.ctx.provide('typert', { lookups: { configure: () => dispose }, contexts: { configureHost: () => dispose } } as never) + h.ctx.provide('fileUploads', { registerAgentResolver: () => dispose } as never) + h.ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'test', model: 'test' }) } as never) + h.ctx.provide('workspaceRegistry', { list: () => [], archivedSessionIds: [] } as never) + const controller = new SessionController(h.ctx, { nativeOpen: false, listWorkSliceMs: 4 }, { canOpenPath: () => false }) + const yieldWork = vi.spyOn(scheduler, 'yield') + const result = await controller.list({}, new AbortController().signal) + expect(result.items).toHaveLength(3) + expect(yieldWork).toHaveBeenCalledTimes(3) + }) + + it.each([0, -1, 0.5, Number.NaN, Number.POSITIVE_INFINITY])('rejects invalid work-slice budget %s', (listWorkSliceMs) => { + expect(() => SessionController.Config({ listWorkSliceMs })).toThrow() + }) + + it.each(['live', 'cold'])('yields between %s summaries that each exhaust the work slice', async (phase) => { + const h = await harness([{ id: 'first', phase }, { id: 'middle', phase }, { id: 'last', phase }]) + h.observer.summary = (_phase, id) => { betweenRows(() => { h.trace.push(`immediate:${id}`) }) } + + const items = await h.list.list() + h.trace.push('resolved') + + expect(h.trace).toEqual([ + `${phase}:first`, 'immediate:first', `${phase}:middle`, 'immediate:middle', + `${phase}:last`, 'immediate:last', 'resolved', + ]) + expect(items.map(item => item.sessionId)).toEqual(['first', 'middle', 'last']) + }) + + it.each([ + { phase: 'live', abortAt: 'first' }, + { phase: 'live', abortAt: 'last' }, + { phase: 'cold', abortAt: 'first' }, + { phase: 'cold', abortAt: 'last' }, + ] as const)('preserves cancellation after the $abortAt $phase row', async ({ phase, abortAt }) => { + const h = await harness([{ id: 'first', phase }, { id: 'middle', phase }, { id: 'last', phase }]) + const lookup = vi.spyOn(h.ctx.sessions, 'get') + const controller = new AbortController() + const reason = { cancellation: `${phase}-${abortAt}` } + h.observer.summary = (_phase, id) => { + if (id === abortAt) betweenRows(() => { controller.abort(reason) }) + } + + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(h.trace).toEqual(abortAt === 'first' + ? [`${phase}:first`] + : [`${phase}:first`, `${phase}:middle`, `${phase}:last`]) + expect(h.listSessions).toHaveBeenCalledExactlyOnceWith(controller.signal) + if (phase === 'live' && abortAt === 'first') { + expect(lookup).toHaveBeenCalledExactlyOnceWith(SessionId('first')) + } + }) + + it('does not start cold summaries after cancellation during the last live row', async () => { + const h = await harness([{ id: 'cold', phase: 'cold' }, { id: 'live', phase: 'live' }]) + const controller = new AbortController() + const reason = new Error('cancel before cold phase') + h.observer.summary = (phase) => { + if (phase === 'live') betweenRows(() => { controller.abort(reason) }) + } + + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(h.trace).toEqual(['live:live']) + }) + + it('rejects a pre-aborted request before querying headers', async () => { + const h = await harness([{ id: 'live', phase: 'live' }]) + const controller = new AbortController() + const reason = new Error('already cancelled') + controller.abort(reason) + + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(h.listSessions).not.toHaveBeenCalled() + expect(h.trace).toEqual([]) + }) + + it('checks cancellation after the header query before summarizing any row', async () => { + const h = await harness([{ id: 'cold', phase: 'cold' }]) + const controller = new AbortController() + const reason = new Error('query cancelled') + h.listSessions.mockImplementation(async () => { + controller.abort(reason) + return [] + }) + + await expect(h.list.list(controller.signal)).rejects.toBe(reason) + expect(h.trace).toEqual([]) + }) + + it('returns an empty list without invoking a summary', async () => { + const h = await harness([]) + await expect(h.list.list()).resolves.toEqual([]) + expect(h.trace).toEqual([]) + }) + + it('omits cwd-less cold rows but retains cwd-less attached Sessions', async () => { + const h = await harness([ + { id: 'omitted-first', phase: 'cold', cwd: false }, + { id: 'visible-cold', phase: 'cold' }, + { id: 'visible-live', phase: 'live', cwd: false }, + { id: 'omitted-last', phase: 'cold', cwd: false }, + ]) + + const items = await h.list.list() + + expect(h.trace).toEqual(['live:visible-live', 'cold:visible-cold']) + expect(items.map(item => item.sessionId)).toEqual(['visible-live', 'visible-cold']) + expect(items[0]).not.toHaveProperty('cwd') + }) + + it('keeps live-before-cold stable ties despite interleaved query order', async () => { + const h = await harness([ + { id: 'cold-a', phase: 'cold' }, { id: 'live-a', phase: 'live' }, + { id: 'cold-b', phase: 'cold' }, { id: 'live-b', phase: 'live' }, + ]) + + const items = await h.list.list() + + expect(h.trace).toEqual(['live:live-a', 'live:live-b', 'cold:cold-a', 'cold:cold-b']) + expect(items.map(item => item.sessionId)).toEqual(['live-a', 'live-b', 'cold-a', 'cold-b']) + expect(items.map(item => item.updatedAt)).toEqual([100, 100, 100, 100]) + }) + + it('observes later attachment and status changes without reclassifying queued cold rows', async () => { + const h = await harness([ + { id: 'queued', phase: 'cold' }, { id: 'first', phase: 'live' }, + { id: 'removed', phase: 'live' }, { id: 'promoted', phase: 'cold' }, + { id: 'status', phase: 'live' }, + ]) + h.observer.summary = (_phase, id) => { + if (id !== 'first') return + betweenRows(() => { + h.detach.get('removed')!() + h.attach('queued') + h.attach('promoted') + h.agents.get('first')!.status = 'running' + h.agents.get('promoted')!.status = 'running' + h.agents.get('status')!.status = 'running' + }) + } + + const items = await h.list.list() + + expect(h.trace).toEqual(['live:first', 'live:promoted', 'live:status', 'cold:queued', 'cold:removed']) + expect(items.map(({ sessionId, agentAvailable, running }) => ({ sessionId, agentAvailable, running }))).toEqual([ + { sessionId: 'first', agentAvailable: true, running: false }, + { sessionId: 'promoted', agentAvailable: true, running: true }, + { sessionId: 'status', agentAvailable: true, running: true }, + { sessionId: 'queued', agentAvailable: false, running: false }, + { sessionId: 'removed', agentAvailable: false, running: false }, + ]) + expect(h.ctx.agents.get(SessionId('queued'))).toBeDefined() + expect(h.ctx.sessions.get(SessionId('removed'))).toBeUndefined() + }) +}) diff --git a/packages/api/session-controller/tests/manager.client.spec.ts b/packages/api/session-controller/tests/manager.client.spec.ts index 4cab61367e..4da138a716 100644 --- a/packages/api/session-controller/tests/manager.client.spec.ts +++ b/packages/api/session-controller/tests/manager.client.spec.ts @@ -730,6 +730,17 @@ describe('Host Remote event routing', () => { }) describe('subagent catalogs', () => { + it('retains external membership without creating a local browsing address', ({ mock, remote }) => { + const manager = makeManager(mock, remote) + onTestFinished(() => manager.dispose()) + const entries = [{ id: S2, createdAt: 1, mode: 'external' as const, label: 'external task' }] + manager.handleControlFrame({ type: 'projection', sessionId: S1, key: 'subagentCatalog', seq: 0, value: entries }) + + expect(manager.getListSnapshot().projectionsBySession[S1]?.values.subagentCatalog).toEqual(entries) + expect(manager.subagentAddress(S2)).toBeUndefined() + expect(manager.getListSnapshot().items).toEqual([]) + }) + it('keeps a catalog-discovered child address across identity resolution and status frames', async ({ mock, remote, start }) => { remote.session.list.mockImplementation(() => Promise.resolve(ok({ items: [ summary(S1), diff --git a/packages/api/session-controller/tests/session-search.host.spec.ts b/packages/api/session-controller/tests/session-search.host.spec.ts index 1e38cd9788..d8690ed0a0 100644 --- a/packages/api/session-controller/tests/session-search.host.spec.ts +++ b/packages/api/session-controller/tests/session-search.host.spec.ts @@ -96,7 +96,7 @@ function installSearchQuery( describe('session.search', () => { it('rejects search when the query service is absent', async () => { const ctx = await baseContext() - const list = new ApiSessionList(ctx) + const list = new ApiSessionList(ctx, 8) await expect(list.search('query', new AbortController().signal)).rejects.toMatchObject({ code: 'gateway/internal', diff --git a/packages/api/session-controller/tests/session-skills.host.spec.ts b/packages/api/session-controller/tests/session-skills.host.spec.ts index 795a6c0b29..939ef7a269 100644 --- a/packages/api/session-controller/tests/session-skills.host.spec.ts +++ b/packages/api/session-controller/tests/session-skills.host.spec.ts @@ -1,5 +1,6 @@ import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' +import { provideWorkingDirectoryFixture } from '@deepseek-ai/dsh-agent-loop-testkit' import type { Agent } from '@deepseek-ai/dsh-agent' import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session' import { SessionQueryError, type SessionObservation } from '@deepseek-ai/dsh-session-query' @@ -9,7 +10,7 @@ import { SessionSkillCatalog } from '../src/skill-catalog.ts' function observation( sessionId: SessionId, - options: { readonly cwd?: string; readonly agentPreset?: string } = {}, + options: { readonly cwd?: string; readonly workingDirectory?: string | null; readonly agentPreset?: string } = {}, ): SessionObservation { const events = Object.freeze([]) const lease = (): SessionObservation => ({ @@ -28,6 +29,7 @@ function observation( asOfSeq: -1, values: { ...options.agentPreset === undefined ? {} : { agentPreset: options.agentPreset }, + ...options.workingDirectory === undefined ? {} : { workingDirectory: options.workingDirectory }, }, }, retain: lease, @@ -40,18 +42,20 @@ async function context(): Promise { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(AgentRegistry) + provideWorkingDirectoryFixture(ctx, '/catalog/default') return ctx } describe('SessionSkillCatalog', () => { - it('reads a cold Session catalog without resuming an Agent', async () => { + it('reads a moved cold Session catalog from its recorded current directory without resuming an Agent', async () => { const ctx = await context() const sessionId = SessionId('cold-skills') - const observed = observation(sessionId, { cwd: '/cold/project' }) + const observed = observation(sessionId, { cwd: '/cold/original', workingDirectory: '/cold/project' }) const dispose = vi.spyOn(observed, Symbol.dispose) const observeSession = vi.fn(() => Promise.resolve(observed)) ctx.provide('sessionQuery', { observeSession } as never) const resume = vi.spyOn(ctx.agents, 'resume') + const ensureDirectory = vi.spyOn(ctx.workingDirectory, 'ensure') const list = vi.fn(() => Promise.resolve([ { name: 'review', @@ -81,16 +85,47 @@ describe('SessionSkillCatalog', () => { expect(observeSession).toHaveBeenCalledWith(sessionId) expect(dispose).toHaveBeenCalledOnce() expect(resume).not.toHaveBeenCalled() + expect(ensureDirectory).not.toHaveBeenCalled() expect(ctx.agents.list()).toEqual([]) expect(list).toHaveBeenCalledWith({ cwd: '/cold/project', scope: undefined }) }) + it.each([ + { cwd: undefined, workingDirectory: '/cold/current', expected: '/cold/current' }, + { cwd: '/cold/original', workingDirectory: null, expected: '/cold/original' }, + { cwd: undefined, workingDirectory: null, expected: '/catalog/default' }, + { cwd: undefined, workingDirectory: undefined, expected: '/catalog/default' }, + ])('resolves cold directory metadata $cwd / $workingDirectory to $expected without filesystem checks', async ({ cwd, workingDirectory, expected }) => { + const ctx = await context() + try { + const sessionId = SessionId('cold-directory-fallback') + ctx.provide('sessionQuery', { + observeSession: () => Promise.resolve(observation(sessionId, { + ...cwd === undefined ? {} : { cwd }, + ...workingDirectory === undefined ? {} : { workingDirectory }, + })), + } as never) + const list = vi.fn(() => Promise.resolve([])) + ctx.provide('skills', { list } as never) + const ensure = vi.spyOn(ctx.workingDirectory, 'ensure') + const resume = vi.spyOn(ctx.agents, 'resume') + await expect(new SessionSkillCatalog(ctx).list({ sessionId }, new AbortController().signal)) + .resolves.toEqual({ skills: [] }) + expect(list).toHaveBeenCalledWith({ cwd: expected, scope: undefined }) + expect(ensure).not.toHaveBeenCalled() + expect(resume).not.toHaveBeenCalled() + } finally { + await ctx.fiber.dispose() + } + }) + it('uses a live Agent to address a preset-owned registry', async () => { const ctx = await context() const sessionId = SessionId('live-skills') const session = ctx.sessions.create(sessionId, { meta: { cwd: '/live/project' } }) const agent = { id: sessionId, session, status: 'idle', ctx } as Agent await ctx.agents.register(agent) + const ensureDirectory = vi.spyOn(ctx.workingDirectory, 'ensure').mockResolvedValue('/live/current') ctx.provide('sessionQuery', { observeSession: () => Promise.resolve(observation(sessionId, { cwd: '/live/project' })), } as never) @@ -113,7 +148,9 @@ describe('SessionSkillCatalog', () => { modelInvocable: false, }], }) - expect(scopedList).toHaveBeenCalledWith({ cwd: '/live/project', scope: agent }) + expect(scopedList).toHaveBeenCalledWith({ cwd: '/live/current', scope: agent }) + expect(ensureDirectory).toHaveBeenCalledWith(agent, expect.any(AbortSignal)) + expect(session.header.cwd).toBe('/live/project') expect(acquireScope).not.toHaveBeenCalled() }) @@ -192,22 +229,18 @@ describe('SessionSkillCatalog', () => { await expect(failed).rejects.toThrow('skill registry is absent') }) - it('rejects observations without projections or a project cwd', async () => { + it('rejects observations without projections', async () => { const ctx = await context() const sessionId = SessionId('incomplete-skills') const withoutProjections = { ...observation(sessionId, { cwd: '/project' }), projections: undefined } const observeSession = vi.fn() .mockResolvedValueOnce(withoutProjections) - .mockResolvedValueOnce(observation(sessionId)) ctx.provide('sessionQuery', { observeSession } as never) const catalog = new SessionSkillCatalog(ctx) const unprojected = catalog.list({ sessionId }, new AbortController().signal) await expect(unprojected).rejects.toMatchObject({ code: 'gateway/internal' }) await expect(unprojected).rejects.toThrow('projected Session observation') - const cwdless = catalog.list({ sessionId }, new AbortController().signal) - await expect(cwdless).rejects.toMatchObject({ code: 'gateway/internal' }) - await expect(cwdless).rejects.toThrow('has no project cwd') }) it('classifies a provider listing failure', async () => { diff --git a/packages/api/session-controller/tests/subagent-catalog.host.spec.ts b/packages/api/session-controller/tests/subagent-catalog.host.spec.ts index 3c8cf0ca00..4d9c4360fc 100644 --- a/packages/api/session-controller/tests/subagent-catalog.host.spec.ts +++ b/packages/api/session-controller/tests/subagent-catalog.host.spec.ts @@ -1,4 +1,5 @@ import { Context } from '@deepseek-ai/cordis' +import { provideWorkingDirectoryFixture } from '@deepseek-ai/dsh-agent-loop-testkit' import AgentRegistry from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' import SessionStore, { SESSION_FORMAT_VERSION, SessionLogOffset, SessionSeq, SessionId } from '@deepseek-ai/dsh-session' @@ -51,6 +52,7 @@ async function bench() { ), }) as never) installSessionReadTestServices(ctx) + provideWorkingDirectoryFixture(ctx) await ctx.plugin(SubagentRuntime) const controller = createSessionTestController(ctx, defaults) return { ctx, controller } diff --git a/packages/api/session-controller/tsconfig.host.json b/packages/api/session-controller/tsconfig.host.json index 9b80bc33f2..6376b437ca 100644 --- a/packages/api/session-controller/tsconfig.host.json +++ b/packages/api/session-controller/tsconfig.host.json @@ -23,34 +23,95 @@ "src/skill-catalog.ts" ], "references": [ - { "path": "../../../vendor/cordis" }, - { "path": "../../../vendor/schemastery" }, - { "path": "../../core/agent" }, - { "path": "../../core/agent-default-model" }, - { "path": "../../core/scope" }, - { "path": "../../core/session" }, - { "path": "../../context/file-reference" }, - { "path": "../../attachment/attachment" }, - { "path": "../../client/file-upload/tsconfig.host.json" }, - { "path": "../../client/connection/tsconfig.host.json" }, - { "path": "../../interaction/permission-presets" }, - { "path": "../../jobs/jobs" }, - { "path": "../../fs/fs" }, - { "path": "../../llm/llm" }, - { "path": "../../util/deque" }, - { "path": "../../util/native-command" }, - { "path": "../../preset/agent-preset-registry" }, - { "path": "../../session/session-persistence" }, - { "path": "../../session/session-projection" }, - { "path": "../../session/session-projection-cache" }, - { "path": "../../session/session-title" }, - { "path": "../../session-query/session-query" }, - { "path": "../../skill/skill" }, - { "path": "../../subagent/subagent" }, - { "path": "../../util/time" }, - { "path": "../../util/values" }, - { "path": "../../typert/protocol" }, - { "path": "../../typert/registry" }, - { "path": "../../workspace/workspace" } + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../../vendor/schemastery" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../core/agent-default-model" + }, + { + "path": "../../core/scope" + }, + { + "path": "../../core/session" + }, + { + "path": "../../context/file-reference" + }, + { + "path": "../../attachment/attachment" + }, + { + "path": "../../client/file-upload/tsconfig.host.json" + }, + { + "path": "../../client/connection/tsconfig.host.json" + }, + { + "path": "../../interaction/permission-presets" + }, + { + "path": "../../jobs/jobs" + }, + { + "path": "../../fs/fs" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../util/deque" + }, + { + "path": "../../util/native-command" + }, + { + "path": "../../preset/agent-preset-registry" + }, + { + "path": "../../session/session-persistence" + }, + { + "path": "../../session/session-projection" + }, + { + "path": "../../session/session-projection-cache" + }, + { + "path": "../../session/session-title" + }, + { + "path": "../../session-query/session-query" + }, + { + "path": "../../skill/skill" + }, + { + "path": "../../subagent/subagent" + }, + { + "path": "../../util/time" + }, + { + "path": "../../util/values" + }, + { + "path": "../../typert/protocol" + }, + { + "path": "../../typert/registry" + }, + { + "path": "../../workspace/workspace" + }, + { + "path": "../../session/working-directory" + } ] } diff --git a/packages/api/settings-controller/README.i18n.yaml b/packages/api/settings-controller/README.i18n.yaml index 8b39231902..4b3f0d24d3 100644 --- a/packages/api/settings-controller/README.i18n.yaml +++ b/packages/api/settings-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: a7475fd8aedf0830 zh: add1adce9d320baa /settings-controller/known-limitations-and-deferred-work/dev-note: - en: b684658b106c5d16 - zh: 1ba652cf16d8418b + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/settings-controller/README.md b/packages/api/settings-controller/README.md index b7472f6b0f..b38072546d 100644 --- a/packages/api/settings-controller/README.md +++ b/packages/api/settings-controller/README.md @@ -67,5 +67,3 @@ No direct effect; reading or writing these configuration values does not alter m None. - -**Runtime invariant:** No companion is published. The settings and credential seams own storage and update events, while this package only projects their methods onto the wire. diff --git a/packages/api/settings-controller/README.zh.md b/packages/api/settings-controller/README.zh.md index 9159e1f167..3c9e876740 100644 --- a/packages/api/settings-controller/README.zh.md +++ b/packages/api/settings-controller/README.zh.md @@ -67,5 +67,3 @@ kind: "package-reference" 无。 - -**运行时不变式:** 不发布伴生入口。settings 与凭据 seam 负责存储和更新事件,本包只把它们的方法投影到 wire。 diff --git a/packages/api/settings-controller/package.json b/packages/api/settings-controller/package.json index 8601923a52..ded8c8bf22 100644 --- a/packages/api/settings-controller/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/terminal-controller/README.i18n.yaml b/packages/api/terminal-controller/README.i18n.yaml index 545139127a..167264cbde 100644 --- a/packages/api/terminal-controller/README.i18n.yaml +++ b/packages/api/terminal-controller/README.i18n.yaml @@ -9,14 +9,14 @@ en: d5eb7912c65b4e26 zh: 7deb2a8167a82c90 /deepseek-ai-dsh-api-terminal-controller/summary: - en: 8e645f037a71a0f6 - zh: 421d962126692dd7 + en: afcf6f0d2f899ecc + zh: e38e2c0830e42ac9 /deepseek-ai-dsh-api-terminal-controller/table-of-contents: en: d152484eb41ac6b4 zh: 1fa9a72d1ee045cc /deepseek-ai-dsh-api-terminal-controller/use-this-package: - en: 0148cabb3a856226 - zh: 059a60413474a173 + en: abda92ff99530baa + zh: 67aa726392ae8e05 /deepseek-ai-dsh-api-terminal-controller/understand-the-implementation: en: 94dcd1835c55acc2 zh: cbfeb2da8cb60395 @@ -24,14 +24,14 @@ en: b1b5d2a2bfb8cc6a zh: aa0ea568aa63fda5 /deepseek-ai-dsh-api-terminal-controller/model-experience: - en: 2c52eec781441d2a - zh: 35a7c88f180f74a1 + en: 283529571e8cf73f + zh: 23336ccfbe65d315 /deepseek-ai-dsh-api-terminal-controller/model-experience/kv-cache-effect: - en: 507d3b3b80596fdc - zh: 4659007c4671afdc + en: be6f8e99d307c7ac + zh: 7dd4dcc0429e766c /deepseek-ai-dsh-api-terminal-controller/known-limitations-and-deferred-work: en: 6af6663b852441db zh: 244ed7729cae8d81 /deepseek-ai-dsh-api-terminal-controller/known-limitations-and-deferred-work/dev-note: - en: 2acda84e5b9a3ce2 - zh: f86e92da1c66ffff + en: e67f633dc6fe7773 + zh: 7aeea62eda52b83b diff --git a/packages/api/terminal-controller/README.md b/packages/api/terminal-controller/README.md index f33ce680c0..2709674163 100644 --- a/packages/api/terminal-controller/README.md +++ b/packages/api/terminal-controller/README.md @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Open the execution environment's default shell in a Session workspace from the Web sidebar. Reconnect to existing processes and close their complete provider-owned process ranges. Terminal output stays outside the Agent transcript. Keeping a terminal open retains its process and a bounded screen buffer. +Open the execution environment's default shell in the Session's current working directory from the Web sidebar. Reconnect to existing processes and close their complete provider-owned process ranges. Terminal output stays outside the Agent transcript. Keeping a terminal open retains its process and a bounded screen buffer. ## Table of Contents @@ -25,9 +25,9 @@ Open the execution environment's default shell in a Session workspace from the W ## Use this package -The Web bundle mounts this package with the subprocess provider, sandbox policy and Typert Gateway. Sandbox policy supplies only the fallback working directory for Sessions without a cwd. `remote.terminal` exposes `environment`, `shells`, `list`, `create`, `retain`, `follow`, `write`, `resize`, `rename` and `close`; each operation is scoped by Session identity. Listing reads retained Host terminals directly, so viewing an offline Session neither activates an Agent nor produces a recovery error. +The Web bundle mounts this package with the subprocess provider, working-directory service, sandbox policy and Typert Gateway. `remote.terminal` exposes `environment`, `shells`, `list`, `create`, `retain`, `follow`, `write`, `resize`, `rename` and `close`; each operation is scoped by Session identity. Listing reads retained Host terminals directly, so viewing an offline Session neither activates an Agent nor produces a recovery error. -Shell discovery lists the execution environment's declared default shell first. Only when the provider omits that default does resolution use `/bin/sh` on POSIX or `cmd.exe` on Windows. An optional `shell` profile overrides that choice with executable `path`, display `name` and `args` (default `[]`). The selector also probes `shellCandidates` through the execution provider and omits confirmed lookup misses. It lists each executable name once, ignoring letter case and `.exe`, and keeps the earliest entry, because PATH lookup can reach the default through another directory, such as `/usr/bin/bash` for `/bin/bash` on merged-`/usr` systems. Creation accepts a discovered `shellPath` and verifies it again; resolution or transport failure is reported without launching a different shell. Environment lookup returns the working directory and limits without resolving a shell, so an unavailable default does not prevent reattaching to an existing process. Automatic POSIX profiles start interactively, and PowerShell uses `-NoLogo`, so completion and startup configuration remain shell-owned. The Session workspace supplies the initial directory. User terminals run with the execution environment’s system-user permissions, independently of the Agent’s sandbox mode and approval policy. Operating-system and container restrictions still apply; DSH does not elevate the user. The subprocess provider retains its credential-environment scrubbing. +Shell discovery lists the execution environment's declared default shell first. Only when the provider omits that default does resolution use `/bin/sh` on POSIX or `cmd.exe` on Windows. An optional `shell` profile overrides that choice with executable `path`, display `name` and `args` (default `[]`). The selector also probes `shellCandidates` through the execution provider and omits confirmed lookup misses. It lists each executable name once, ignoring letter case and `.exe`, and keeps the earliest entry, because PATH lookup can reach the default through another directory, such as `/usr/bin/bash` for `/bin/bash` on merged-`/usr` systems. Creation accepts a discovered `shellPath` and verifies it again; resolution or transport failure is reported without launching a different shell. Environment lookup reads the current directory and limits without filesystem validation or shell resolution, so unavailable directories or default shells do not prevent reattaching to an existing process. Automatic POSIX profiles start interactively, and PowerShell uses `-NoLogo`, so completion and startup configuration remain shell-owned. New terminals validate the current directory through the [working-directory service](../../session/working-directory/README.md) before spawning, including its recovery behavior. Existing terminals keep their process directories. User terminals run with the execution environment’s system-user permissions, independently of the Agent’s sandbox mode and approval policy. Operating-system and container restrictions still apply; DSH does not elevate the user. The subprocess provider retains its credential-environment scrubbing. | Configuration | Default | Meaning | |---|---|---| @@ -76,11 +76,11 @@ Closing saves an unfinished cleanup request before releasing the tab, then await ## Model Experience -None, as this package handles user terminal interaction without adding model input. +Indirectly, through [working-directory recovery](../../session/working-directory/README.md): creating a terminal may update directory user context, while terminal output stays outside model history. #### KV Cache effect -None; terminal output travels only between the browser and Host. +Directory recovery appends user context without replacing the system-prompt prefix; terminal output stays outside model requests. ## Known Limitations and Deferred Work @@ -97,6 +97,6 @@ None; terminal output travels only between the browser and Host.
Working context for maintainers — click to expand -No runtime invariant companion is published. One owner orders terminal metadata and screen updates; the provider exposes no independently observed dimensions to compare. +None.
diff --git a/packages/api/terminal-controller/README.zh.md b/packages/api/terminal-controller/README.zh.md index d4d92f0d3e..49fc7ab343 100644 --- a/packages/api/terminal-controller/README.zh.md +++ b/packages/api/terminal-controller/README.zh.md @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -从 Web 侧栏在会话工作区打开执行环境的默认 shell。重新连接已有进程,并关闭 provider 管理的完整进程范围。终端输出不进入 Agent 对话记录。保留终端会占用进程和有界屏幕缓存。 +从 Web 侧栏在 Session 当前工作目录打开执行环境的默认 shell。重新连接已有进程,并关闭 provider 管理的完整进程范围。终端输出不进入 Agent 对话记录。保留终端会占用进程和有界屏幕缓存。 ## 目录 @@ -25,9 +25,9 @@ kind: "package-reference" ## 使用此包 -Web bundle 将此包与 subprocess provider、sandbox policy 和 Typert Gateway 一起挂载。Sandbox policy 仅为没有 cwd 的 Session 提供默认工作目录。`remote.terminal` 提供 `environment`、`shells`、`list`、`create`、`retain`、`follow`、`write`、`resize`、`rename` 和 `close`;每个操作均按 Session 标识限定范围。列表直接读取 Host 保留的终端,因此查看离线 Session 不会激活 Agent,也不会产生恢复错误。 +Web bundle 将此包与 subprocess provider、working-directory 服务、sandbox policy 和 Typert Gateway 一起挂载。`remote.terminal` 提供 `environment`、`shells`、`list`、`create`、`retain`、`follow`、`write`、`resize`、`rename` 和 `close`;每个操作均按 Session 标识限定范围。列表直接读取 Host 保留的终端,因此查看离线 Session 不会激活 Agent,也不会产生恢复错误。 -Shell 探测结果首先列出执行环境声明的默认 shell。仅当 provider 未声明默认值时,才在 POSIX 使用 `/bin/sh`,在 Windows 使用 `cmd.exe`。可选的 `shell` profile 通过可执行路径 `path`、显示名称 `name` 和参数 `args`(默认 `[]`)覆盖这一选择。选择器还会通过执行 provider 探测 `shellCandidates`,并省略确定未找到的候选。同一可执行文件名(不区分大小写,忽略 `.exe`)只列出最早的一项,因为 PATH 查找可能经由其他目录找到默认 shell,例如 merged-`/usr` 系统上 `/bin/bash` 对应的 `/usr/bin/bash`。创建请求接受探测返回的 `shellPath` 并再次验证;解析或传输失败会直接报告,不启动其他 shell。环境查询只返回工作目录和限制,不解析 shell,因此默认 shell 不可用时仍可重新连接已有进程。POSIX 自动 profile 以交互模式启动,PowerShell 使用 `-NoLogo`,补全和启动配置仍由 shell 提供。初始目录来自 Session 工作区。用户终端使用执行环境中系统用户的权限,独立于 Agent 的沙箱模式和审批策略。操作系统和容器的限制仍然生效;DSH 不提升用户权限。Subprocess provider 继续清除环境中的凭据变量。 +Shell 探测结果首先列出执行环境声明的默认 shell。仅当 provider 未声明默认值时,才在 POSIX 使用 `/bin/sh`,在 Windows 使用 `cmd.exe`。可选的 `shell` profile 通过可执行路径 `path`、显示名称 `name` 和参数 `args`(默认 `[]`)覆盖这一选择。选择器还会通过执行 provider 探测 `shellCandidates`,并省略确定未找到的候选。同一可执行文件名(不区分大小写,忽略 `.exe`)只列出最早的一项,因为 PATH 查找可能经由其他目录找到默认 shell,例如 merged-`/usr` 系统上 `/bin/bash` 对应的 `/usr/bin/bash`。创建请求接受探测返回的 `shellPath` 并再次验证;解析或传输失败会直接报告,不启动其他 shell。环境查询读取当前目录和限制,不校验文件系统或解析 shell,因此目录或默认 shell 不可用时仍可重新连接已有进程。POSIX 自动 profile 以交互模式启动,PowerShell 使用 `-NoLogo`,补全和启动配置仍由 shell 提供。新终端启动前通过[工作目录服务](../../session/working-directory/README.zh.md)校验当前目录,并采用该服务的恢复行为。已有终端保留其进程目录。用户终端使用执行环境中系统用户的权限,独立于 Agent 的沙箱模式和审批策略。操作系统和容器的限制仍然生效;DSH 不提升用户权限。Subprocess provider 继续清除环境中的凭据变量。 | 配置 | 默认值 | 含义 | |---|---|---| @@ -76,11 +76,11 @@ Client 在分配前将每条 Session/内容与终端身份的关联保存到独 ## 模型体验 -无;此包只处理用户交互式终端,不向模型请求添加内容。 +间接影响由[工作目录恢复](../../session/working-directory/README.zh.md)拥有:创建终端可能更新目录用户上下文,终端输出仍不进入模型历史。 #### KV 缓存影响 -无;终端输出只在浏览器与 Host 之间传输。 +目录恢复追加用户上下文,不替换 system-prompt 前缀;终端输出仍不进入模型请求。 ## 已知限制与延期工作 @@ -97,6 +97,6 @@ Client 在分配前将每条 Session/内容与终端身份的关联保存到独
维护说明 -不发布运行时 invariant companion。终端元数据与屏幕更新由同一对象按序写入,没有独立的进程尺寸观测可供比较。 +无。
diff --git a/packages/api/terminal-controller/package.json b/packages/api/terminal-controller/package.json index aea86312b3..9b67bd26cc 100644 --- a/packages/api/terminal-controller/package.json +++ b/packages/api/terminal-controller/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-terminal-controller", "description": "Session-owned interactive terminals with shell discovery, screen recovery and typed Remote control", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -80,7 +80,8 @@ "@deepseek-ai/dsh-session-projection": "workspace:*", "@deepseek-ai/dsh-subprocess": "workspace:*", "@deepseek-ai/dsh-subprocess-local": "workspace:*", - "@deepseek-ai/dsh-util-crypto": "workspace:*" + "@deepseek-ai/dsh-util-crypto": "workspace:*", + "@deepseek-ai/dsh-working-directory": "workspace:*" }, "files": [ "lib/index.js", diff --git a/packages/api/terminal-controller/src/index.ts b/packages/api/terminal-controller/src/index.ts index 24bf1a1f01..953c34a9fc 100644 --- a/packages/api/terminal-controller/src/index.ts +++ b/packages/api/terminal-controller/src/index.ts @@ -4,6 +4,7 @@ import z from '@deepseek-ai/schemastery' import type { Agent } from '@deepseek-ai/dsh-agent' import type { SessionId } from '@deepseek-ai/dsh-session' import type {} from '@deepseek-ai/dsh-sandbox-policy' +import type {} from '@deepseek-ai/dsh-working-directory' import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' import { discoverShells, resolveShell } from './shells.ts' import { BrowserTerminal } from './terminal.ts' @@ -73,7 +74,7 @@ interface OwnedSession { /** Typed Remote control of transient Session-owned terminal processes. */ export class TerminalController extends TypertRemoteService { - static inject = ['subprocess', 'sandboxPolicy', 'typert'] + static inject = ['subprocess', 'sandboxPolicy', 'workingDirectory', 'typert'] static Config: z = z.object({ shell: z.union([z.object({ path: z.string().required(), name: z.string().required(), args: z.array(z.string()).default([]), @@ -109,16 +110,16 @@ export class TerminalController extends TypertRemoteService { } /** - * Read the Session working directory and terminal limits without resolving a shell. + * Read the Session's current directory and terminal limits without filesystem validation or shell lookup. * @param agent - Session owner supplied by the Gateway. * @param signal - request cancellation. - * @returns the Session workspace directory and terminal limits. + * @returns the logged current directory and terminal limits; retained terminals keep their own process directories. */ @Remote environment(agent: Agent, signal: AbortSignal): TerminalEnvironment { signal.throwIfAborted() - const { sandboxPolicy } = this.execution(agent) - return { cwd: agent.session.header.cwd ?? sandboxPolicy.workspaceRoot, + const { workingDirectory } = this.execution(agent) + return { cwd: workingDirectory.get(agent.session), maxInputBytes: this.config.maxInputBytes, maxCols: this.config.maxCols, maxRows: this.config.maxRows, scrollback: this.config.scrollback } } @@ -328,29 +329,31 @@ export class TerminalController extends TypertRemoteService { || !Number.isSafeInteger(rows) || rows < 1 || rows > this.config.maxRows) throw new Error('Terminal dimensions exceed the configured limits') } - private execution(agent: Agent): { subprocess: Context['subprocess']; sandboxPolicy: Context['sandboxPolicy'] } { + private execution(agent: Agent): { subprocess: Context['subprocess']; sandboxPolicy: Context['sandboxPolicy']; workingDirectory: Context['workingDirectory'] } { // The Agent context selects execution providers but does not inject consumer services. const subprocess = agent.ctx.get('subprocess') const sandboxPolicy = agent.ctx.get('sandboxPolicy') if (subprocess === undefined || sandboxPolicy === undefined) throw new Error('The Session execution environment requires subprocess and sandbox policy providers') - return { subprocess, sandboxPolicy } + const workingDirectory = agent.ctx.get('workingDirectory') + if (workingDirectory === undefined) throw new Error('The Session execution environment requires a working-directory provider') + return { subprocess, sandboxPolicy, workingDirectory } } private async spawn(agent: Agent, owner: OwnedSession, request: TerminalCreateRequest, signal: AbortSignal): Promise { - const environment = this.environment(agent, signal) - const { subprocess } = this.execution(agent) + const { subprocess, workingDirectory } = this.execution(agent) + const cwd = await workingDirectory.ensure(agent, signal) const shell = request.shellPath === undefined ? await resolveShell(subprocess, this.config.shell, signal) : (await this.shells(agent, signal)).find(candidate => candidate.path === request.shellPath) if (shell === undefined) throw new Error('Selected shell is not available in this execution environment') const handle = await subprocess.spawnTerminal({ - argv: [shell.path, ...shell.args], cwd: environment.cwd, cols: request.cols, rows: request.rows, + argv: [shell.path, ...shell.args], cwd, cols: request.cols, rows: request.rows, terminalType: 'xterm-256color', env: { DSH_SESSION_ID: agent.id }, shellActivity: true, graceMs: this.config.disposeGraceMs, signal, }) const info: WebTerminalInfo = { - id: request.id, shell, title: shell.name, cwd: environment.cwd, + id: request.id, shell, title: shell.name, cwd, cols: request.cols, rows: request.rows, state: 'running', exitCode: null, } try { diff --git a/packages/api/terminal-controller/src/shells.ts b/packages/api/terminal-controller/src/shells.ts index 7c1f6a4ce2..ac89696776 100644 --- a/packages/api/terminal-controller/src/shells.ts +++ b/packages/api/terminal-controller/src/shells.ts @@ -1,5 +1,5 @@ /** Shell selection and executable verification use the target execution provider. */ -import { SubprocessExecutableNotFoundError, type SubprocessRuntime } from '@deepseek-ai/dsh-subprocess' +import type { SubprocessRuntime } from '@deepseek-ai/dsh-subprocess' import type { TerminalShell } from './types.ts' /** @@ -51,7 +51,8 @@ export async function discoverShells( const found = await Promise.all(candidates.map(async (candidate) => { try { return await resolveShell(subprocess, profile(candidate), signal) } catch (error) { - if (error instanceof SubprocessExecutableNotFoundError) return undefined + if (typeof error === 'object' && error !== null + && 'name' in error && error.name === 'SubprocessExecutableNotFoundError') return undefined throw error } })) diff --git a/packages/api/terminal-controller/src/types.ts b/packages/api/terminal-controller/src/types.ts index 5e1df56868..8490ea1a7f 100644 --- a/packages/api/terminal-controller/src/types.ts +++ b/packages/api/terminal-controller/src/types.ts @@ -30,8 +30,9 @@ export interface TerminalShell { readonly name: string } -/** Working directory and limits shared by new and restored terminals. */ +/** Current Session directory and terminal limits; retained terminals keep their own process directories. */ export interface TerminalEnvironment { + /** Logged current directory, read without filesystem validation. */ readonly cwd: string readonly maxInputBytes: number readonly maxCols: number diff --git a/packages/api/terminal-controller/tests/controller.spec.ts b/packages/api/terminal-controller/tests/controller.spec.ts index a516965228..d9f7ee273f 100644 --- a/packages/api/terminal-controller/tests/controller.spec.ts +++ b/packages/api/terminal-controller/tests/controller.spec.ts @@ -30,7 +30,12 @@ function fixture(overrides: Partial = {}) { roots.push(ctx) const effects = vi.spyOn(ctx.fiber, 'effect') const sandboxPolicy = { defaultMode: 'danger-full-access', workspaceRoot: '/workspace', resolve: vi.fn((): SandboxExecutionPolicy => ({ mode: 'danger-full-access', workspaceRoot: '/workspace' })) } + const workingDirectory = { + get: vi.fn<(session: Agent['session']) => string>(session => session.header.cwd ?? '/workspace'), + ensure: vi.fn<(agent: Agent, signal?: AbortSignal) => Promise>(async agent => agent.session.header.cwd ?? '/workspace'), + } ctx.provide('sandboxPolicy', sandboxPolicy as never) + ctx.provide('workingDirectory', workingDirectory as never) const output = new PassThrough() const done = Promise.withResolvers<{ exitCode: number; signal: null }>() const handle = { @@ -48,7 +53,7 @@ function fixture(overrides: Partial = {}) { if (result?.type !== 'return' || typeof result.value !== 'function') throw new Error(`Missing effect: ${label}`) return result.value() } - return { ctx, agent: owner(ctx), controller, subprocess, handle, sandboxPolicy, disposeEffect } + return { ctx, agent: owner(ctx), controller, subprocess, handle, sandboxPolicy, workingDirectory, disposeEffect } } describe('TerminalController', () => { @@ -100,6 +105,75 @@ describe('TerminalController', () => { expect(sandboxPolicy.resolve).not.toHaveBeenCalled() }) + it('uses the current directory for new user terminals while retaining earlier directories and permissions', async () => { + const { ctx, controller, agent, subprocess, sandboxPolicy, workingDirectory } = fixture() + const policy: SandboxExecutionPolicy = { mode: 'workspace-write', workspaceRoot: '/workspace', sessionId: agent.id } + sandboxPolicy.resolve.mockReturnValue(policy) + const confine = vi.fn(async (argv: readonly string[], _policy: SandboxExecutionPolicy) => ({ argv: [...argv] })) + ctx.provide('sandbox', { confine } as never) + const first = await controller.create(agent, request, signal()) + workingDirectory.get.mockReturnValue('/workspace/selected') + workingDirectory.ensure.mockResolvedValue('/workspace/selected') + + expect(controller.environment(agent, signal()).cwd).toBe('/workspace/selected') + expect(workingDirectory.ensure).toHaveBeenCalledOnce() + expect(await controller.create(agent, request, signal())).toBe(first) + expect(first.cwd).toBe('/workspace') + const second = await controller.create(agent, { ...request, id: 'second' as WebTerminalId }, signal()) + + expect(second.cwd).toBe('/workspace/selected') + expect(subprocess.spawnTerminal).toHaveBeenNthCalledWith(1, expect.objectContaining({ cwd: '/workspace' })) + expect(subprocess.spawnTerminal).toHaveBeenNthCalledWith(2, expect.objectContaining({ cwd: '/workspace/selected' })) + expect(confine).not.toHaveBeenCalled() + expect(sandboxPolicy.resolve).not.toHaveBeenCalled() + expect(controller.list(agent.id).map(terminal => terminal.cwd)).toEqual(['/workspace', '/workspace/selected']) + }) + + it('captures the validated directory before a delayed shell lookup', async () => { + const { controller, agent, subprocess, workingDirectory } = fixture() + workingDirectory.ensure.mockResolvedValue('/workspace/starting') + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + subprocess.resolveExecutable.mockImplementationOnce(async (path) => { + entered.resolve(undefined) + await release.promise + return path + }) + const creating = controller.create(agent, request, signal()) + try { + await entered.promise + workingDirectory.get.mockReturnValue('/workspace/later') + workingDirectory.ensure.mockResolvedValue('/workspace/later') + release.resolve(undefined) + expect((await creating).cwd).toBe('/workspace/starting') + expect(subprocess.spawnTerminal).toHaveBeenCalledWith(expect.objectContaining({ cwd: '/workspace/starting' })) + expect(workingDirectory.ensure).toHaveBeenCalledOnce() + } finally { + release.resolve(undefined) + await creating + } + }) + + it('uses recovered directory values for new creation and leaves environment reads unvalidated', async () => { + const { controller, agent, subprocess, workingDirectory } = fixture() + workingDirectory.get.mockReturnValue('/missing') + workingDirectory.ensure.mockResolvedValue('/workspace/recovered') + expect(controller.environment(agent, signal()).cwd).toBe('/missing') + expect(workingDirectory.ensure).not.toHaveBeenCalled() + + const created = await controller.create(agent, request, signal()) + + expect(created.cwd).toBe('/workspace/recovered') + expect(workingDirectory.ensure).toHaveBeenCalledWith(agent, expect.any(AbortSignal)) + expect(subprocess.spawnTerminal).toHaveBeenCalledWith(expect.objectContaining({ cwd: '/workspace/recovered' })) + workingDirectory.ensure.mockRejectedValue(new Error('original directory is unavailable')) + expect(controller.environment(agent, signal()).cwd).toBe('/missing') + expect(await controller.create(agent, request, signal())).toBe(created) + await expect(controller.create(agent, { ...request, id: 'unavailable' as WebTerminalId }, signal())) + .rejects.toThrow('original directory is unavailable') + expect(subprocess.spawnTerminal).toHaveBeenCalledOnce() + }) + it('deduplicates concurrent creates, scopes access by Session, and preserves terminals when a stream aborts', async () => { const { controller, agent, ctx, subprocess, handle } = fixture() const [first, second] = await Promise.all([controller.create(agent, request, signal()), controller.create(agent, request, signal())]) @@ -299,13 +373,15 @@ describe('TerminalController', () => { expect(subprocess.spawnTerminal).toHaveBeenCalledWith(expect.objectContaining({ cwd: '/another-workspace' })) }) - it.each(['subprocess', 'sandboxPolicy'] as const)('fails clearly when the Session lacks %s', (missing) => { - const { controller, subprocess, sandboxPolicy } = fixture() + it.each(['subprocess', 'sandboxPolicy', 'workingDirectory'] as const)('fails clearly when the Session lacks %s', (missing) => { + const { controller, subprocess, sandboxPolicy, workingDirectory } = fixture() const isolated = new Context() roots.push(isolated) if (missing !== 'subprocess') isolated.provide('subprocess', subprocess as never) if (missing !== 'sandboxPolicy') isolated.provide('sandboxPolicy', sandboxPolicy as never) - expect(() => controller.environment(owner(isolated), signal())).toThrow('requires subprocess and sandbox policy providers') + if (missing !== 'workingDirectory') isolated.provide('workingDirectory', workingDirectory as never) + expect(() => controller.environment(owner(isolated), signal())).toThrow(missing === 'workingDirectory' + ? 'requires a working-directory provider' : 'requires subprocess and sandbox policy providers') }) it('allows Agent sandbox-mode changes while retaining the same user terminal', async () => { @@ -513,8 +589,10 @@ it.skipIf(process.platform === 'win32')('runs a real interactive shell with comp it('discovers installed shells once per path, preserves default arguments, and refuses unlisted paths', async () => { const h = fixture({ shellCandidates: ['bash', 'zsh', 'missing'] }) + const missing = Object.assign(new Error('absent'), { name: 'SubprocessExecutableNotFoundError' }) + expect(missing).not.toBeInstanceOf(SubprocessExecutableNotFoundError) h.subprocess.resolveExecutable.mockImplementation(async (path) => { - if (path === 'missing') throw new SubprocessExecutableNotFoundError('absent') + if (path === 'missing') throw missing return path.startsWith('/') ? path : `/bin/${path}` }) const shells = await h.controller.shells(h.agent, signal()) diff --git a/packages/api/terminal-controller/tsconfig.host.json b/packages/api/terminal-controller/tsconfig.host.json index ec31d46046..68bc1a559e 100644 --- a/packages/api/terminal-controller/tsconfig.host.json +++ b/packages/api/terminal-controller/tsconfig.host.json @@ -38,6 +38,9 @@ { "path": "../../session/session-projection" }, + { + "path": "../../session/working-directory" + }, { "path": "../../subprocess/subprocess" }, diff --git a/packages/api/workspace-controller/README.i18n.yaml b/packages/api/workspace-controller/README.i18n.yaml index 6df6cd44a3..d804399b83 100644 --- a/packages/api/workspace-controller/README.i18n.yaml +++ b/packages/api/workspace-controller/README.i18n.yaml @@ -30,5 +30,5 @@ en: 43b497c19779532e zh: c6d9d3afff774219 /workspace-controller/known-limitations-and-deferred-work/dev-note: - en: 0f74591fc1097e46 - zh: f4df9e5617a50a9f + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/api/workspace-controller/README.md b/packages/api/workspace-controller/README.md index ab96bb0114..b24053384f 100644 --- a/packages/api/workspace-controller/README.md +++ b/packages/api/workspace-controller/README.md @@ -70,5 +70,3 @@ No direct effect; Workspace mutations do not alter model requests. None. - -**Runtime invariant:** No companion is published. Workspace Registry owns persistence; every stream generation is a full projection. diff --git a/packages/api/workspace-controller/README.zh.md b/packages/api/workspace-controller/README.zh.md index bcf713306e..dc240950cc 100644 --- a/packages/api/workspace-controller/README.zh.md +++ b/packages/api/workspace-controller/README.zh.md @@ -70,5 +70,3 @@ Documents 查询占用注册表变更队列,因此其他 Workspace 变更( 无。 - -**运行时不变式:** 不发布伴生入口。Workspace 注册表负责持久化,每次流生成都是完整投影。 diff --git a/packages/api/workspace-controller/package.json b/packages/api/workspace-controller/package.json index 8cd898beed..118a4772a9 100644 --- a/packages/api/workspace-controller/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/api/workspace-files/README.i18n.yaml b/packages/api/workspace-files/README.i18n.yaml index e76b5b1e0f..0dbb9b3f71 100644 --- a/packages/api/workspace-files/README.i18n.yaml +++ b/packages/api/workspace-files/README.i18n.yaml @@ -48,8 +48,8 @@ en: c3a8852c33dcc0e9 zh: d24fd47da7faaa29 /deepseek-ai-dsh-api-workspace-files/understand-the-implementation/source-map: - en: 2394243d582bb452 - zh: 763e56ca827efa0d + en: 596a75f43bfafda3 + zh: 3cb267a0946cd75e /deepseek-ai-dsh-api-workspace-files/further-exploration: en: 71f00fb919d12181 zh: 1e3819cd90dfca31 diff --git a/packages/api/workspace-files/README.md b/packages/api/workspace-files/README.md index 3511616eea..9ceb0105fa 100644 --- a/packages/api/workspace-files/README.md +++ b/packages/api/workspace-files/README.md @@ -105,7 +105,6 @@ Complete-file reads delegate size enforcement to `fs.readBytes` and return raw b | [`src/types.ts`](src/types.ts) | Wire types and the `RemoteErrorDetailsMap` codes, published as `./types` for Client packages | | [`src/client/index.ts`](src/client/index.ts), [`provider.ts`](src/client/provider.ts), [`change-feed.ts`](src/client/change-feed.ts) | Browser plugin, file metadata, and per-target change feeds | | [`src/client/types.ts`](src/client/types.ts), [`remote.ts`](src/client/remote.ts) | Resource values, parameters, Client error codes, and generated Remote types | -| — | No runtime invariant companion is published; every Host answer is derived from `ctx.fs` and the sandbox policy at call time. | Typert generates the Host and Client Remote artifacts exposed by `./typert` and `./remote`. diff --git a/packages/api/workspace-files/README.zh.md b/packages/api/workspace-files/README.zh.md index d11740b8e7..e1f0e0000d 100644 --- a/packages/api/workspace-files/README.zh.md +++ b/packages/api/workspace-files/README.zh.md @@ -105,7 +105,6 @@ kind: "package-reference" | [`src/types.ts`](src/types.ts) | 线路类型与 `RemoteErrorDetailsMap` 错误码,以 `./types` 发布给 Client 包 | | [`src/client/index.ts`](src/client/index.ts)、[`provider.ts`](src/client/provider.ts)、[`change-feed.ts`](src/client/change-feed.ts) | 浏览器插件、文件元数据与按目标建立的变更流 | | [`src/client/types.ts`](src/client/types.ts)、[`remote.ts`](src/client/remote.ts) | 资源值、参数、Client 错误码与生成的 Remote 类型 | -| — | 不发布运行时 invariant 伴生件;每个 Host 答案都在调用时由 `ctx.fs` 与沙箱策略推导。 | Typert 生成 `./typert` 与 `./remote` 暴露的 Host 与 Client Remote 产物。 diff --git a/packages/api/workspace-files/package.json b/packages/api/workspace-files/package.json index d1537d23c5..01a35b7da2 100644 --- a/packages/api/workspace-files/package.json +++ b/packages/api/workspace-files/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-api-workspace-files", "description": "Workspace file service and Client resource provider: bounded reads, directory listing, and live metadata over the workspaceFiles Remote namespace", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/attachment/attachment-local/README.i18n.yaml b/packages/attachment/attachment-local/README.i18n.yaml index 4a8f7cd11f..520e5af483 100644 --- a/packages/attachment/attachment-local/README.i18n.yaml +++ b/packages/attachment/attachment-local/README.i18n.yaml @@ -39,8 +39,8 @@ en: 609ccc784a87b770 zh: bc702582cf2b3316 /deepseek-ai-dsh-attachment-local/understand-the-implementation/source-map: - en: b092fd38c68a06bd - zh: 4f3f4f1ec98401f4 + en: 05af85aa85934d01 + zh: 1c89e80d496dc0a3 /deepseek-ai-dsh-attachment-local/further-exploration: en: f8b09b2542211379 zh: 57a8fa44aa76b737 diff --git a/packages/attachment/attachment-local/README.md b/packages/attachment/attachment-local/README.md index ea15a8700b..4303813905 100644 --- a/packages/attachment/attachment-local/README.md +++ b/packages/attachment/attachment-local/README.md @@ -99,7 +99,6 @@ Generic-file bytes have one canonical object at `/attachments/v1/file- | [`src/normalization.ts`](src/normalization.ts) + [`src/encoding.ts`](src/encoding.ts) | Provider-independent normalization and bounded format/quality candidates | | [`src/request-image.ts`](src/request-image.ts) | Route-specific request transforms, cache identity, and singleflight | | [`src/image.ts`](src/image.ts) | Full raster decode and metadata verification | -| — | No runtime invariant companion is published; immutable writes and verified reads are enforced directly at the backend boundary. | diff --git a/packages/attachment/attachment-local/README.zh.md b/packages/attachment/attachment-local/README.zh.md index f9b24dfd8e..4c52dab3ac 100644 --- a/packages/attachment/attachment-local/README.zh.md +++ b/packages/attachment/attachment-local/README.zh.md @@ -99,7 +99,6 @@ kind: "package-reference" | [`src/normalization.ts`](src/normalization.ts) + [`src/encoding.ts`](src/encoding.ts) | 提供方无关的规范化与有界格式/质量候选 | | [`src/request-image.ts`](src/request-image.ts) | 路由专用请求变换、缓存身份与 singleflight | | [`src/image.ts`](src/image.ts) | 完整光栅解码与元数据校验 | -| — | 不发布运行时不变式伴生入口;不可变写入与校验读取在后端边界直接强制。 | diff --git a/packages/attachment/attachment-local/package.json b/packages/attachment/attachment-local/package.json index 5f25e49265..810513ab0e 100644 --- a/packages/attachment/attachment-local/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -28,7 +28,7 @@ "dependencies": { "@deepseek-ai/dsh-lazy-require": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", - "sharp": "^0.35.3" + "sharp": "^0.35.5" }, "devDependencies": { "@deepseek-ai/dsh-attachment": "workspace:*", diff --git a/packages/attachment/attachment/README.i18n.yaml b/packages/attachment/attachment/README.i18n.yaml index 0c7491db3a..4f941409fe 100644 --- a/packages/attachment/attachment/README.i18n.yaml +++ b/packages/attachment/attachment/README.i18n.yaml @@ -42,8 +42,8 @@ en: 4a35cdba790c3b8b zh: 08d4a18bbad4b182 /deepseek-ai-dsh-attachment/understand-the-implementation/source-map: - en: a196e1f4cf9895ae - zh: 39b430b53d30297e + en: 4fb4c96184c38169 + zh: 015b06e4bfff4fcd /deepseek-ai-dsh-attachment/further-exploration: en: 0f179d9ab84a962a zh: 4a1d0722a3309f4a diff --git a/packages/attachment/attachment/README.md b/packages/attachment/attachment/README.md index d2a667e2d1..43eb4d8ae8 100644 --- a/packages/attachment/attachment/README.md +++ b/packages/attachment/attachment/README.md @@ -83,7 +83,6 @@ The service family runs one admission-and-storage flow: every entry point enforc | [`src/admission.ts`](src/admission.ts) | Canonical-base64 enforcement and store delegation for encoded image and file uploads | | [`src/error.ts`](src/error.ts) | `AttachmentError` class and the `isImageAdmissionError` runtime subset | | [`src/brand.ts`](src/brand.ts) | `AttachmentId` branded opaque identifier | -| — | No runtime invariant companion is published; this stateless seam owns types while implementations enforce immutable-store checks. | diff --git a/packages/attachment/attachment/README.zh.md b/packages/attachment/attachment/README.zh.md index b5598eb8cc..4fa95d20f3 100644 --- a/packages/attachment/attachment/README.zh.md +++ b/packages/attachment/attachment/README.zh.md @@ -83,7 +83,6 @@ kind: "package-reference" | [`src/admission.ts`](src/admission.ts) | 对编码图片和文件上传强制执行规范 base64 并委托存储 | | [`src/error.ts`](src/error.ts) | `AttachmentError` 类与 `isImageAdmissionError` 运行时子集 | | [`src/brand.ts`](src/brand.ts) | `AttachmentId` 带类型标记的不透明标识符 | -| — | 不发布运行时不变式伴生入口;这个无状态 seam 承载类型,实现则负责强制执行不可变存储检查。 | diff --git a/packages/attachment/attachment/package.json b/packages/attachment/attachment/package.json index 9876949586..e119eb4700 100644 --- a/packages/attachment/attachment/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index bd29896a32..df0e4f782e 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -24,8 +24,8 @@ en: 1099528bde0e2aee zh: b68772bdb9bb7856 /deepseek-ai-dsh-app-boot/use-this-package/profiles: - en: d50b0fdaf62353dd - zh: 0fccb6968602e5de + en: 1db121100677e45f + zh: c6a8c6316020eb1b /deepseek-ai-dsh-app-boot/use-this-package/previewing-the-effective-configuration: en: 646a0ccdd7881ad1 zh: e3696acb1573dd7b @@ -33,11 +33,11 @@ en: c4a301652b851ce9 zh: 784871e19cd9db0a /deepseek-ai-dsh-app-boot/use-this-package/reading-plugin-display-metadata: - en: 480ad3a4d96112f0 - zh: 10a92f1013aa5ac4 + en: ebf6cb3960a5b378 + zh: 41ba129a0f86c168 /deepseek-ai-dsh-app-boot/use-this-package/startup-and-reload-failures: - en: 09890e218ab49c84 - zh: e3d86597e7fb1adb + en: b2e5cfdf8847d214 + zh: 8d65771a3bd8a7c9 /deepseek-ai-dsh-app-boot/use-this-package/telling-the-agent-where-the-harness-lives: en: 07c459f26f4f8517 zh: 210f1e4894731b1a @@ -45,17 +45,17 @@ en: f4a2695cb4c8a570 zh: 89837235d2f376cf /deepseek-ai-dsh-app-boot/understand-the-implementation/design-notes: - en: 4ac0c33806b5a5b2 - zh: cdb56f3adfcb4364 + en: 25a0d1b46fd66d68 + zh: 5cd8b4ac382dccdc /deepseek-ai-dsh-app-boot/understand-the-implementation/helper-behavior: en: f7bc3a8dd4b00bdd zh: 30098428aa769ad0 /deepseek-ai-dsh-app-boot/understand-the-implementation/source-map: - en: 25602fd8eb9b2907 - zh: 870a33dd34a52c32 + en: 3da01827e3354655 + zh: 06ccfaed83f65a76 /deepseek-ai-dsh-app-boot/further-exploration: - en: ffd10e94443a10ca - zh: cd11495bae751bdd + en: 401269cc5ea8593e + zh: cd337c356b320565 /deepseek-ai-dsh-app-boot/model-experience: en: 769777f1cb1a9408 zh: f02b2834179df50d diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index aae8f44cc6..4821673423 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -62,7 +62,21 @@ Your machine-local preferences also live in the Harness home: The enabled `dsh-hmr` plugin watches the profile manifest and both user patch files, re-reads the ordered bundle layers, and applies the [reload failure policy](#startup-and-reload-failures). [DSH HMR](../hmr/README.md) serializes these reloads with [Plugin Manager](../plugin-manager/README.md) configuration writes; package operations run outside its queue. The launcher does not install HMR or watchers; disabled or absent HMR means changes require restart. -Inserted plugin names may be absolute filesystem paths, file URLs, or package specifiers. Patch loading converts absolute paths and patch-relative `./` or `../` paths to file URLs within `insert` rows and their nested groups; existing-entry name assertions and replacement `config` values remain literal. +Inserted plugin names may be absolute filesystem paths, file URLs, or package specifiers. Patch loading converts absolute paths and patch-relative `./` or `../` paths to file URLs within `insert` rows, their nested groups, and inserted preset definitions; existing-entry name assertions and replacement `config` values remain literal. + +A profile patch may set `preset` to an outer row id and apply the remaining ordinary patch fields inside that row's literal `config.plugins` list. Any bundle or user layer can contribute these operations. Each operation sees the current child list; later user operations take precedence, and a later whole-preset `config` replacement replaces every earlier child contribution. Ordinary outer patches retain Include's single-index lookup: children introduced by replacing a group's `config` are not addressable outer targets. Missing or malformed outer preset targets and duplicate child ids introduced by an insertion fail composition. An unmatched inner `id` warns and skips, so disabling a bundle does not invalidate an old user override of its child. + +```yaml +- preset: preset-standard + insert: + - id: optional-string-editor + name: '@deepseek-ai/dsh-tool-str-replace-editor' +- preset: preset-standard + id: optional-string-editor + disabled: true +``` + +`ProfilePatch` extends the native patch fields with `preset`. `applyProfilePatches` composes these operations; `compileProfilePatches` returns ordinary Include patches. Startup, profile reload, effective-config dumps, schema inspection, and compatibility preflight share this compiler. Scoped `insert` module paths resolve beside their declaring patch, including nested groups; `!!js` remains unevaluated until each child activates. Native included-file patch lists keep their ordinary Include syntax. Loaded scoped insertions without IDs receive stable internal IDs derived from the canonical declaring file and entry position, including nested groups; ordinary anonymous root rows keep Loader’s existing behavior. Generated IDs identify contributions across reloads and retained revisions, and are implementation details rather than names to use in authored patches. Compiler failures name the declaring patch file and operation, including after profile layers are combined. Before mounting profile rows, the `dsh` launcher computes one immutable runtime resolution from the installation and ordered bundle dependency graphs. Every profile launcher uses runtime resolution, including plain Node, packaged executables, and the Electron Host. It installs the runtime resolution through Node's ESM and CommonJS resolvers without creating fallback links. @@ -82,12 +96,18 @@ The projector preserves native omission behavior by checking literal defaults ag ### Reading plugin display metadata -Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. Missing locale fields fall back to the accessible `package.json` at that address; malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads `package.json.icon` as an image data URL, even when locale text is complete; an icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format. +`resolvePluginResource(specifier, parentURL)` resolves a plugin module or exported resource to its local filesystem path through the active Node ESM resolver without evaluating it. It throws when the resolver is unavailable or the resource cannot resolve to a local file. + +`realModuleFile(path)` canonicalizes an existing module resource in Node and packaged executables. It follows physical file symlinks and preserves archive-backed resources through their canonical containing directory. Missing or inaccessible resources throw. + +Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. For a package-root specifier, missing locale fields fall back to the accessible `package.json`; a subpath specifier never reads a `package.json`. Malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads an image data URL, even when locale text is complete: a package root uses its manifest `icon`, or `/icon` when that field is omitted; a subpath uses `/icon`. An icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format. + +`ON_DEMAND_BUNDLES` names the public Official packages installed only on request. `OFFICIAL_ON_DEMAND_CATALOG` embeds their package-owned localized metadata and icons for offline discovery; `pnpm gen-official-bundle-catalog` explicitly updates generated content, and `verify-official-bundle-catalog` checks resource completeness and freshness; builds consume the committed catalog without importing provider code. The catalog contains no install target: [Plugin Manager](../plugin-manager/README.md#use-this-package) derives it from the running version and installation and uses the ordinary bundle installer. The [product-use check](../../../scripts/verify-product-use.ts) composes shipped and on-demand selections against Web without executing providers. ### Startup and reload failures -Profile reconciliation returns diagnostics for unchanged inactive entries without failing an unrelated mutation. A new inactive entry, a changed configuration or fiber, or a changed diagnostic fails reconciliation; removed fibers must still finish disposal. Explicit enablement targets must activate even when their failure predates the operation. Successful reconciliation emits `app-boot/config-reload` after lifecycle settlement and diagnostic checks, including programmatic updates without HMR. The event carries no diff or parsed config. Successful reconciliation returns after lifecycle settlement and diagnostic checks; volatile-only entry changes are committed by Loader during the update. +Profile reconciliation returns diagnostics for unchanged inactive entries without failing an unrelated mutation. A new inactive entry, a changed configuration or fiber, or a changed diagnostic fails reconciliation; removed fibers must still finish disposal. Active root rows that disappear or become literally disabled release their resources before replacement rows activate, so a singleton provider can drain in-flight work before its successor registers. Compilation and disposal checks use the same parsed root entries for each reconciliation. Explicit enablement targets must activate even when their failure predates the operation. Successful reconciliation emits `app-boot/config-reload` after lifecycle settlement and diagnostic checks, including programmatic updates without HMR. The event carries no diff or parsed config. Successful reconciliation returns after lifecycle settlement and diagnostic checks; volatile-only entry changes are committed by Loader during the update. After the Loader settles, app-boot warns when only optional entries are inactive. If an enabled required entry cannot activate, `boot()` rejects with `StartupError` after disposal. An independently owned logger exporter retains warning and error records through asynchronous disposal and is released before `boot()` settles. Its message groups all failed plugins and pending services, marks required entries, and retains original stacks, nested causes, and aggregate members. The CLI prints that message once and saves [full startup diagnostics](../../../apps/cli/reference/README.md#startup-diagnostics) before exiting with code 1; unrelated exceptions retain their normal stack output. In the table, stopping startup means disposing any mounted plugins and exiting nonzero without reporting readiness; continuing keeps successful plugins running. Later configuration HMR does not repeat the required-startup audit and does not roll back the whole update. @@ -135,7 +155,7 @@ This section explains how the outcomes above are realized and points at the code - **Package metadata.** `ctx.pluginPackages.packageOf` locates the owning package without loading code or requiring an exported `package.json`; a subpath selects its package without validating that file. The installed runtime resolution owns selection even for a miss. Low-level embedders that install the service without one retain native lookup. Display metadata uses the separate entry-aware reader described above. - **Two Loader builtins.** `mountRootInclude` registers `cordis:include` and `cordis:group` as Loader builtins: a group row gives one `isolate` realm to a provider and its consumers together, and an agent preset outside this workspace cannot resolve `@deepseek-ai/cordis-plugin-group` by name. Both load through the ambient module pipeline rather than the included tree's own specifier resolution. - **Consumer-owned strictness.** Ordinary Loader groups keep successful siblings. App-boot applies the global required-entry policy after initial settlement; agent presets and dynamic multi-entry compositions own and dispose their separate Loader subtree when they require all-or-nothing setup. App-boot reads failed fibers to report their recorded errors and coalesces duplicate Loader rejection notifications through one process checkpoint. -- **One runtime resolution.** The installation-first and ordered-bundle breadth-first traversal produces the runtime table. Runtime resolution creates no links; runtime resolution entries occupy their package names at `$DSH_HOME/profiles/node_modules`, and every other name sees that directory as an ordinary ancestor. Profile load removes the `.dsh-module-fallback` projections that link-backend releases wrote into a profile; pnpm-installed packages stay. External bare targets selected by package `imports` use the same package order, while Node retains mapping, conditions, and exact target resolution. A complete successor may add package names and update linked-root membership atomically within the existing package-mapping and local-name constraints. +- **One runtime resolution.** The installation-first and ordered-bundle breadth-first traversal produces the runtime table. Runtime resolution creates no links; runtime resolution entries occupy their package names at `$DSH_HOME/profiles/node_modules`, and every other name sees that directory as an ordinary ancestor. Profile load removes the `.dsh-module-fallback` projections that link-backend releases wrote into a profile; pnpm-installed packages stay. External bare targets selected by package `imports` use the same package order, while Node retains mapping, conditions, and exact target resolution. A complete successor may add package names, remove profile-scoped mappings and profile-local names, and update linked-root membership atomically; retained mappings keep their normalized directory, version, and scope. Installation anchors remain fixed; a profile mapping may change its declarer. [Plugin Manager](../plugin-manager/README.md) publishes successors after package operations ([decision](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md)). - **Removing linked interception.** A successor may remove a linked root without restart. Directories outside all remaining roots use native lookup for subsequent requests, which may find a development copy or report a missing package. Existing module references and Node caches stay unchanged. Re-adding the same link name and target is allowed; pointing a previously published name at a different target is rejected even after removal ([generation rules](../../../.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.md#immutable-generations)). - **Application-owned profiles.** Application-owned profiles use the same runtime resolution. Links within the active profile directory, including pnpm store links, are not external roots even when the profile is outside the shared profiles tree. Resolution does not modify their `node_modules`; pnpm owns installed packages. - **Owned Workers.** Worker build banners import `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap` before bundled business code. Each Worker installs the structured-cloned runtime resolution in its own isolate. The bootstrap bundle has no static package imports. Source Worker entries retain their self-contained dependency closure, and third-party Workers receive no injection. @@ -159,7 +179,6 @@ The exports each own one stage of the boot: config resolution and snapshot repla | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | Profile patch backup and recovery bundle activation | | [`src/config-schema/`](src/config-schema/) | Profile schema generation, discovery, native projection, and result types | | [`src/profile-resolution/`](src/profile-resolution/) | Runtime resolver, package-metadata service, and built Worker bootstrap | -| — | No runtime invariant companion is published; one interception owns each runtime resolution. | @@ -177,7 +196,7 @@ Read these pages when the package-level contract is not enough. They move from t - [dsh-home-paths](../../util/home-paths/README.md) — the Harness-home resolver (`resolveDshHome`). - [Configuration source ownership](../../../.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.md) — why a discovered file may not decide bootstrap behavior. - [Profile plugin bundles](../../../.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.md) — the profile and bundle composition design. -- [User-patch HMR tests](../../../.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.md) — ownership of live-patch behavior and native filesystem delivery. +- [User-patch HMR tests](tests/user-patches.spec.ts) — ownership of live-patch behavior and native filesystem delivery. ----- diff --git a/packages/boot/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md index 9cd18a99fa..236c59d522 100644 --- a/packages/boot/app-boot/README.zh.md +++ b/packages/boot/app-boot/README.zh.md @@ -62,7 +62,21 @@ profile 导入插件前,DSH 会检查其 `peerDependencies` 中对 `@deepseek- 启用的 `dsh-hmr` 插件会监视 profile manifest 与两份用户 patch 文件,重新读取按顺序排列的组合包层,并应用[重载失败策略](#startup-and-reload-failures)。[DSH HMR](../hmr/README.zh.md) 将这些重载与[插件管理器](../plugin-manager/README.zh.md)的配置写入串行化;包操作在其队列之外执行。启动器不安装 HMR 或监视器;HMR 被禁用或不存在时,更改需要重启。 -插入条目的插件名可以是绝对文件系统路径、文件 URL 或包标识符。patch 加载会把 `insert` 条目及其嵌套分组中的绝对路径以及相对于 patch 文件的 `./` 或 `../` 路径转换为文件 URL;对已有条目名称的断言及替换用的 `config` 值保持原样。 +插入条目的插件名可以是绝对文件系统路径、文件 URL 或包标识符。patch 加载会把 `insert` 条目、其嵌套分组和插入的 preset 定义中的绝对路径,以及相对于 patch 文件的 `./` 或 `../` 路径转换为文件 URL;对已有条目名称的断言及替换用的 `config` 值保持原样。 + +profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch 字段应用到该条目的字面量 `config.plugins` 列表中。任何 bundle 或用户层都可以提供这些操作。每个操作作用于当前子条目列表;较晚的用户操作优先,而较晚的整个 preset `config` 替换会覆盖此前所有子条目贡献。普通外层 patch 保留 Include 的单次索引查找语义:替换 group 的 `config` 所引入的子条目无法作为外层目标。外层 preset 目标缺失或格式错误、插入造成子条目 id 重复都会使组合失败。内层 `id` 不匹配时只警告并跳过,因此禁用 bundle 不会使用户保留的子条目覆盖失效。 + +```yaml +- preset: preset-standard + insert: + - id: optional-string-editor + name: '@deepseek-ai/dsh-tool-str-replace-editor' +- preset: preset-standard + id: optional-string-editor + disabled: true +``` + +`ProfilePatch` 在原生 patch 字段上增加 `preset`。`applyProfilePatches` 组合这些操作;`compileProfilePatches` 返回普通 Include patch。启动、profile 重载、有效配置 dump、schema 检查及兼容性预检共用此编译器。限定到 preset 的 `insert` 模块路径以声明它的 patch 所在目录为基准,包括嵌套分组;`!!js` 保持不求值,直至对应子条目激活。原生被 Include 引入文件的 patch 列表仍使用普通 Include 语法。 读取的作用域插入行若未声明 ID,会依据声明文件的规范路径及条目位置获得稳定的内部 ID,嵌套组内的行也如此;普通匿名根行保留 Loader 的既有行为。生成的 ID 用于在重载和保留代际之间识别贡献项,是实现细节,不是作者应在 patch 中引用的名称。编译失败会标明声明 patch 文件和操作序号,profile 层合并后也保留这些信息。 挂载 profile 条目前,`dsh` launcher 会从安装依赖图与有序 bundle 依赖图计算一份不可变的 runtime resolution。普通 Node、打包可执行文件与 Electron Host 等所有 profile 启动器都使用 runtime 解析,将 runtime resolution 安装到 Node 的 ESM 与 CommonJS 解析器中,不创建 fallback 链接。 @@ -82,12 +96,18 @@ profile 导入插件前,DSH 会检查其 `peerDependencies` 中对 `@deepseek- ### 读取插件展示元信息 -使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。缺失的 locale 字段回退到该地址下可访问的 `package.json`;格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会将 `package.json.icon` 加载为图片 data URL;图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。 +`resolvePluginResource(specifier, parentURL)` 通过活动的 Node ESM 解析器将插件模块或导出资源解析为本地文件路径,不执行其代码。解析器不可用或资源无法解析为本地文件时会抛出错误。 + +`realModuleFile(path)` 在 Node 和打包可执行程序中规范化已有模块资源的路径。它跟随物理文件符号链接,并通过规范化后的所在目录保留归档资源。资源不存在或无法访问时抛出错误。 + +使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。包根标识缺失的 locale 字段回退到可访问的 `package.json`;子路径标识从不读取 `package.json`。格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会加载图片 data URL:包根使用清单 `icon`,省略该字段时使用 `<包名>/icon`;子路径使用 `<标识>/icon`。图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。 + +`ON_DEMAND_BUNDLES` 列出仅按需安装的官方公开包。`OFFICIAL_ON_DEMAND_CATALOG` 内嵌这些包拥有的本地化元信息和图标,支持离线发现;`pnpm gen-official-bundle-catalog` 显式更新生成内容,`verify-official-bundle-catalog` 检查资源完整性与新鲜度;构建只读取已提交的目录,不导入提供者代码。目录不保存安装目标;[插件管理器](../plugin-manager/README.zh.md#use-this-package)根据当前版本和安装位置确定目标,并使用普通组合包安装器。[产品用途检查](../../../scripts/verify-product-use.ts)将随附和按需选择与 Web 组合,但不执行提供者。 ### 启动与重载失败 -profile 重载返回未变化的已有故障诊断,不让无关修改因此失败。新增未激活条目、配置或 fiber 变化、诊断变化都会使重载失败;被移除的 fiber 仍须完成释放。显式启用的目标必须成功激活,即使它的故障早于本次操作。成功重载在生命周期结束及诊断检查通过后发出 `app-boot/config-reload`,包括未启用 HMR 时的程序化更新。事件不携带 diff 或解析后的配置。 成功重载在生命周期结束及诊断检查通过后返回;仅 volatile 的条目变化由 Loader 在更新过程中提交。 +profile 重载返回未变化的已有故障诊断,不让无关修改因此失败。新增未激活条目、配置或 fiber 变化、诊断变化都会使重载失败;被移除的 fiber 仍须完成释放。消失或被字面量禁用的活跃根条目先释放资源,再激活替代条目,因此单实例提供方可以先排空进行中的工作,再让后继者注册。每次重组时,编译与释放检查使用同一份已解析的根条目。显式启用的目标必须成功激活,即使它的故障早于本次操作。成功重载在生命周期结束及诊断检查通过后发出 `app-boot/config-reload`,包括未启用 HMR 时的程序化更新。事件不携带 diff 或解析后的配置。 成功重载在生命周期结束及诊断检查通过后返回;仅 volatile 的条目变化由 Loader 在更新过程中提交。 Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如果已启用的 required 条目无法激活,`boot()` 会在释放资源后以 `StartupError` 拒绝。独立管理生命周期的 logger exporter 会保留异步资源释放期间的警告和错误记录,并在 `boot()` 结算前释放。其消息分组列出所有失败插件和等待的服务,标记 required 条目,并保留原始堆栈、嵌套原因和聚合错误成员。CLI 仅输出该消息一次,并在保存[完整启动诊断](../../../apps/cli/reference/README.zh.md#startup-diagnostics)后以退出码 1 结束;其他异常保留正常堆栈输出。表中的“终止启动”指释放已挂载插件并以非零码退出,不报告就绪;“继续”指保留成功运行的插件。后续配置 HMR 不会再次执行 required 启动审计,也不会回滚整个更新。 @@ -135,7 +155,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如 - **包元数据。** `ctx.pluginPackages.packageOf` 定位所属包,不加载代码,也不要求导出 `package.json`;子路径选择其所属包,不校验该文件。安装 runtime resolution 后,即使查询未命中也以其选包规则为准。仅安装服务而不提供 runtime resolution 的底层嵌入方保留原生查询。展示元数据使用上文另述的入口感知读取器。 - **两个 Loader builtin。** `mountRootInclude` 把 `cordis:include` 与 `cordis:group` 注册为 Loader builtin:group 行能把一个提供方与它的消费方放进同一个 `isolate` realm,而位于本工作区之外的 agent preset 无法按名称解析 `@deepseek-ai/cordis-plugin-group`。两者都通过宿主的模块管线加载,而非被包含树自身的说明符解析。 - **由 consumer 持有严格语义。** 普通 Loader group 保留成功 sibling。App-boot 在首次结算后应用全局 required-entry policy;agent preset 与动态多 entry 组合在需要 all-or-nothing setup 时,持有并拆卸各自的独立 Loader 子树。App-boot 读取 failed fiber 来报告已记录的错误,并在一个进程检查点内合并 Loader 重复的 rejection 通知。 -- **唯一 runtime resolution。** 安装优先、有序 bundle 逐根 breadth-first 遍历生成运行时表。runtime 解析不创建链接;runtime resolution 条目占据 `$DSH_HOME/profiles/node_modules` 上各自的包名位置,其余包名把该目录当作普通祖先。profile 加载时删除 Link 后端发布版写进 profile 的 `.dsh-module-fallback` 投影;pnpm 安装的包保留。package `imports` 选中的外部 bare target 使用相同的选包顺序,映射、conditions 和精确 target 解析仍由 Node 负责。完整后继 runtime resolution 可以在既有包映射和本地包名约束内原子增加 package name、更新 linked root 集合。 +- **唯一 runtime resolution。** 安装优先、有序 bundle 逐根 breadth-first 遍历生成运行时表。runtime 解析不创建链接;runtime resolution 条目占据 `$DSH_HOME/profiles/node_modules` 上各自的包名位置,其余包名把该目录当作普通祖先。profile 加载时删除 Link 后端发布版写进 profile 的 `.dsh-module-fallback` 投影;pnpm 安装的包保留。package `imports` 选中的外部 bare target 使用相同的选包顺序,映射、conditions 和精确 target 解析仍由 Node 负责。完整后继 runtime resolution 可以原子地增加 package name、移除 profile 范围的映射和 profile 本地包名、更新 linked root 集合;保留映射的规范化目录、版本和作用域不变。安装范围的锚点不变;profile 映射可以更换声明来源。[插件管理器](../plugin-manager/README.zh.md)在包操作之后发布后继代([决策](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md))。 - **移除链接拦截。** 后继 generation 可以移除 linked root,无需重启。目录不再被任何剩余 root 覆盖时,后续请求使用原生查询,可能找到开发副本,也可能报告缺包。已有模块引用和 Node 缓存保持不变。同名、同目标可以重新加入;曾发布的名称改指向不同目标时,即使中间移除过也会被拒绝([generation 规则](../../../.agents/notes/implemented/architecture/2026-09-09-profile-resolution-generations.zh.md#immutable-generations))。 - **应用自有 profile。** 应用自有 profile 使用相同的 runtime resolution。目标位于当前 profile 目录内的链接(包括 pnpm store 链接)不算外部 root,即使 profile 位于共享 profiles 树外。解析过程不修改其 `node_modules`;已安装包由 pnpm 管理。 - **自有 Worker。** Worker 构建 banner 会在业务 bundle 前导入 `@deepseek-ai/dsh-app-boot/worker/profile-resolution-bootstrap`。每个 Worker 在自己的 isolate 中安装结构化克隆的 runtime resolution。bootstrap bundle 不静态导入任何包。源码 Worker 入口保留自包含依赖,第三方 Worker 不接受注入。 @@ -159,7 +179,6 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如 | [`src/profile-sanitize.ts`](src/profile-sanitize.ts) | profile patch 备份与恢复 bundle 启用状态 | | [`src/config-schema/`](src/config-schema/) | Profile schema 生成、发现、原生投影与结果类型 | | [`src/profile-resolution/`](src/profile-resolution/) | 运行时 resolver、package metadata 服务与构建后 Worker bootstrap | -| — | 不发布运行时不变式伴生入口;每个 runtime resolution 只有一个拦截所有。 | @@ -177,7 +196,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如 - [dsh-home-paths](../../util/home-paths/README.zh.md)——harness home 解析器(`resolveDshHome`)。 - [配置来源归属](../../../.agents/notes/implemented/architecture/2026-08-04-configuration-source-ownership.zh.md)——被发现的文件为何不得决定 bootstrap 行为。 - [Profile 插件组合包](../../../.agents/notes/implemented/architecture/2026-08-05-profile-plugin-bundles.zh.md)——profile 与组合包组合设计。 -- [用户 patch HMR 测试](../../../.agents/notes/implemented/testing/2026-09-09-user-patch-hmr-test-delivery.zh.md)——实时 patch 行为与原生文件系统投递的验证归属。 +- [用户 patch HMR 测试](tests/user-patches.spec.ts)——实时 patch 行为与原生文件系统投递的验证归属。 ----- diff --git a/packages/boot/app-boot/package.json b/packages/boot/app-boot/package.json index 08f105f6fa..af2930239b 100644 --- a/packages/boot/app-boot/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -36,7 +36,7 @@ "@deepseek-ai/dsh-package-manifest": "workspace:*", "@eslint-community/regexpp": "^4.12.2", "ajv": "^8.20.0", - "js-yaml": "^4.2.0", + "js-yaml": "^4.3.2", "node-addon-require-builtin": "^0.1.6", "resolve.exports": "^2.0.3", "semver": "^7.8.5" diff --git a/packages/boot/app-boot/src/compatibility-preflight.ts b/packages/boot/app-boot/src/compatibility-preflight.ts index 5b4589b117..1e33abe184 100644 --- a/packages/boot/app-boot/src/compatibility-preflight.ts +++ b/packages/boot/app-boot/src/compatibility-preflight.ts @@ -7,6 +7,7 @@ import type { Context } from '@deepseek-ai/cordis' import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' import { applyEntryPatches, entryListSchema, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' import { load } from 'js-yaml' +import { applyProfilePatches, type ProfilePatch } from './profile-patches.ts' import { resolvePluginResource } from './package-meta.ts' import { barePackageName } from './profile-resolution/resolver.ts' import type {} from './profile-resolution/service.ts' @@ -178,10 +179,10 @@ function preflight( * @returns One prepared insertion patch for profiles, or the original patches for non-profile callers. */ export function prepareProfilePatches( - ctx: Context, patches: PatchOptions[], parentURL: string, binName = 'dsh', + ctx: Context, patches: ProfilePatch[], parentURL: string, binName = 'dsh', ): PatchOptions[] { if (ctx.get('profileContext') === undefined) return patches - const entries = applyEntryPatches([], patches, patchWarning(ctx)) + const entries = applyProfilePatches([], patches, patchWarning(ctx)) const rows = prepareProfileEntries(ctx, entries, parentURL, binName) return rows.length === 0 ? [] : [{ insert: rows }] } diff --git a/packages/boot/app-boot/src/config-schema/document.ts b/packages/boot/app-boot/src/config-schema/document.ts index 70428c6671..768fb3c152 100644 --- a/packages/boot/app-boot/src/config-schema/document.ts +++ b/packages/boot/app-boot/src/config-schema/document.ts @@ -143,13 +143,23 @@ export async function buildConfigSchemaDocument( required: ['id'], properties: { id: { const: id } }, allOf: [ { not: { required: ['insert'] } }, + { not: { required: ['preset'] } }, { anyOf: [{ not: { required: ['name'] } }, { properties: { name: { enum: ['', target.name] } } }] }, ], }, then: { properties: { config: { $ref: configRef } } }, }) } - definitions.patch = { ...patchStructure(), allOf: [ref('entryMetadata'), ...patchRules] } + definitions.patch = { + ...patchStructure(), allOf: [ref('entryMetadata'), ...patchRules], + properties: { + insert: ref('entryList'), + preset: { + type: 'string', pattern: '\\S', + description: 'Outer preset row id. The remaining patch fields apply to its current config.plugins list; a missing or unaddressable preset fails composition.', + }, + }, + } const positions = new Map(entries.map((entry, index) => [entry.path, index])) diagnostics.sort((left, right) => (positions.get(left.path ?? '') ?? -1) - (positions.get(right.path ?? '') ?? -1)) return { diff --git a/packages/boot/app-boot/src/config-schema/index.ts b/packages/boot/app-boot/src/config-schema/index.ts index 44bb553249..e515c961f0 100644 --- a/packages/boot/app-boot/src/config-schema/index.ts +++ b/packages/boot/app-boot/src/config-schema/index.ts @@ -1,6 +1,6 @@ /** Profile schema generation: composition diagnostics, runtime resolution, and boot-free discovery. */ -import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import type { ProfilePatch } from '../profile-patches.ts' import { composeEntries, createRuntimeResolution, type Profile } from '../profile.ts' import { collectConfigSchemas } from './collect.ts' import type { ConfigSchemaDiagnostic, ConfigSchemaDump } from './types.ts' @@ -20,7 +20,7 @@ export type { ConfigSchemaDump, NativeConfigSchema } from './types.ts' */ export async function generateConfigSchema( profile: Profile, - layers: readonly PatchOptions[][], + layers: readonly ProfilePatch[][], installAnchor: string, ): Promise { const diagnostics: ConfigSchemaDiagnostic[] = [] diff --git a/packages/boot/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts index 188cb33460..38abd1f256 100644 --- a/packages/boot/app-boot/src/index.ts +++ b/packages/boot/app-boot/src/index.ts @@ -13,7 +13,7 @@ import { basename, dirname, isAbsolute, resolve } from 'node:path' import * as yaml from 'js-yaml' import { Context, type FiberState } from '@deepseek-ai/cordis' import Loader, { type Entry, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader' -import Include, { applyEntryPatches, entryListSchema, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import Include, { entryListSchema } from '@deepseek-ai/cordis-plugin-include' import Group from '@deepseek-ai/cordis-plugin-group' import { dshHomePath, resolveDshHome } from '@deepseek-ai/dsh-home-paths' import { createLaunchEnvironmentSnapshot, type LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment' @@ -25,8 +25,12 @@ export { setProfileVersionExemption, type ProfileCompatibility, } from './profile-compatibility.ts' import { prepareProfilePatches } from './compatibility-preflight.ts' +import { applyProfilePatches, compileProfilePatches, profilePatchPreset, prepareLoadedProfilePatches, type ProfilePatch } from './profile-patches.ts' +export { applyProfilePatches, compileProfilePatches, profilePatchPreset, type ProfilePatch, type ProfilePatchWarning } from './profile-patches.ts' export { prepareProfileEntries, prepareProfilePatches } from './compatibility-preflight.ts' -export { readPluginMeta } from './package-meta.ts' +export { readPluginMeta, resolvePluginResource } from './package-meta.ts' +export { ON_DEMAND_BUNDLES, OFFICIAL_ON_DEMAND_CATALOG, type OfficialBundleCatalogEntry } from './official-bundles.ts' +export { realModuleFile } from './profile-resolution/legacy-links.ts' export { generateConfigSchema, type ConfigSchemaDump, type NativeConfigSchema } from './config-schema/index.ts' export { createConfigProjector, LOADER_EXPRESSION_SCHEMA, type ConfigProjection } from './config-schema/projector.ts' export { isNativeConfigSchema } from './config-schema/native.ts' @@ -56,6 +60,7 @@ declare module '@deepseek-ai/cordis' { export { composeEntries, createRuntimeResolution, + ProfileRuntimeResolution, DEFAULT_PROFILE_BUNDLES, OPTIONAL_BUNDLES, bundlePatchFiles, @@ -271,7 +276,7 @@ const userPatchesSchema = entryListSchema * @returns Diagnostics for unchanged pre-existing inactive entries; new or changed failures reject. */ export async function reconcileProfilePatches( - ctx: Context, patches: PatchOptions[], binName: string, requiredIds: readonly string[] = [], + ctx: Context, patches: ProfilePatch[], binName: string, requiredIds: readonly string[] = [], ): Promise { const entry = bootstrapIncludes.get(ctx) if (entry === undefined) throw new Error(`${binName}: profile reload requires the root Include entry`) @@ -285,7 +290,9 @@ export async function reconcileProfilePatches( const { patches: _previous, ...includeConfig } = entry.options.config as Include.Config // The recomposition judges the rows the launch judged, resolved from the file this Include read. const parentURL = new URL('.', new URL(includeConfig.path, entry.parent.tree.ctx.baseUrl)).href - const prepared = prepareProfilePatches(ctx, patches, parentURL, binName) + const initial = readRootEntries(includeConfig.path, parentURL, binName) + const prepared = prepareRootPatches(ctx, patches, includeConfig.path, parentURL, binName, initial) + await drainRetiringEntries(ctx, entry, initial, prepared) await entry.update({ config: { ...includeConfig, patches: prepared } }) const results = await Promise.allSettled(previousFibers.map(({ fiber }) => fiber.await())) await ctx.loader.await() @@ -301,10 +308,35 @@ export async function reconcileProfilePatches( return failures.map(inactiveDiagnostic) } +/** Disappearing and literal-disabled rows release singleton services before their replacements activate. */ +async function drainRetiringEntries( + ctx: Context, root: Entry, initial: EntryOptions[], patches: readonly ProfilePatch[], +): Promise { + const rows = applyProfilePatches(initial, patches) + const next = new Map() + const visit = (rows: EntryOptions[], outerDisabled = false): void => { + for (const row of rows) { + const disabled = outerDisabled || row.disabled === true + next.set(row.id, !row.group && disabled) + if (row.group && Array.isArray(row.config)) visit(row.config as EntryOptions[], disabled) + } + } + visit(rows) + const retiring = [...ctx.loader.entries()].flatMap((entry) => { + const fiber = entry.fiber + return entry.parent.tree === root.subtree && fiber?.state === FIBER_ACTIVE + && (!next.has(entry.options.id) || next.get(entry.options.id)) ? [fiber] : [] + }) + await Promise.all(retiring.map(async (fiber) => { + await fiber.dispose() + await fiber.await() + })) +} + /** - * Load an optional patch-list file: a top-level YAML array of loader patch - * entries (`@deepseek-ai/cordis-plugin-include`'s `PatchOptions`): id-targeted config - * overrides and `insert` lists, with `!!js` expressions allowed. A missing + * Load an optional patch-list file: a top-level YAML array of profile patch + * entries: id-targeted overrides, `insert` lists, and preset-scoped operations, + * with `!!js` expressions allowed. A missing * file means "no layer"; an unreadable, unparsable, or non-array file throws — * a present patch file that cannot apply is a misconfiguration and must fail * loud at boot, never be silently skipped. @@ -312,7 +344,7 @@ export async function reconcileProfilePatches( * @param file - absolute path of the patch file. * @returns the parsed patches, or `undefined` when the file does not exist. */ -export function loadOptionalPatches(binName: string, file: string): PatchOptions[] | undefined { +export function loadOptionalPatches(binName: string, file: string): ProfilePatch[] | undefined { let content: string try { content = readFileSync(file, 'utf8') @@ -332,7 +364,7 @@ export function loadOptionalPatches(binName: string, file: string): PatchOptions * @param file - absolute path of the overlay file. * @returns the parsed patch list. */ -export function loadOverlayPatches(binName: string, file: string): PatchOptions[] { +export function loadOverlayPatches(binName: string, file: string): ProfilePatch[] { let content: string try { content = readFileSync(file, 'utf8') @@ -343,24 +375,30 @@ export function loadOverlayPatches(binName: string, file: string): PatchOptions[ } /** Convert inserted filesystem paths to file URLs, anchoring relative paths beside the patch; keep assertion names literal. */ -function anchorInsertedPluginNames(patches: PatchOptions[], file: string): PatchOptions[] { +function anchorInsertedPluginNames(patches: ProfilePatch[], file: string): ProfilePatch[] { const base = dirname(resolve(file)) const visit = (entry: EntryOptions): void => { if (typeof entry.name === 'string' && (isAbsolute(entry.name) || entry.name.startsWith('./') || entry.name.startsWith('../'))) { entry.name = pathToFileURL(resolve(base, entry.name)).href } if (entry.group && Array.isArray(entry.config)) entry.config.forEach(visit) + const config = entry.config as { plugins?: unknown } | undefined + if (entry.name === '@deepseek-ai/dsh-agent-preset' && Array.isArray(config?.plugins)) { + (config.plugins as EntryOptions[]).forEach(visit) + } + } + for (const patch of patches) { + patch.insert?.forEach(visit) } - for (const patch of patches) patch.insert?.forEach(visit) return patches } /** * Parse one loader patch list: a top-level YAML array of - * `@deepseek-ai/cordis-plugin-include` `PatchOptions` (id-targeted config overrides and - * `insert` lists, `!!js` expressions allowed). Every invalid field or value throws, - * because a patch file that cannot be applied at all is a misconfiguration; a - * single patch whose target row is absent stays a per-entry Loader warning, so - * one overlay shared across surfaces does not have to match every tree. + * profile patches (id-targeted overrides, `insert` lists, and preset-scoped + * operations, with `!!js` expressions allowed). Invalid lists and malformed + * preset targets throw because they cannot be applied. An ordinary or scoped + * child patch whose target is absent stays a per-entry Loader warning; a + * missing outer preset target fails composition. * @param binName - the diagnostic prefix on the thrown error. * @param file - the source path, quoted in errors. * @param content - the file's text. @@ -369,7 +407,7 @@ function anchorInsertedPluginNames(patches: PatchOptions[], file: string): Patch */ function parsePatchList( binName: string, file: string, content: string, label: string, -): PatchOptions[] { +): ProfilePatch[] { let parsed: unknown try { parsed = yaml.load(content, { schema: userPatchesSchema }) @@ -384,7 +422,7 @@ function parsePatchList( throw new Error(`${binName}: ${label} entry ${index + 1} in ${file} must be a mapping (a loader patch entry)`) } }) - return anchorInsertedPluginNames(parsed as PatchOptions[], file) + return anchorInsertedPluginNames(prepareLoadedProfilePatches(parsed as ProfilePatch[], file, binName), file) } /** One overlay patch list with the source label printed in dump comments. */ @@ -392,13 +430,13 @@ export interface ConfigDumpLayer { /** Source name shown in dump comments (a file basename or path). */ label: string /** The layer's patches, from {@link loadOverlayPatches} / {@link loadOptionalPatches}. */ - patches: PatchOptions[] + patches: ProfilePatch[] } /** * Compose the configured entry list: parse the base config file with the - * include's entry-list dialect, apply every layer's patches as ONE flattened - * list through the include's own patch algorithm (`applyEntryPatches`) — the + * include's entry-list dialect, compile preset operations, then apply the + * flattened native patch list through Include's `applyEntryPatches` — the * same call `boot()` makes, so even patch-visibility corner cases (a later * layer targeting a group child a plain config replacement introduced, which * the single-pass id index never sees) compose identically — then render the @@ -418,8 +456,8 @@ export interface ConfigDumpLayer { * A patch that matches no row is reported through `warn` with its layer * label, mirroring the Loader's boot-time warning. Earlier layers' patches * see an identical preceding state in every snapshot that includes them, so - * each snapshot's warning list extends the previous one and the new tail - * belongs to the added layer. + * additional warnings belong to the added layer. Warning multiplicity is + * preserved when scoped and ordinary diagnostics have different ordering. * @param binName - the diagnostic prefix on read/parse errors. * @param absoluteConfigPath - the base config file `boot()` would include. * @param layers - overlay layers in application order (later wins). @@ -452,16 +490,14 @@ export function renderConfigDump( // YAML parsing yields untyped rows; the include validates each entry // at mount, and the dump prints whatever the file holds, so `EntryOptions` // here is structural trust in the same file `boot()` would include. - const base = parsed as Parameters[0] + const base = parsed as EntryOptions[] // snapshot_k = ONE application of layers 1..k flattened, using the exact // arguments boot passes for that prefix. snapshot_N is the mounted composition. - // The patches are cloned per call: applyEntryPatches detaches the entry - // list but pushes `insert` rows by reference from the patch list, so - // sharing patch objects across snapshot calls would leak a later - // snapshot's mutations into an earlier one's result. - const snapshot = (count: number, warnings: string[]): ReturnType => { - const flattened = structuredClone(layers.slice(0, count).flatMap(layer => layer.patches)) - return applyEntryPatches(base, flattened, (message: string, ...args: unknown[]) => { + // The compiler clones patch objects for each snapshot: native insertions retain + // references, so sharing them would let later snapshots mutate earlier results. + const snapshot = (count: number, warnings: string[]): EntryOptions[] => { + const flattened = layers.slice(0, count).flatMap(layer => layer.patches) + return applyProfilePatches(base, flattened, (message: string, ...args: unknown[]) => { // The include logs through cordis's printf-style logger (`%C` = code); a // dump has no logger, so substitute inline for a plain line. let index = 0 @@ -478,8 +514,11 @@ export function renderConfigDump( if (layer === undefined) continue const warnings: string[] = [] composed = snapshot(count, warnings) - for (const line of warnings.slice(previousWarnings.length)) { - warn(`${binName}: [${layer.label}] ${line}`) + const remaining = [...previousWarnings] + for (const line of warnings) { + const previousIndex = remaining.indexOf(line) + if (previousIndex >= 0) remaining.splice(previousIndex, 1) + else warn(`${binName}: [${layer.label}] ${line}`) } const before = previous.map(entry => JSON.stringify(entry)) for (let index = 0; index < composed.length; index += 1) { @@ -523,6 +562,22 @@ function groupedDump( return lines.join('\n') + '\n' } +/** Compile profile operations before passing an ordinary patch list to the root Include. */ +function readRootEntries(configPath: string, parentURL: string, binName: string): EntryOptions[] { + const parsed: unknown = yaml.load(readFileSync(new URL(configPath, parentURL), 'utf8'), { schema: entryListSchema }) + if (!Array.isArray(parsed)) throw new Error(`${binName}: config ${configPath} must be a top-level array of entries`) + return parsed as EntryOptions[] +} + +function prepareRootPatches( + ctx: Context, patches: ProfilePatch[], configPath: string, parentURL: string, binName: string, initial?: EntryOptions[], +): ProfilePatch[] { + if (ctx.get('profileContext') !== undefined) return prepareProfilePatches(ctx, patches, parentURL, binName) + if (!patches.some(patch => profilePatchPreset(patch) !== undefined)) return patches + return compileProfilePatches(initial ?? readRootEntries(configPath, parentURL, binName), patches, + (message, ...args) => { ctx.logger.warn(message, ...args) }) +} + /** * Mount and remember the exact root Include entry used by app boot and user patch-layer HMR. * @param ctx - context carrying an initialized Loader service. @@ -538,7 +593,7 @@ function groupedDump( export async function mountRootInclude( ctx: Context, absoluteConfigPath: string, - patches: readonly PatchOptions[] = [], + patches: readonly ProfilePatch[] = [], bareModuleBaseUrl?: string, binName = 'dsh', ): Promise { @@ -566,7 +621,7 @@ export async function mountRootInclude( // diagnostics unstable across runs (and snapshot fixtures). // The launcher's own copy is prepared here: compatibility decisions must be made before the root // Include imports anything, and they change no profile patch layer, manifest, or bundle list. - const prepared = prepareProfilePatches(ctx, [...patches], pathToFileURL(dirname(absoluteConfigPath)).href + '/', binName) + const prepared = prepareRootPatches(ctx, [...patches], pathToFileURL(absoluteConfigPath).href, pathToFileURL(dirname(absoluteConfigPath)).href + '/', binName) const includeConfig: Include.Config = { path: pathToFileURL(absoluteConfigPath).href, ...prepared.length > 0 ? { patches: prepared } : {}, @@ -972,7 +1027,7 @@ export async function auditStartupEntries( export async function boot( binName: string, absoluteConfigPath: string, - patches?: PatchOptions[], + patches?: ProfilePatch[], prepare?: (ctx: Context) => Promise | void, bareModuleBaseUrl?: string, ): Promise { diff --git a/packages/boot/app-boot/src/official-bundle-packages.ts b/packages/boot/app-boot/src/official-bundle-packages.ts new file mode 100644 index 0000000000..b77f9fcdd9 --- /dev/null +++ b/packages/boot/app-boot/src/official-bundle-packages.ts @@ -0,0 +1,7 @@ +/** Package admission shared by offline catalog generation and release ordering. */ + +/** Public DSH release members available through the ordinary on-demand bundle installer. */ +export const ON_DEMAND_BUNDLES: readonly string[] = [ + '@deepseek-ai/dsh-subagent-claude-code', + '@deepseek-ai/dsh-subagent-codex', +] diff --git a/packages/boot/app-boot/src/official-bundles.generated.ts b/packages/boot/app-boot/src/official-bundles.generated.ts new file mode 100644 index 0000000000..1fffe43c5e --- /dev/null +++ b/packages/boot/app-boot/src/official-bundles.generated.ts @@ -0,0 +1,23 @@ +/** Generated offline Official bundle metadata. Run pnpm gen-official-bundle-catalog; do not edit. */ + +import type { OfficialBundleCatalogEntry } from './official-bundles.ts' + +/** Official on-demand entries, independent of installed packages and registry availability. */ +export const OFFICIAL_ON_DEMAND_CATALOG: readonly OfficialBundleCatalogEntry[] = [ + { + packageName: '@deepseek-ai/dsh-subagent-claude-code', + meta: { + title: { 'en': 'Claude Code subagent', 'zh': 'Claude Code \u5B50\u667A\u80FD\u4F53' }, + description: { 'en': 'Delegate one task to Claude Code using its native authentication and settings.', 'zh': '\u4F7F\u7528 Claude Code \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' }, + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNC45ODkzIiB5PSIxNS4xODc3IiB3aWR0aD0iMTIuMjM2NSIgaGVpZ2h0PSIxMi4yMzY1IiByeD0iMiIgZmlsbD0idXJsKCNkc2hfcGx1Z2luX2FydF9SMGEpIiBmaWxsLW9wYWNpdHk9IjAuOCI+PC9yZWN0PjxyZWN0IHg9IjguODcxMDkiIHk9IjkuMDY5NyIgd2lkdGg9IjEyLjIzNjUiIGhlaWdodD0iMTIuMjM2NSIgcng9IjIiIGZpbGw9InVybCgjZHNoX3BsdWdpbl9hcnRfUjBiKSIgZmlsbC1vcGFjaXR5PSIwLjgiPjwvcmVjdD48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYSIgeDE9IjIxLjEwNzUiIHkxPSIxNS4xODc3IiB4Mj0iMjEuMTA3NSIgeTI9IjI3LjQyNDMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjNDVFN0E0Ij48L3N0b3A+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDU5MDlEIj48L3N0b3A+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYiIgeDE9IjE0Ljk4OTQiIHkxPSI5LjA2OTciIHgyPSIxNC45ODk0IiB5Mj0iMjEuMzA2MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiM2OUI5RkYiPjwvc3RvcD48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMjRERTIiPjwvc3RvcD48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4K', + }, + }, + { + packageName: '@deepseek-ai/dsh-subagent-codex', + meta: { + title: { 'en': 'Codex subagent', 'zh': 'Codex \u5B50\u667A\u80FD\u4F53' }, + description: { 'en': 'Delegate one task to Codex using its native authentication and settings.', 'zh': '\u4F7F\u7528 Codex \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' }, + icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNC45ODkzIiB5PSIxNS4xODc3IiB3aWR0aD0iMTIuMjM2NSIgaGVpZ2h0PSIxMi4yMzY1IiByeD0iMiIgZmlsbD0idXJsKCNkc2hfcGx1Z2luX2FydF9SMGEpIiBmaWxsLW9wYWNpdHk9IjAuOCI+PC9yZWN0PjxyZWN0IHg9IjguODcxMDkiIHk9IjkuMDY5NyIgd2lkdGg9IjEyLjIzNjUiIGhlaWdodD0iMTIuMjM2NSIgcng9IjIiIGZpbGw9InVybCgjZHNoX3BsdWdpbl9hcnRfUjBiKSIgZmlsbC1vcGFjaXR5PSIwLjgiPjwvcmVjdD48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYSIgeDE9IjIxLjEwNzUiIHkxPSIxNS4xODc3IiB4Mj0iMjEuMTA3NSIgeTI9IjI3LjQyNDMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjNDVFN0E0Ij48L3N0b3A+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDU5MDlEIj48L3N0b3A+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYiIgeDE9IjE0Ljk4OTQiIHkxPSI5LjA2OTciIHgyPSIxNC45ODk0IiB5Mj0iMjEuMzA2MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiM2OUI5RkYiPjwvc3RvcD48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMjRERTIiPjwvc3RvcD48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4K', + }, + }, +] diff --git a/packages/boot/app-boot/src/official-bundles.ts b/packages/boot/app-boot/src/official-bundles.ts new file mode 100644 index 0000000000..fdcb8fda52 --- /dev/null +++ b/packages/boot/app-boot/src/official-bundles.ts @@ -0,0 +1,15 @@ +/** Official bundles offered without installing their provider runtimes in the DSH distribution. */ + +import type { PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest' + +export { ON_DEMAND_BUNDLES } from './official-bundle-packages.ts' + +/** Offline discovery metadata; installation state and the requested version belong to the running host. */ +export interface OfficialBundleCatalogEntry { + /** Exact npm package name accepted by the existing bundle installer. */ + readonly packageName: string + /** Package-owned localized text and a self-contained icon, embedded without provider code. */ + readonly meta: Required> +} + +export { OFFICIAL_ON_DEMAND_CATALOG } from './official-bundles.generated.ts' diff --git a/packages/boot/app-boot/src/package-meta.ts b/packages/boot/app-boot/src/package-meta.ts index 131082ce8a..b54a930724 100644 --- a/packages/boot/app-boot/src/package-meta.ts +++ b/packages/boot/app-boot/src/package-meta.ts @@ -1,11 +1,12 @@ /** Read plugin display text and icons through exported resources without evaluating plugin code. */ -import { readdirSync, readFileSync, realpathSync, statSync } from 'node:fs' -import { dirname, extname, isAbsolute, relative, resolve, sep, win32 } from 'node:path' +import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs' +import { dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from 'node:path' import { fileURLToPath } from 'node:url' import { ModuleLoader } from '@deepseek-ai/cordis-plugin-loader' import type { LocalizedText, PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest' import { barePackageName } from './profile-resolution/resolver.ts' +import { realModuleFile } from './profile-resolution/legacy-links.ts' const LANGUAGE_ID = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/u @@ -18,31 +19,53 @@ const ICON_MEDIA_TYPES = new Map([ ['.jpeg', 'image/jpeg'], ['.webp', 'image/webp'], ]) -function iconOf(value: unknown, manifestPath: string): string | undefined { - const icon = textOf(value, `${manifestPath}: icon`) - if (icon === undefined) return undefined - if (isAbsolute(icon) || win32.isAbsolute(icon) || /^[A-Za-z][A-Za-z\d+.-]*:/u.test(icon)) { - throw new Error(`${manifestPath}: icon must be a relative file path`) - } - const mediaType = ICON_MEDIA_TYPES.get(extname(icon).toLowerCase()) - if (mediaType === undefined) throw new Error(`${manifestPath}: icon must be SVG, PNG, JPEG, or WebP`) - const directory = realpathSync(dirname(manifestPath)) - const file = realpathSync(resolve(directory, icon)) - const local = relative(directory, file) - if (local === '..' || local.startsWith(`..${sep}`) || isAbsolute(local)) { - throw new Error(`${manifestPath}: icon must remain inside its manifest directory`) +function iconOf( + specifier: string, packageName: string, parentURL: string, manifestPath: string | undefined, value: unknown, +): string | undefined { + const icon = manifestPath === undefined ? undefined : textOf(value, `${manifestPath}: icon`) + let target: string + // Diagnostics name the file the author edits: the declaring manifest or the exported image. + let label: string + if (manifestPath !== undefined && icon !== undefined) { + if (isAbsolute(icon) || win32.isAbsolute(icon) || /^[A-Za-z][A-Za-z\d+.-]*:/u.test(icon)) { + throw new Error(`${manifestPath}: icon must be a relative file path`) + } + label = manifestPath + const root = realpathSync(dirname(manifestPath)) + target = resolve(root, icon) + const local = relative(root, realModuleFile(target)) + if (local === '..' || local.startsWith(`..${sep}`) || isAbsolute(local)) { + throw new Error(`${label}: icon must remain inside its manifest directory`) + } + } else { + const exported = optionalResourcePath(`${specifier}/icon`, parentURL) + if (exported === undefined) return undefined + target = label = exported + assertPackageOwned(realModuleFile(target), packageName) } + const mediaType = ICON_MEDIA_TYPES.get(extname(target).toLowerCase()) + if (mediaType === undefined) throw new Error(`${label}: icon must be SVG, PNG, JPEG, or WebP`) + const file = realModuleFile(target) const stat = statSync(file) - if (!stat.isFile()) throw new Error(`${manifestPath}: icon must be a regular file`) - if (stat.size > MAX_ICON_BYTES) throw new Error(`${manifestPath}: icon exceeds 256 KiB`) + if (!stat.isFile()) throw new Error(`${label}: icon must be a regular file`) + if (stat.size > MAX_ICON_BYTES) throw new Error(`${label}: icon exceeds 256 KiB`) const bytes = readFileSync(file) - if (bytes.length > MAX_ICON_BYTES) throw new Error(`${manifestPath}: icon exceeds 256 KiB`) + if (bytes.length > MAX_ICON_BYTES) throw new Error(`${label}: icon exceeds 256 KiB`) return `data:${mediaType};base64,${bytes.toString('base64')}` } +/** Require an ancestor of the real file whose package.json names the owning package. */ +function assertPackageOwned(file: string, name: string): void { + for (let directory = dirname(file); ; directory = dirname(directory)) { + const manifest = join(directory, 'package.json') + if (existsSync(manifest) && readObject(manifest).name === name) return + if (dirname(directory) === directory) throw new Error(`${file}: icon must remain inside its package directory`) + } +} + /** * Resolve a plugin resource through the active Node ESM resolver without evaluating it. - * @param specifier - complete resource module specifier, including its locale filename. + * @param specifier - plugin module or exported resource specifier. * @param parentURL - owning module-resolution base. * @returns the local filesystem path selected by Node and the active profile. * @throws when the resolver is unavailable or the resource cannot resolve to a local file. @@ -133,24 +156,29 @@ function localizedText( } /** - * Read localized display text and the icon declared in a plugin's exported package.json. - * Icons are manifest-relative SVG, PNG, JPEG, or WebP files of at most 256 KiB, - * contained in the manifest directory after realpath resolution. Icon failures retain display text. + * Read plugin locale JSON display text and icon without evaluating JavaScript entries. + * Only a package-root specifier reads its exported package.json: `name`/`description` fill missing + * locale fields, and a declared `icon` takes priority over the `/icon` resource. + * A subpath specifier never reads package.json; its icon comes only from `/icon`. + * Manifest icons remain inside their declaring directory; exported icons remain inside their + * owning package after realpath resolution. Both accept SVG, PNG, JPEG, or WebP up to 256 KiB. + * Icon failures retain display text without falling back. * Non-package specifiers are skipped without invoking the resource resolver. * Language files share the directory containing the resolved English resource; * each file is resolved through the complete plugin specifier before reading. - * Missing fields use the same address's package.json name/description. Translation - * maps retain an English fallback, ultimately the full module specifier for titles and empty for descriptions. + * Translation maps retain an English fallback, ultimately the full module specifier + * for titles and empty for descriptions. * @param specifier - configured plugin module name, including any package subpath. * @param parentURL - owning Loader tree's module-resolution base. - * @returns display fields and any icon diagnostic, or undefined for non-package specifiers or absent metadata. + * @returns display fields and any resource diagnostic, or undefined for non-package specifiers or absent metadata. */ export function readPluginMeta(specifier: string, parentURL: string): PluginLocalizedMeta | undefined { - if (barePackageName(specifier) === undefined) return undefined + const packageName = barePackageName(specifier) + if (packageName === undefined) return undefined try { const englishPath = optionalResourcePath(`${specifier}/locale/en.json`, parentURL) const dictionaries = englishPath === undefined ? new Map() : dictionariesOf(englishPath, specifier, parentURL) - const manifestPath = optionalResourcePath(`${specifier}/package.json`, parentURL) + const manifestPath = packageName === specifier ? optionalResourcePath(`${specifier}/package.json`, parentURL) : undefined const manifest = manifestPath === undefined ? undefined : readObject(manifestPath) const title = localizedText('title', dictionaries, fallbackText(manifest?.name), specifier) const description = localizedText('description', dictionaries, fallbackText(manifest?.description), '') @@ -160,7 +188,7 @@ export function readPluginMeta(specifier: string, parentURL: string): PluginLoca } let icon: string | undefined try { - icon = manifestPath === undefined ? undefined : iconOf(manifest?.icon, manifestPath) + icon = iconOf(specifier, packageName, parentURL, manifestPath, manifest?.icon) } catch (error) { return { ...text, error: `Plugin metadata for ${specifier}: ${String(error)}` } } diff --git a/packages/boot/app-boot/src/profile-context.ts b/packages/boot/app-boot/src/profile-context.ts index 328b543e00..4f299ae699 100644 --- a/packages/boot/app-boot/src/profile-context.ts +++ b/packages/boot/app-boot/src/profile-context.ts @@ -2,7 +2,7 @@ import { join } from 'node:path' import { composeEntries, loadProfileDirectory, PROFILE_PATCH_FILENAME, type Profile } from './profile.ts' import { loadOptionalPatches } from './index.ts' -import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import { cloneProfilePatches, type ProfilePatch } from './profile-patches.ts' /** Application-owned package manager executable; environment applies only to package operations. */ export interface ProfilePnpmInvocation { @@ -24,7 +24,7 @@ export interface ProfileContext { /** Bundle packages used to start this process, before any persisted edits. */ readonly startedBundles: readonly string[] /** Parsed command-line overlays, applied above profile and home patches. */ - readonly overlays: readonly PatchOptions[] + readonly overlays: readonly ProfilePatch[] /** Launch-time DSH_TELEMETRY_DISABLED value; any non-empty value opts out. */ readonly telemetryDisabledEnv: string | undefined } @@ -49,7 +49,7 @@ const TELEMETRY_ROW_ID = 'session-telemetry-otel' * @param hasRow - whether the composition carries the telemetry row. * @returns the disable patch, or `undefined` when no hard-disable patch is required. */ -export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): PatchOptions | undefined { +export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: boolean): ProfilePatch | undefined { if ((disabledEnv ?? '') === '' || !hasRow) return undefined return { id: TELEMETRY_ROW_ID, disabled: true } } @@ -60,9 +60,9 @@ export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: b * @param initialProfile Already loaded startup profile; omitted reads the current files. * @returns Detached ordered patches; this function does not update the Loader. */ -export function readProfilePatches(binName: string, context: ProfileContext, initialProfile?: Profile): PatchOptions[] { +export function readProfilePatches(binName: string, context: ProfileContext, initialProfile?: Profile): ProfilePatch[] { const profile = initialProfile ?? loadProfileDirectory(binName, context.dir, context.installAnchor, { userLayer: false }) - const patches = structuredClone([ + const patches = cloneProfilePatches([ ...profile.layers.flatMap(layer => layer.patches), ...(initialProfile?.patches ?? loadOptionalPatches(binName, context.patchPath) ?? []), ...(loadOptionalPatches(binName, join(context.home, PROFILE_PATCH_FILENAME)) ?? []), diff --git a/packages/boot/app-boot/src/profile-patches.ts b/packages/boot/app-boot/src/profile-patches.ts new file mode 100644 index 0000000000..d7f34ab539 --- /dev/null +++ b/packages/boot/app-boot/src/profile-patches.ts @@ -0,0 +1,162 @@ +/** Compile preset-scoped profile operations to the native Include patch language. */ + +import { createHash } from 'node:crypto' +import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' +import { realModuleFile } from './profile-resolution/legacy-links.ts' + +/** A native entry patch, optionally applied inside one preset row's `config.plugins`. */ +export interface ProfilePatch extends PatchOptions { + /** Literal id of an addressable outer preset row; `id` then addresses one of its child rows. */ + preset?: string +} + +interface PatchSource { readonly binName: string; readonly file: string; readonly operation: number } +const patchSources = new WeakMap() + +function sourcedError(patch: ProfilePatch, error: unknown): unknown { + const source = patchSources.get(patch) + return source === undefined ? error : new Error( + `${source.binName}: ${error instanceof Error ? error.message : String(error)} in ${source.file} (patch ${source.operation + 1})`, + { cause: error }, + ) +} + +/** Clone patch data while preserving the file attribution held outside its serialized fields. + * @param patches Complete ordered patches, possibly loaded from files. + * @returns Detached patches with the same diagnostic sources and shared-reference relationships. + */ +export function cloneProfilePatches(patches: readonly ProfilePatch[]): ProfilePatch[] { + const copied = structuredClone([...patches]) + const sources = patches.map(patch => patchSources.get(patch)) + copied.forEach((patch, index) => { + const source = sources[index] + if (source !== undefined) patchSources.set(patch, source) + }) + return copied +} + +/** Attach source diagnostics and stable internal identities to parsed scoped insertions. + * @param patches Parsed file patches, modified before they reach any composition consumer. + * @param file Declaring patch file; its canonical path distinguishes anonymous contributions. + * @param binName Diagnostic prefix. + * @returns The same patches, with anonymous scoped rows identified by file and position. + * @throws When a preset target is malformed, naming its declaring file and operation. + */ +export function prepareLoadedProfilePatches(patches: ProfilePatch[], file: string, binName: string): ProfilePatch[] { + const canonical = realModuleFile(file) + patches.forEach((patch, operation) => { + patchSources.set(patch, { binName, file, operation }) + try { + if (profilePatchPreset(patch) === undefined || patch.insert === undefined) return + const identify = (rows: EntryOptions[], parent: readonly number[]): void => { + rows.forEach((row, index) => { + const position = [...parent, index] + if (!row.id) row.id = `__dsh_profile_${createHash('sha256').update(JSON.stringify([canonical, operation, position])).digest('hex').slice(0, 32)}` + if (row.group && Array.isArray(row.config)) identify(row.config as EntryOptions[], position) + }) + } + identify(patch.insert, []) + } catch (error) { + throw sourcedError(patch, error) + } + }) + return patches +} + +/** Native Include diagnostic arguments. + * @param message Diagnostic format; `%C` marks a value rendered as code. + * @param args Values substituted into the diagnostic format. + */ +export type ProfilePatchWarning = (message: string, ...args: unknown[]) => void + +function visitRows(rows: readonly EntryOptions[], visit: (row: EntryOptions) => void): void { + for (const row of rows) { + visit(row) + if (row.group && Array.isArray(row.config)) visitRows(row.config as EntryOptions[], visit) + } +} + +/** Validate the profile-only target while keeping native patch fields under Include's existing rules. + * @param patch Parsed or programmatically supplied profile patch. + * @returns The literal outer row id, or undefined for an ordinary patch. + * @throws When a declared preset target is not a nonempty literal string. + */ +export function profilePatchPreset(patch: ProfilePatch): string | undefined { + if (!Object.hasOwn(patch, 'preset')) return undefined + if (typeof patch.preset !== 'string' || patch.preset.trim() === '') { + throw new Error('profile patch preset must be a nonempty literal row id') + } + return patch.preset +} + +function targetOf(initial: EntryOptions[], patches: PatchOptions[], preset: string): EntryOptions { + // The native index retains replaced-away children and never indexes replacement-created children. + // A marker override locates its actual reachable target without rebuilding that index from the result. + const marker = {} + const rows = applyEntryPatches(initial, [...structuredClone(patches), { id: preset, inject: marker }], () => {}) + let target: EntryOptions | undefined + visitRows(rows, (row) => { if (row.inject === marker) target = row }) + if (target === undefined) throw new Error(`profile patch preset ${JSON.stringify(preset)} is missing or not addressable`) + const config: unknown = target.config + if (config === null || typeof config !== 'object' || Array.isArray(config) || !Array.isArray(Reflect.get(config, 'plugins'))) { + throw new Error(`profile patch preset ${JSON.stringify(preset)} must have a literal config.plugins entry list`) + } + return target +} + +function assertInsertedIds(preset: string, rows: readonly EntryOptions[]): void { + const ids = new Set() + visitRows(rows, (row) => { + if (!row.id) return + if (ids.has(row.id)) throw new Error(`profile patch preset ${JSON.stringify(preset)} has duplicate entry id ${JSON.stringify(row.id)}`) + ids.add(row.id) + }) +} + +/** Lower preset operations to native whole-config replacements without changing native root patch indexing. + * Each scoped operation sees the then-current preset children. Inputs remain unchanged. + * @param initial Root entries before profile patches. + * @param patches Complete ordered profile patch list. + * @param warn Diagnostic sink for scoped child patches; ordinary patches report when Include applies the result. + * @returns Detached native patches, suitable for the root Include. + * @throws For malformed or unaddressable preset targets and duplicate inserted child ids. + */ +export function compileProfilePatches( + initial: EntryOptions[], patches: readonly ProfilePatch[], warn: ProfilePatchWarning = () => {}, +): PatchOptions[] { + const lowered: PatchOptions[] = [] + for (const patch of cloneProfilePatches(patches)) { + try { + const preset = profilePatchPreset(patch) + if (preset === undefined) { + lowered.push(patch) + continue + } + const target = targetOf(initial, lowered, preset) + const config = target.config as Record & { plugins: EntryOptions[] } + const { preset: _preset, ...operation } = patch + const plugins = applyEntryPatches(config.plugins, [operation], (message: string, ...args: unknown[]) => { + warn(`preset ${JSON.stringify(preset)}: ${message}`, ...args) + }) + if (operation.insert !== undefined) assertInsertedIds(preset, plugins) + lowered.push({ id: preset, config: { ...config, plugins } }) + } catch (error) { + throw sourcedError(patch, error) + } + } + return lowered +} + +/** Compose root entries and profile patches with the same compiler used for startup and reload. + * @param initial Root entries before profile patches. + * @param patches Complete ordered profile patch list. + * @param warn Diagnostic sink for unmatched ordinary and scoped child patches. + * @returns The effective entry list, with expressions unevaluated. + * @throws For malformed or unaddressable preset targets and duplicate inserted child ids. + */ +export function applyProfilePatches( + initial: EntryOptions[], patches: readonly ProfilePatch[], warn: ProfilePatchWarning = () => {}, +): EntryOptions[] { + return applyEntryPatches(initial, compileProfilePatches(initial, patches, warn), warn) +} diff --git a/packages/boot/app-boot/src/profile-resolution/legacy-links.ts b/packages/boot/app-boot/src/profile-resolution/legacy-links.ts index 6c67762b77..7d9c75f37a 100644 --- a/packages/boot/app-boot/src/profile-resolution/legacy-links.ts +++ b/packages/boot/app-boot/src/profile-resolution/legacy-links.ts @@ -1,6 +1,7 @@ -/** Package directory canonicalization through the active runtime carrier's filesystem. */ +/** Package directory and resource canonicalization through the active runtime carrier's filesystem. */ -import { realpathSync } from 'node:fs' +import { lstatSync, realpathSync } from 'node:fs' +import { basename, dirname, join } from 'node:path' /** * Return whether the process reads application modules from pkg's virtual filesystem. @@ -18,3 +19,15 @@ function isPackagedExecutable(): boolean { export function realModuleDirectory(path: string): string { return isPackagedExecutable() ? realpathSync(path) : realpathSync.native(path) } + +/** Canonicalize a module resource, preserving physical file aliases. + * SEA archive files are symlink-free and support stat and reads, while their containing directories support realpath. + * @param path Existing module resource path. + * @returns Canonical resource path under the active carrier's filesystem. + * @throws When the resource or its directory cannot be inspected or resolved. + */ +export function realModuleFile(path: string): string { + return !isPackagedExecutable() || lstatSync(path).isSymbolicLink() + ? realpathSync(path) + : join(realModuleDirectory(dirname(path)), basename(path)) +} diff --git a/packages/boot/app-boot/src/profile-resolution/resolver.ts b/packages/boot/app-boot/src/profile-resolution/resolver.ts index 27473ba14f..85c530665d 100644 --- a/packages/boot/app-boot/src/profile-resolution/resolver.ts +++ b/packages/boot/app-boot/src/profile-resolution/resolver.ts @@ -124,11 +124,14 @@ export interface RuntimeInterception { */ packageDir(specifier: string, parentURL: string): string | undefined /** - * Atomically publish a complete successor and fresh caches. Linked roots may be added or removed. - * Removed roots stop intercepting uncovered directories; existing modules and Node caches remain intact. - * @param successor - fully constructed generation retaining existing package mappings and local names. - * @throws when the profile scope or existing mappings change, local names are removed or override - * existing mappings, or a previously published link name selects a different real directory. + * Atomically publish a complete successor and fresh caches. Profile-scoped mappings, local names, and linked + * roots may be removed; removed roots stop intercepting uncovered directories. Existing modules and Node caches + * remain intact, so callers remove a package only after its plugins stop. + * A retained profile mapping may change its declarer if its normalized directory, version, and scope stay unchanged. + * @param successor - fully constructed generation retaining the installation mappings. + * @throws when the profile scope, a retained directory, version, scope, or installation declarer changes; + * an installation mapping is removed; a new local name overrides an existing mapping; or a previously published + * link name selects a different real directory. */ replace(successor: RuntimeResolution): void /** Restore the native resolver methods. Interceptions dispose in reverse order. */ @@ -378,20 +381,16 @@ class ResolutionRouter { } for (const [name, current] of this.current.entries) { const next = entries.get(name) + if (next === undefined && current.scope === 'profile') continue if (next === undefined || !sameResolution(current.packageDir, next.packageDir) - || !sameResolution(current.declarer, next.declarer) + || (current.scope === 'installation' && !sameResolution(current.declarer, next.declarer)) || current.version !== next.version || current.scope !== next.scope) { throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`) } } const localPackageNames = new Set(successor.localPackageNames) - for (const name of this.current.localPackageNames) { - if (!localPackageNames.has(name)) { - throw new Error(`profile resolution: removing local package ${JSON.stringify(name)} requires a process restart`) - } - } for (const name of localPackageNames) { if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) { throw new Error(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`) diff --git a/packages/boot/app-boot/src/profile-resolution/service.ts b/packages/boot/app-boot/src/profile-resolution/service.ts index 5523b51eaf..7d493698a7 100644 --- a/packages/boot/app-boot/src/profile-resolution/service.ts +++ b/packages/boot/app-boot/src/profile-resolution/service.ts @@ -11,7 +11,7 @@ import { registerWorkerResolution, type RuntimeInterception, } from './resolver.ts' -import type { RuntimeResolution } from '../profile.ts' +import { ProfileRuntimeResolution, type RuntimeResolution } from '../profile.ts' import { readPluginMeta } from '../package-meta.ts' declare module '@deepseek-ai/cordis' { @@ -61,10 +61,12 @@ export class PluginPackages extends Service { private packages = new Map() private readonly interception: RuntimeInterception | undefined private disposeWorkerResolution: (() => void) | undefined + private current: RuntimeResolution | undefined constructor(ctx: Context, config: PluginPackagesConfig = {}) { super(ctx, 'pluginPackages') if (config.resolution === undefined) return + this.current = config.resolution const interception = installRuntimeInterception(config.resolution) this.disposeWorkerResolution = registerWorkerResolution(config.resolution) this.interception = interception @@ -76,17 +78,33 @@ export class PluginPackages extends Service { /** * Publish a complete successor generation for this process and subsequently created Workers. - * Linked roots may be removed without unloading modules or clearing Node caches. + * Profile mappings and local package names may be removed after their plugins stop; linked roots may also be removed. + * Retained profile mappings may change their declarer, but not their normalized directory, version, or scope; + * installation mappings must remain unchanged. Publication does not unload modules or clear Node caches. * @param successor - fully constructed generation accepted by {@link RuntimeInterception.replace}. */ replace(successor: RuntimeResolution): void { if (this.interception === undefined) throw new Error('plugin-packages: runtime resolution is not installed') this.interception.replace(successor) + this.current = successor this.packages = new Map() this.disposeWorkerResolution?.() this.disposeWorkerResolution = registerWorkerResolution(successor) } + /** + * Publish the latest generation computed by the installed resolution through {@link replace}. Package contents + * and loaded modules are not reloaded. + * @throws when no resolution is installed, it is plain data rather than a {@link ProfileRuntimeResolution}, + * reading the latest files fails, or the successor is rejected. A computed resolution without a profile can refresh. + */ + async refresh(): Promise { + if (!(this.current instanceof ProfileRuntimeResolution)) { + throw new Error('plugin-packages: the installed runtime resolution cannot be recomputed') + } + this.replace(await this.current.computeLatestResolution()) + } + /** * Locate the package named by a specifier without requiring a package export. * @param specifier - module specifier whose package owns the requested module. diff --git a/packages/boot/app-boot/src/profile.ts b/packages/boot/app-boot/src/profile.ts index da580c762e..e8aee3ce21 100644 --- a/packages/boot/app-boot/src/profile.ts +++ b/packages/boot/app-boot/src/profile.ts @@ -25,12 +25,12 @@ import { createRequire } from 'node:module' import { existsSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from 'node:fs' import { basename, dirname, join, relative, resolve, sep } from 'node:path' import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' -import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' import { resolveDshHome } from '@deepseek-ai/dsh-home-paths' import type { DshBundleManifest, DshPackageManifest } from '@deepseek-ai/dsh-package-manifest' import { evaluatePluginCompatibility, pluginCompatibilityWarning } from './plugin-compatibility.ts' import { readProfileVersionExemptions } from './profile-compatibility.ts' import { loadOverlayPatches } from './index.ts' +import { applyProfilePatches, type ProfilePatch } from './profile-patches.ts' import { realModuleDirectory } from './profile-resolution/legacy-links.ts' /** Directory under the Harness home holding every profile. */ @@ -83,7 +83,7 @@ export interface ProfileLayer { /** Absolute paths of the bundle's patch files, in application order. */ patchPaths: readonly string[] /** The parsed patch lists of every file, concatenated in application order. */ - patches: PatchOptions[] + patches: ProfilePatch[] } /** A loaded profile: resolved bundle layers plus the user's own patch layer. */ @@ -97,7 +97,7 @@ export interface Profile { /** Absolute path of the profile's own patch file. */ patchPath: string /** The profile's own patches; empty when the file is absent. */ - patches: PatchOptions[] + patches: ProfilePatch[] /** Selected bundles that contributed no layer, in `dsh.profile.bundles` order, with why. */ skippedBundles: SkippedBundle[] } @@ -199,6 +199,16 @@ const INSTALLATION_OWNED_PROFILE_TUPLES: Record = { headless: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-headless'], } +/** + * Bundles an earlier release shipped and the installation no longer carries; + * {@link loadProfileDirectory} removes them from the profile's bundle list. + */ +const RETIRED_BUNDLES: ReadonlySet = new Set([ + // The Web composition mounts Schedule itself + // ([upgrade guide](../../../../docs/upgrade-guide/v0.2.0-rc.2/schedule-bundle-retired/guide.md)). + '@deepseek-ai/dsh-experimental-schedule-bundle', +]) + /** The bundle list a `dsh plugin` init uses for a name with no shipped template. */ export const DEFAULT_PROFILE_BUNDLES: readonly string[] = ['@deepseek-ai/dsh-base'] @@ -211,9 +221,17 @@ export const DEFAULT_PROFILE_BUNDLES: readonly string[] = ['@deepseek-ai/dsh-bas * [admission](../../../../.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md)). */ export const OPTIONAL_BUNDLES: readonly string[] = [ + '@deepseek-ai/dsh-experimental-session-search', + '@deepseek-ai/dsh-experimental-ralph-bundle', + '@deepseek-ai/dsh-experimental-terminal-bundle', + '@deepseek-ai/dsh-experimental-badge-skill-bundle', + '@deepseek-ai/dsh-experimental-session-titles-bundle', '@deepseek-ai/dsh-experimental-agent-team-profile', '@deepseek-ai/dsh-experimental-voice-input-bundle', + '@deepseek-ai/dsh-experimental-cot-translation-bundle', '@deepseek-ai/dsh-experimental-auto-review', + '@deepseek-ai/dsh-experimental-inspector-profile', + '@deepseek-ai/dsh-experimental-tool-worktree', ] const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer: @@ -429,7 +447,7 @@ export interface RuntimeResolutionOptions { */ export async function createRuntimeResolution( options: RuntimeResolutionOptions, -): Promise { +): Promise { const { installAnchor, profile, home = resolveDshHome() } = options const profilesDir = join(home, PROFILES_DIR) const manifest = readOptionalProfileManifest(profile) @@ -444,7 +462,7 @@ export async function createRuntimeResolution( : collectProfileScopePackages(profile, packageNames, profileDeclarers, profileVersions) const linkedRoots = profile === undefined ? [] : linkedProfileRoots(profile, profilesDir) // The Promise return type is the pre-stable API; construction has no asynchronous step. - return await Promise.resolve(Object.freeze({ + return await Promise.resolve(new ProfileRuntimeResolution({ installAnchor, profileDir: profile?.dir, home }, { profilesDir, profileDir: profile?.dir, localPackageNames: Object.freeze(localPackageNames), @@ -462,6 +480,54 @@ export async function createRuntimeResolution( })) } +/** Inputs a {@link ProfileRuntimeResolution} reuses to compute its successor. */ +interface ResolutionSource { + installAnchor: string + home: string + profileDir: string | undefined +} + +/** + * A runtime resolution that remembers the inputs it was computed from. Worker environment data carries only its + * fields; the inputs stay private to the thread that computed it. + */ +export class ProfileRuntimeResolution implements RuntimeResolution { + readonly profilesDir: string + readonly profileDir: string | undefined + readonly localPackageNames: readonly string[] + readonly entries: readonly RuntimeResolutionEntry[] + readonly linkedRoots: readonly LinkedRoot[] + readonly #source: ResolutionSource + + /** + * @param source - inputs of {@link createRuntimeResolution}, reused by {@link computeLatestResolution}. + * @param table - the computed package table. + */ + constructor(source: ResolutionSource, table: RuntimeResolution) { + this.#source = source + this.profilesDir = table.profilesDir + this.profileDir = table.profileDir + this.localPackageNames = table.localPackageNames + this.entries = table.entries + this.linkedRoots = table.linkedRoots + Object.freeze(this) + } + + /** + * Compute the latest generation from the same installation, profile directory, and Harness home, rereading the + * profile's manifest, bundle selection, and installed packages from disk, without retaining synthetic layers. + * With no profile directory, only installation packages are recomputed. This instance is unchanged. + * @returns a new resolution for the latest generation. + */ + computeLatestResolution(): Promise { + const { installAnchor, home, profileDir } = this.#source + return createRuntimeResolution({ + installAnchor, home, + ...profileDir === undefined ? {} : { profile: loadProfileDirectory('dsh', profileDir, installAnchor) }, + }) + } +} + /** Synthetic profiles used by direct callers may have no on-disk manifest. */ function readOptionalProfileManifest(profile: Profile | undefined): ProfileManifest | undefined { if (profile === undefined) return undefined @@ -571,6 +637,20 @@ function sameBundles(left: readonly string[], right: readonly string[]): boolean return left.length === right.length && left.every((value, index) => value === right[index]) } +/** Return `manifest` with `dsh.profile.bundles` replaced, preserving all other fields. */ +function withBundles(manifest: ProfileManifest, bundles: readonly string[]): ProfileManifest { + return { + ...manifest, + dsh: { + ...manifest.dsh, + profile: { + ...manifest.dsh?.profile, + bundles: [...bundles], + }, + }, + } +} + /** * Normalize an exact installation-owned bundle tuple to its shipped template, * preserving all other manifest fields. Other bundle lists remain untouched. @@ -582,16 +662,20 @@ function normalizeShippedProfile(name: string, dir: string, manifest: ProfileMan if (template === undefined || bundles === undefined) return manifest const isRetiredTuple = installationOwned !== undefined && sameBundles(bundles, installationOwned) if (!isRetiredTuple) return manifest - const normalized: ProfileManifest = { - ...manifest, - dsh: { - ...manifest.dsh, - profile: { - ...manifest.dsh?.profile, - bundles: [...template.bundles], - }, - }, - } + const normalized = withBundles(manifest, template.bundles) + writeProfileManifest(dir, normalized) + return normalized +} + +/** + * Remove {@link RETIRED_BUNDLES} from a profile's bundle list, writing the + * manifest back only when it listed one. + */ +function dropRetiredBundles(dir: string, manifest: ProfileManifest): ProfileManifest { + const bundles = manifest.dsh?.profile?.bundles ?? [] + const kept = bundles.filter(bundle => !RETIRED_BUNDLES.has(bundle)) + if (kept.length === bundles.length) return manifest + const normalized = withBundles(manifest, kept) writeProfileManifest(dir, normalized) return normalized } @@ -643,7 +727,8 @@ export function resolveBundleDir( * Load an already initialized profile directory without resolving it through * the shared Harness home. This is used by application-owned profiles whose * package project and lifecycle belong to that application. - * Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped + * Retired bundles are removed from the stored bundle list first, rewriting the + * manifest when it listed one. Unreadable bundles, and bundles whose own dsh peers the profile does not exempt, are skipped * without changing the manifest and listed in `skippedBundles`; nothing is printed. * @param binName - the diagnostic prefix on thrown errors. * @param dir - absolute profile package directory. @@ -657,7 +742,7 @@ export function loadProfileDirectory( installAnchor: string, options: { userLayer?: boolean } = {}, ): Profile { - const manifest = readProfileManifest(binName, dir) + const manifest = dropRetiredBundles(dir, readProfileManifest(binName, dir)) const bundles = manifest.dsh?.profile?.bundles ?? [] const layers: ProfileLayer[] = [] const skippedBundles: SkippedBundle[] = [] @@ -721,16 +806,16 @@ export function loadProfile( /** * Compose patch layers into the effective entry list over an empty root — - * the same single `applyEntryPatches` call the boot include makes, so flag - * derivation and config dumps see exactly what mounts. + * compile preset operations and apply the same native patch list as boot, + * so flag derivation and config dumps see exactly what mounts. * @param layers - patch lists in application order. * @param warn - sink for skipped-patch diagnostics; defaults to silent (boot repeats them). * @returns the composed entry list. */ export function composeEntries( - layers: readonly PatchOptions[][], warn: (message: string) => void = () => {}, + layers: readonly ProfilePatch[][], warn: (message: string) => void = () => {}, ): EntryOptions[] { - return applyEntryPatches([], structuredClone(layers.flat()), (message: string, ...args: unknown[]) => { + return applyProfilePatches([], layers.flat(), (message: string, ...args: unknown[]) => { let index = 0 warn(message.replace(/%C/g, () => JSON.stringify(args[index++]))) }) diff --git a/packages/boot/app-boot/tests/config-schema.spec.ts b/packages/boot/app-boot/tests/config-schema.spec.ts index ed7ce470dc..aaacfcd68e 100644 --- a/packages/boot/app-boot/tests/config-schema.spec.ts +++ b/packages/boot/app-boot/tests/config-schema.spec.ts @@ -79,7 +79,8 @@ describe('generateConfigSchema', () => { ] const original = structuredClone(layers) const installAnchor = join(dir, 'installation.json') - const resolve = vi.spyOn(profileOperations, 'createRuntimeResolution').mockResolvedValue(resolution) + const resolve = vi.spyOn(profileOperations, 'createRuntimeResolution') + .mockResolvedValue(new profileOperations.ProfileRuntimeResolution({ installAnchor: '', home: '', profileDir: undefined }, resolution)) const result = await generateConfigSchema(profile, layers, installAnchor) expect(resolve).toHaveBeenCalledExactlyOnceWith({ installAnchor, profile }) expect(result['x-cordis'].entries.map(entry => [entry.path, entry.name])).toEqual([ @@ -499,6 +500,10 @@ describe('collectConfigSchemas', () => { expect(validates(result, [{ name: 'server', config: { port: 'wrong' } }])).toBe(false) expect(validates(result, [{ name: 'new-plugin', config: { arbitrary: true } }])).toBe(true) expect(validates(result, [{ id: 'server', disabled: true }], 'patchList')).toBe(true) + expect(validates(result, [{ preset: 'preset-standard', id: 'server', config: 'child-owned' }], 'patchList')).toBe(true) + expect(validates(result, [{ preset: '', insert: [] }], 'patchList')).toBe(false) + expect(validates(result, [{ preset: ' ', insert: [] }], 'patchList')).toBe(false) + expect(validates(result, [{ preset: { __jsExpr: 'ctx.preset' }, insert: [] }], 'patchList')).toBe(false) expect(validates(result, [{ id: 'server', config: { port: 'wrong' } }], 'patchList')).toBe(false) expect(validates(result, [{ id: 'server', config: {} }], 'patchList')).toBe(false) expect(validates(result, [{ id: 'server', name: 'not-server', config: 'ignored' }], 'patchList')).toBe(true) diff --git a/packages/boot/app-boot/tests/official-bundles.spec.ts b/packages/boot/app-boot/tests/official-bundles.spec.ts new file mode 100644 index 0000000000..37ab1f4a70 --- /dev/null +++ b/packages/boot/app-boot/tests/official-bundles.spec.ts @@ -0,0 +1,20 @@ +/** The offline catalog does not depend on installed provider packages or registry requests. */ + +import { describe, expect, it } from 'vitest' +import { OFFICIAL_ON_DEMAND_CATALOG, ON_DEMAND_BUNDLES } from '../src/official-bundles.ts' + +describe('Official on-demand catalog', () => { + it('offers exactly the two native subagent integrations with offline localized metadata', () => { + expect(ON_DEMAND_BUNDLES).toEqual(['@deepseek-ai/dsh-subagent-claude-code', '@deepseek-ai/dsh-subagent-codex']) + expect(OFFICIAL_ON_DEMAND_CATALOG.map(entry => entry.packageName)).toEqual(ON_DEMAND_BUNDLES) + for (const entry of OFFICIAL_ON_DEMAND_CATALOG) { + expect(entry.meta.icon).toMatch(/^data:image\/svg\+xml;base64,/) + for (const text of [entry.meta.title, entry.meta.description]) { + if (typeof text === 'string') throw new Error('Official metadata requires both shipped languages') + expect(text.en).toMatch(/\S/) + expect(text.zh).toMatch(/\S/) + } + expect(entry).not.toHaveProperty('version') + } + }) +}) diff --git a/packages/boot/app-boot/tests/package-meta.spec.ts b/packages/boot/app-boot/tests/package-meta.spec.ts index 3f6a170777..21ea6b9fd5 100644 --- a/packages/boot/app-boot/tests/package-meta.spec.ts +++ b/packages/boot/app-boot/tests/package-meta.spec.ts @@ -105,6 +105,18 @@ describe('plugin locale display metadata', () => { }) }) + it('never reads package.json for a subpath plugin, even when one is exported at its address', () => { + manifest({ './search/locale/*.json': './locale/*.json', './search/package.json': './broken.json', './icon': './missing.svg' }) + file(join(dir, 'broken.json'), '{') + dictionary('en', { meta: { title: 'Search' } }) + expect(readPluginMeta('localized/search', parentURL)).toEqual({ title: { en: 'Search' } }) + }) + + it.each([undefined, null])('keeps a bundle with exports=%j manageable without an icon', (exports) => { + file(join(dir, 'package.json'), JSON.stringify({ name: 'localized', description: 'Plain bundle', type: 'module', exports })) + expect(readPluginMeta('localized', parentURL)).toEqual({ title: 'localized', description: 'Plain bundle' }) + }) + it('falls back to package fields when locale resources are absent', () => { manifest({ '.': './index.js', './package.json': './package.json' }, { description: 'Package introduction' }) expect(readPluginMeta('localized', parentURL)).toEqual({ title: 'localized', description: 'Package introduction' }) @@ -135,26 +147,26 @@ describe('plugin locale display metadata', () => { }) }) - it('uses only the package resource exported for the requested plugin address', () => { - manifest({ - './package.json': './package.json', - './search/package.json': './search-manifest.json', - }, { description: 'Whole package' }) - file(join(dir, 'search-manifest.json'), JSON.stringify({ name: 'Search plugin', description: 'Search introduction' })) - expect(readPluginMeta('localized/search', parentURL)).toEqual({ title: 'Search plugin', description: 'Search introduction' }) + it('reads a subpath icon only from the resource exported at that plugin address', () => { + manifest({ './package.json': './package.json', './icon': './root.svg', './search/icon': './search.svg' }, { icon: './root.svg' }) + file(join(dir, 'root.svg'), 'root') + file(join(dir, 'search.svg'), 'search') + expect(readPluginMeta('localized/search', parentURL)).toEqual({ + icon: `data:image/svg+xml;base64,${Buffer.from('search').toString('base64')}`, + }) expect(readPluginMeta('localized/review', parentURL)).toBeUndefined() }) it.each([{}, { name: '', description: ' ' }, { name: false, description: null }])('ignores unavailable package text: %j', (fields) => { - manifest({ './feature/package.json': './feature.json' }) + manifest({ './package.json': './feature.json' }) file(join(dir, 'feature.json'), JSON.stringify(fields)) - expect(readPluginMeta('localized/feature', parentURL)).toBeUndefined() + expect(readPluginMeta('localized', parentURL)).toBeUndefined() }) - it('reports malformed fallback files', () => { - manifest({ './feature/package.json': './feature.json' }) + it('reports malformed package manifests', () => { + manifest({ './package.json': './feature.json' }) file(join(dir, 'feature.json'), '{') - expect(readPluginMeta('localized/feature', parentURL)?.error).toContain('feature.json') + expect(readPluginMeta('localized', parentURL)?.error).toContain('feature.json') }) it('does not hide invalid locale fields behind package text', () => { @@ -163,8 +175,67 @@ describe('plugin locale display metadata', () => { expect(readPluginMeta('localized', parentURL)?.error).toContain('meta.title must be a non-empty string') }) - it('reads the manifest icon even when both English fields are supplied', () => { - manifest({ './locale/*.json': './locale/*.json', './package.json': './package.json' }, { icon: './art/team.svg' }) + it.each([undefined, './fallback.svg', '../invalid.svg'])('prefers the manifest icon to export %j', (fallback) => { + manifest({ './package.json': './package.json', './icon': fallback }, { icon: './legacy.svg' }) + file(join(dir, 'legacy.svg'), 'legacy') + file(join(dir, 'fallback.svg'), 'fallback') + expect(readPluginMeta('localized', parentURL)).toEqual({ + title: 'localized', icon: `data:image/svg+xml;base64,${Buffer.from('legacy').toString('base64')}`, + }) + }) + + it('reads a legacy icon without an exports map', () => { + file(join(dir, 'package.json'), JSON.stringify({ name: 'localized', type: 'module', icon: 'legacy.svg' })) + file(join(dir, 'legacy.svg'), 'legacy') + expect(readPluginMeta('localized', parentURL)?.icon) + .toBe(`data:image/svg+xml;base64,${Buffer.from('legacy').toString('base64')}`) + }) + + it('resolves a legacy icon relative to its remapped exported manifest', () => { + manifest({ './package.json': './display/manifest.json', './icon': './fallback.svg' }) + file(join(dir, 'display', 'manifest.json'), JSON.stringify({ icon: './logo.svg' })) + file(join(dir, 'display', 'logo.svg'), 'remapped') + file(join(dir, 'fallback.svg'), 'fallback') + expect(readPluginMeta('localized', parentURL)).toEqual({ + icon: `data:image/svg+xml;base64,${Buffer.from('remapped').toString('base64')}`, + }) + }) + + it('keeps legacy icons confined to the declaring manifest directory', () => { + manifest({ './package.json': './display/manifest.json', './icon': './fallback.svg' }) + file(join(dir, 'display', 'manifest.json'), JSON.stringify({ icon: '../outside.svg' })) + file(join(dir, 'outside.svg'), 'outside') + file(join(dir, 'fallback.svg'), 'fallback') + const meta = readPluginMeta('localized', parentURL) + expect(meta?.error).toContain(`${join(dir, 'display', 'manifest.json')}: icon must remain inside its manifest directory`) + expect(meta?.icon).toBeUndefined() + }) + + it.each([null, false, 1, '', ' ', '/tmp/icon.svg', 'C:/icons/icon.svg', 'https://example.test/icon.svg']) + ('does not fall through an invalid manifest icon %j', (icon) => { + manifest({ './package.json': './package.json', './icon': './fallback.svg' }, { icon }) + file(join(dir, 'fallback.svg'), 'fallback') + const meta = readPluginMeta('localized', parentURL) + expect(meta?.title).toBe('localized') + expect(meta?.error).toBeDefined() + expect(meta?.icon).toBeUndefined() + }) + + it.each(['missing.svg', 'directory.svg', 'oversized.svg', 'unsupported.gif']) + ('does not replace an unreadable or invalid legacy image %s with the export', (icon) => { + manifest({ './package.json': './package.json', './icon': './fallback.svg' }, { icon }) + file(join(dir, 'fallback.svg'), 'fallback') + mkdirSync(join(dir, 'directory.svg')) + file(join(dir, 'oversized.svg'), 'x'.repeat(256 * 1024 + 1)) + file(join(dir, 'unsupported.gif'), 'gif') + const meta = readPluginMeta('localized', parentURL) + expect(meta?.title).toBe('localized') + expect(meta?.error).toBeDefined() + expect(meta?.icon).toBeUndefined() + }) + + it('reads the exported bundle icon even when both English fields are supplied', () => { + manifest({ './locale/*.json': './locale/*.json', './package.json': './package.json', './icon': './art/team.svg' }) file(join(dir, 'art', 'team.svg'), '') dictionary('en', { meta: { title: 'Plugin', description: 'Locale introduction' } }) expect(readPluginMeta('localized', parentURL)).toEqual({ @@ -173,11 +244,51 @@ describe('plugin locale display metadata', () => { }) }) + it.each(['manifest', 'export'])('reads a package-owned %s icon when the archive cannot realpath regular files', (source) => { + manifest({ './package.json': './package.json', './icon': './icon.svg' }, source === 'manifest' ? { icon: './icon.svg' } : {}) + const icon = join(dir, 'icon.svg') + file(icon, '') + // The executable resolver supplies module URLs independently of archive file realpath support. + resolvePluginResource('localized/icon', parentURL) + const original = fs.realpathSync + const previousPkg = Object.getOwnPropertyDescriptor(process, 'pkg') + Object.defineProperty(process, 'pkg', { configurable: true, value: {} }) + fs.realpathSync = new Proxy(original, { + apply(target, receiver: unknown, args: unknown[]): unknown { + if (String(args[0]).endsWith(join('localized', 'icon.svg'))) { + throw Object.assign(new Error('archive-backed icon has no realpath entry'), { code: 'ENOENT' }) + } + return Reflect.apply(target, receiver, args) + }, + }) + syncBuiltinESMExports() + try { + expect(() => fs.realpathSync(icon)).toThrow('archive-backed') + expect(readPluginMeta('localized', parentURL)).toEqual({ title: 'localized', icon: 'data:image/svg+xml;base64,PHN2Zy8+' }) + } finally { + fs.realpathSync = original + if (previousPkg === undefined) Reflect.deleteProperty(process, 'pkg') + else Object.defineProperty(process, 'pkg', previousPkg) + syncBuiltinESMExports() + } + }) + + it.each(['manifest', 'export'])('refuses a %s icon file symlink owned by another package', (source) => { + manifest({ './package.json': './package.json', './icon': './icon.svg' }, source === 'manifest' ? { icon: './icon.svg' } : {}) + const foreign = join(root, 'foreign-package', 'icon.svg') + file(foreign, '') + file(join(root, 'foreign-package', 'package.json'), '{"name":"foreign-package"}') + symlinkSync(foreign, join(dir, 'icon.svg'), 'file') + const meta = readPluginMeta('localized', parentURL) + expect(meta?.icon).toBeUndefined() + expect(meta?.error).toContain(source === 'manifest' ? 'icon must remain inside its manifest directory' : 'icon must remain inside its package directory') + }) + it.each([ ['svg', 'image/svg+xml'], ['png', 'image/png'], ['jpg', 'image/jpeg'], ['jpeg', 'image/jpeg'], ['webp', 'image/webp'], ['SVG', 'image/svg+xml'], ])('encodes a package-local %s icon without executing the plugin', (extension, mediaType) => { - manifest({ '.': './index.js', './package.json': './package.json' }, { icon: `./icon.${extension}` }) + manifest({ '.': './index.js', './package.json': './package.json', './icon': `./icon.${extension}` }) const bytes = Buffer.from([0, 1, 127, 128, 255]) writeFileSync(join(dir, `icon.${extension}`), bytes) expect(readPluginMeta('localized', parentURL)).toEqual({ @@ -185,50 +296,37 @@ describe('plugin locale display metadata', () => { }) }) - it('reads an icon-only subexport relative to its own manifest without inheriting the package icon', () => { - manifest({ './package.json': './package.json', './search/package.json': './search/manifest.json' }, { icon: './root.png' }) - file(join(dir, 'root.png'), 'root') - file(join(dir, 'search', 'manifest.json'), JSON.stringify({ icon: './icon.webp' })) - file(join(dir, 'search', 'icon.webp'), 'search') - expect(readPluginMeta('localized/search', parentURL)).toEqual({ - icon: `data:image/webp;base64,${Buffer.from('search').toString('base64')}`, + it.each(['./assets/logo.webp', { import: './assets/logo.webp', default: './missing.gif' }])('reads an independent icon using the Node-selected export %j', (icon) => { + manifest({ './icon': icon }) + file(join(dir, 'assets', 'logo.webp'), 'image') + expect(readPluginMeta('localized', parentURL)).toEqual({ + icon: `data:image/webp;base64,${Buffer.from('image').toString('base64')}`, }) - expect(readPluginMeta('localized/review', parentURL)).toBeUndefined() }) - it.each([null, false, 1, {}, [], '', ' '])('retains localized text while rejecting malformed icon %j', (icon) => { - manifest({ './locale/*.json': './locale/*.json', './package.json': './package.json' }, { icon }) - dictionary('en', { meta: { title: 'Plugin', description: 'Introduction' } }) - dictionary('zh', { meta: { title: '插件' } }) - const { error, ...meta } = readPluginMeta('localized', parentURL)! - expect(meta).toEqual({ title: { en: 'Plugin', zh: '插件' }, description: { en: 'Introduction' } }) - expect(error).toContain('icon must be a non-empty string') + it.each([false, 1, '', '/tmp/icon.svg', '../icon.svg', 'https://example.test/icon.svg'])('reports invalid exported icon target %j while retaining text', (icon) => { + manifest({ './locale/*.json': './locale/*.json', './icon': icon }) + dictionary('en', { meta: { title: 'Plugin' } }) + const meta = readPluginMeta('localized', parentURL) + expect(meta?.title).toEqual({ en: 'Plugin' }) + expect(meta?.error).toBeDefined() }) - it.each(['/tmp/icon.svg', 'C:\\icon.png', 'C:icon.png', '\\\\server\\share\\icon.png', 'https://example.test/icon.svg', 'data:image/svg+xml,'])('rejects non-relative icon %s', (icon) => { - manifest({ './package.json': './package.json' }, { icon, description: 'Introduction' }) - const { error, ...meta } = readPluginMeta('localized', parentURL)! - expect(meta).toEqual({ title: 'localized', description: 'Introduction' }) - expect(error).toContain('icon must be a relative file path') - }) - - it.each(['icon.gif', 'icon.html', 'icon', 'icon.svg?query'])('rejects unsupported icon file %s', (icon) => { - manifest({ './package.json': './package.json' }, { icon }) + it.each(['icon.gif', 'icon.html', 'icon'])('rejects unsupported icon file %s', (icon) => { + manifest({ './package.json': './package.json', './icon': `./${icon}` }) + file(join(dir, icon), 'unsupported') expect(readPluginMeta('localized', parentURL)?.error).toContain('icon must be SVG, PNG, JPEG, or WebP') }) - it('reports missing files, directories, and paths outside the manifest directory', () => { - manifest({ './package.json': './package.json' }, { icon: './missing.svg' }) - expect(readPluginMeta('localized', parentURL)?.error).toContain('missing.svg') + it('treats a missing icon target like a missing locale file and reports a directory target', () => { + manifest({ './icon': './missing.svg' }) + expect(readPluginMeta('localized', parentURL)).toBeUndefined() mkdirSync(join(dir, 'missing.svg')) - expect(readPluginMeta('localized', parentURL)?.error).toContain('icon must be a regular file') - manifest({ './package.json': './package.json' }, { icon: '../localized-other/icon.svg' }) - file(join(root, 'node_modules', 'localized-other', 'icon.svg'), 'outside') - expect(readPluginMeta('localized', parentURL)?.error).toContain('icon must remain inside its manifest directory') + expect(readPluginMeta('localized', parentURL)?.error).toContain('Directory import') }) it('accepts an icon at the byte limit and rejects one byte more', () => { - manifest({ './package.json': './package.json' }, { icon: './icon.png' }) + manifest({ './package.json': './package.json', './icon': './icon.png' }) const bytes = Buffer.alloc(256 * 1024) writeFileSync(join(dir, 'icon.png'), bytes) expect(readPluginMeta('localized', parentURL)?.icon).toBe(`data:image/png;base64,${bytes.toString('base64')}`) @@ -236,8 +334,8 @@ describe('plugin locale display metadata', () => { expect(readPluginMeta('localized', parentURL)?.error).toContain('icon exceeds 256 KiB') }) - it('rejects an icon that grows beyond the limit after its size check', () => { - manifest({ './package.json': './package.json' }, { icon: './icon.png' }) + it.each(['growth', 'directory'])('rejects an icon changed after resolution: %s', (change) => { + manifest({ './package.json': './package.json', './icon': './icon.png' }) const icon = join(dir, 'icon.png') file(icon, 'small') const original = fs.statSync @@ -245,35 +343,36 @@ describe('plugin locale display metadata', () => { try { stat.mockImplementation(new Proxy(original, { apply(target, receiver: unknown, args: unknown[]): unknown { + const ownIcon = String(args[0]).endsWith(join('localized', 'icon.png')) + if (ownIcon && change === 'directory') { + unlinkSync(icon) + mkdirSync(icon) + } const result: unknown = Reflect.apply(target, receiver, args) - if (String(args[0]).endsWith(join('localized', 'icon.png'))) writeFileSync(icon, Buffer.alloc(256 * 1024 + 1)) + if (ownIcon && change === 'growth') writeFileSync(icon, Buffer.alloc(256 * 1024 + 1)) return result }, })) syncBuiltinESMExports() - expect(readPluginMeta('localized', parentURL)?.error).toContain('icon exceeds 256 KiB') + expect(readPluginMeta('localized', parentURL)?.error) + .toContain(change === 'growth' ? 'icon exceeds 256 KiB' : 'icon must be a regular file') } finally { stat.mockRestore() syncBuiltinESMExports() } }) - it('rejects icons reached through an external directory link and accepts internal links', () => { - manifest({ './package.json': './package.json' }, { icon: './art/icon.svg' }) - const outside = join(root, 'outside') - file(join(outside, 'icon.svg'), 'outside') + it.each(['inside', 'outside'])('checks an icon reached through a directory link %s the package', (location) => { + manifest({ './icon': './art/icon.svg' }) + const destination = join(location === 'inside' ? dir : root, 'art-target') + file(join(destination, 'icon.svg'), location) + file(join(destination, 'package.json'), JSON.stringify({ name: 'nested' })) const link = join(dir, 'art') - symlinkSync(outside, link, 'junction') + symlinkSync(destination, link, 'junction') try { - expect(readPluginMeta('localized', parentURL)?.error).toContain('icon must remain inside its manifest directory') - } finally { - unlinkSync(link) - } - const inside = join(dir, 'images') - file(join(inside, 'icon.svg'), 'inside') - symlinkSync(inside, link, 'junction') - try { - expect(readPluginMeta('localized', parentURL)?.icon).toBe(`data:image/svg+xml;base64,${Buffer.from('inside').toString('base64')}`) + const meta = readPluginMeta('localized', parentURL) + if (location === 'outside') expect(meta?.error).toContain('icon must remain inside its package directory') + else expect(meta?.icon).toBe(`data:image/svg+xml;base64,${Buffer.from(location).toString('base64')}`) } finally { unlinkSync(link) } @@ -379,7 +478,11 @@ describe('plugin locale display metadata', () => { it.each(['v1', 'v2'] as const)('uses the %s Node resolver argument order', (version) => { const loader = ModuleLoader.fromInternal()! dictionary('en', { meta: { title: 'Resolved resource', description: 'Resolved introduction' } }) - const resolveSync = vi.fn(() => ({ url: pathToFileURL(join(dir, 'locale', 'en.json')).href })) + const resolveSync = vi.fn((...args: unknown[]) => { + const request = version === 'v1' ? args[0] : (args[1] as { specifier: string }).specifier + if (request !== 'localized/locale/en.json') throw Object.assign(new Error(`${String(request)} is not exported`), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }) + return { url: pathToFileURL(join(dir, 'locale', 'en.json')).href } + }) const adapted = new Proxy(loader, { get(target, property) { if (property === 'version') return version @@ -392,11 +495,10 @@ describe('plugin locale display metadata', () => { expect(readPluginMeta('localized', parentURL)).toEqual({ title: { en: 'Resolved resource' }, description: { en: 'Resolved introduction' }, }) - const specifier = 'localized/locale/en.json' - expect(resolveSync.mock.calls).toEqual(version === 'v1' - ? [[specifier, parentURL, {}], [specifier, parentURL, {}], ['localized/package.json', parentURL, {}]] - : [[parentURL, { specifier, attributes: {} }], [parentURL, { specifier, attributes: {} }], - [parentURL, { specifier: 'localized/package.json', attributes: {} }]]) + const requests = ['localized/locale/en.json', 'localized/locale/en.json', 'localized/package.json', 'localized/icon'] + expect(resolveSync.mock.calls).toEqual(requests.map(specifier => version === 'v1' + ? [specifier, parentURL, {}] + : [parentURL, { specifier, attributes: {} }])) }) it('rejects case-equivalent language names in a directory listing', () => { diff --git a/packages/boot/app-boot/tests/profile-patches-carrier.spec.ts b/packages/boot/app-boot/tests/profile-patches-carrier.spec.ts new file mode 100644 index 0000000000..d9d913d11e --- /dev/null +++ b/packages/boot/app-boot/tests/profile-patches-carrier.spec.ts @@ -0,0 +1,78 @@ +/** Patch identities work with archive-backed files and preserve physical filesystem aliases. */ +import { lstatSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, expect, it, onTestFinished, vi } from 'vitest' +import { loadOverlayPatches, realModuleFile } from '../src/index.ts' + +vi.mock('node:fs', async (importOriginal) => { + const actual = await importOriginal() + return { ...actual, realpathSync: Object.assign(vi.fn(actual.realpathSync), { native: actual.realpathSync.native }) } +}) + +afterEach(() => { vi.mocked(realpathSync).mockReset() }) + +function packagedRuntime(): void { + const previous = Object.getOwnPropertyDescriptor(process, 'pkg') + Object.defineProperty(process, 'pkg', { configurable: true, value: {} }) + onTestFinished(() => { + if (previous === undefined) Reflect.deleteProperty(process, 'pkg') + else Object.defineProperty(process, 'pkg', previous) + }) +} + +function fixture(): string { + const root = mkdtempSync(join(tmpdir(), 'profile-patch-carrier-')) + onTestFinished(() => { rmSync(root, { recursive: true, force: true }) }) + return root +} + +it('loads ordinary and anonymous scoped patches when packaged files support reads and directory realpath', () => { + packagedRuntime() + const root = fixture() + const file = join(root, 'cordis.patch.yml') + writeFileSync(file, JSON.stringify([ + { insert: [{ id: 'base', name: 'base' }] }, + { preset: 'preset-standard', insert: [{ name: 'anonymous' }] }, + ])) + vi.mocked(realpathSync).mockImplementation((path, options) => { + if (lstatSync(path).isFile()) throw Object.assign(new Error('archive file has no physical realpath'), { code: 'ENOENT' }) + return realpathSync.native(path, options) + }) + expect(() => realpathSync(file)).toThrow('archive file has no physical realpath') + const first = loadOverlayPatches('test', file) + expect(first[0]).toEqual({ insert: [{ id: 'base', name: 'base' }] }) + expect(first[1]?.insert?.[0]).toEqual({ id: expect.any(String) as string, name: 'anonymous' }) + expect(loadOverlayPatches('test', file)).toEqual(first) + expect(realModuleFile(file)).toBe(join(realpathSync(root), 'cordis.patch.yml')) +}) + +it.each([false, true])('keeps directory and file symlinks on the same patch identity (packaged: %s)', (packaged) => { + if (packaged) packagedRuntime() + const root = fixture() + const directory = join(root, 'bundle') + mkdirSync(directory) + const file = join(directory, 'Patch.yml') + writeFileSync(file, JSON.stringify([{ preset: 'preset-standard', insert: [{ name: 'anonymous' }] }])) + const directoryAlias = join(root, 'bundle-alias') + const fileAlias = join(root, 'patch-alias.yml') + symlinkSync(directory, directoryAlias, process.platform === 'win32' ? 'junction' : 'dir') + symlinkSync(file, fileAlias, 'file') + const first = loadOverlayPatches('test', file) + expect(loadOverlayPatches('test', join(directoryAlias, 'Patch.yml'))).toEqual(first) + expect(loadOverlayPatches('test', fileAlias)).toEqual(first) + expect(realModuleFile(fileAlias)).toBe(realpathSync(file)) +}) + +it('keeps ordinary physical resource normalization identical to Node realpath', () => { + const root = fixture() + const file = join(root, 'MixedCase.yml') + writeFileSync(file, '[]\n') + const requested = process.platform === 'win32' ? file.toLowerCase() : file + expect(realModuleFile(requested)).toBe(realpathSync(requested)) +}) + +it('reports missing packaged resources instead of assigning them an identity', () => { + packagedRuntime() + expect(() => realModuleFile(join(fixture(), 'absent.yml'))).toThrow('ENOENT') +}) diff --git a/packages/boot/app-boot/tests/profile-patches.spec.ts b/packages/boot/app-boot/tests/profile-patches.spec.ts new file mode 100644 index 0000000000..7afd826d32 --- /dev/null +++ b/packages/boot/app-boot/tests/profile-patches.spec.ts @@ -0,0 +1,305 @@ +/** Preset operations preserve native root patch lookup and share startup/dump/reload compilation. */ +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { describe, expect, it, onTestFinished } from 'vitest' +import { load } from 'js-yaml' +import { Context } from '@deepseek-ai/cordis' +import { EntryGroup, type EntryOptions } from '@deepseek-ai/cordis-plugin-loader' +import Group from '@deepseek-ai/cordis-plugin-group' +import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' +import { + applyProfilePatches, boot, compileProfilePatches, composeEntries, loadOverlayPatches, + prepareProfilePatches, profilePatchPreset, reconcileProfilePatches, renderConfigDump, type ProfilePatch, +} from '../src/index.ts' + +import { cloneProfilePatches } from '../src/profile-patches.ts' + +const preset = (plugins: EntryOptions[] = []): Omit & { + config: { id: string; description: string; plugins: EntryOptions[] } +} => ({ + id: 'preset-standard', name: 'cordis:test-preset', config: { id: 'standard', description: 'Original', plugins }, +}) +const row = (id: string): EntryOptions => ({ id, name: 'test-plugin', config: { value: 'initial' } }) +const children = (rows: EntryOptions[]): EntryOptions[] => (rows[0]!.config as { plugins: EntryOptions[] }).plugins +const temp = (): string => { + const dir = mkdtempSync(join(tmpdir(), 'dsh-preset-patches-')) + onTestFinished(() => { rmSync(dir, { recursive: true, force: true }) }) + return dir +} + +describe('preset patch composition', () => { + it('composes bundle insertions and later scoped user overrides without mutating inputs', () => { + const initial = [preset([row('base')])] + const patches: ProfilePatch[] = [ + { preset: 'preset-standard', insert: [row('optional')] }, + { preset: 'preset-standard', id: 'optional', config: { value: { __jsExpr: 'ctx.value' } } }, + { preset: 'preset-standard', id: 'base', disabled: true }, + ] + const original = structuredClone({ initial, patches }) + const result = applyProfilePatches(initial, patches) + expect(children(result)).toEqual([ + { ...row('base'), disabled: true }, + { ...row('optional'), config: { value: { __jsExpr: 'ctx.value' } } }, + ]) + expect(result[0]?.config).toMatchObject({ description: 'Original' }) + expect({ initial, patches }).toEqual(original) + expect(composeEntries([[{ insert: initial }], patches])).toEqual(result) + expect(compileProfilePatches(initial, patches).every(patch => !Object.hasOwn(patch, 'preset'))).toBe(true) + }) + + it('allows a later whole-preset replacement to replace all scoped edits', () => { + const replacement = { id: 'standard', plugins: [row('user')] } + expect(applyProfilePatches([preset()], [ + { preset: 'preset-standard', insert: [row('optional')] }, + { id: 'preset-standard', config: replacement }, + ])[0]?.config).toEqual(replacement) + }) + + it('applies each scoped operation to the current child list, including replacement children', () => { + const group = { id: 'group', name: 'cordis:group', group: true, config: [] } + const result = applyProfilePatches([preset([group])], [ + { preset: 'preset-standard', id: 'group', config: [row('replaced-child')] }, + { preset: 'preset-standard', id: 'replaced-child', disabled: true }, + { preset: 'preset-standard', id: 'group', insert: [row('inserted-child')] }, + ]) + expect(children(result)[0]?.config).toEqual([ + { ...row('replaced-child'), disabled: true }, row('inserted-child'), + ]) + }) + + it('retains ordinary root single-index behavior before and after a scoped operation', () => { + const warn: string[] = [] + const result = applyProfilePatches([ + { id: 'group', name: 'cordis:group', group: true, config: [] }, preset(), + ], [ + { id: 'group', config: [row('replacement')] }, + { preset: 'preset-standard', insert: [row('optional')] }, + { id: 'replacement', disabled: true }, + ], message => warn.push(message)) + expect(result[0]?.config).toEqual([row('replacement')]) + expect(warn).toEqual(['patch: entry %C not found']) + }) + + it.each([ + [{ id: 'group', name: 'cordis:group', group: true, config: [] }, [preset()]], + [{ id: 'group', name: 'cordis:group', group: true, config: [preset()] }, []], + ])('rejects an outer target added or removed by a whole-group replacement', (group, replacement) => { + expect(() => applyProfilePatches([group as EntryOptions], [ + { id: 'group', config: replacement }, { preset: 'preset-standard', insert: [row('optional')] }, + ])).toThrow('is missing or not addressable') + }) + + it('accepts an outer target inserted in a patchable group', () => { + const result = applyProfilePatches([{ id: 'group', name: 'cordis:group', group: true, config: [] }], [ + { id: 'group', insert: [preset()] }, + { preset: 'preset-standard', insert: [row('optional')] }, + ]) + expect(children(result[0]?.config as EntryOptions[])).toEqual([row('optional')]) + }) + + it('warns and skips a stale user override after its optional bundle is deselected', () => { + const messages: unknown[][] = [] + expect(applyProfilePatches([preset()], [{ preset: 'preset-standard', id: 'optional', disabled: true }], + (...args) => messages.push(args))).toEqual([preset()]) + expect(messages).toEqual([['preset "preset-standard": patch: entry %C not found', 'optional']]) + }) + + it.each([null, '', ' ', 1, { __jsExpr: 'ctx.preset' }])('rejects a nonliteral or empty outer target %j', (target) => { + const dir = temp() + const path = join(dir, 'patch.yml') + writeFileSync(path, JSON.stringify([{ preset: target, insert: [] }])) + expect(() => loadOverlayPatches('test', path)).toThrow('nonempty literal row id') + expect(() => loadOverlayPatches('test', path)).toThrow(path) + }) + + it('allows callers to omit a warning sink for skipped ordinary and scoped rows', () => { + expect(applyProfilePatches([], [{ id: 'missing', disabled: true }])).toEqual([]) + expect(compileProfilePatches([preset()], [{ preset: 'preset-standard', id: 'missing', disabled: true }])) + .toEqual([{ id: 'preset-standard', config: preset().config }]) + }) + + it('rejects a missing outer row', () => { + expect(() => applyProfilePatches([], [{ preset: 'missing', insert: [] }])).toThrow('preset "missing" is missing') + expect(profilePatchPreset({ id: 'ordinary' })).toBeUndefined() + }) + + it.each([undefined, null, [], { plugins: { __jsExpr: '[]' } }])('rejects a malformed preset config %j', (config) => { + expect(() => applyProfilePatches([{ ...preset(), config }], [{ preset: 'preset-standard', insert: [] }])) + .toThrow('literal config.plugins entry list') + }) + + it.each([ + { inserted: [row('same')] }, + { inserted: [{ id: 'nested', name: 'cordis:group', group: true, config: [row('same')] }] }, + ])('rejects inserted ids colliding with existing nested ids', ({ inserted }) => { + expect(() => applyProfilePatches([preset([ + { id: 'old-group', name: 'cordis:group', group: true, config: [row('same')] }, + ])], [{ preset: 'preset-standard', insert: inserted }])).toThrow('duplicate entry id "same"') + }) + + it('rejects duplicate ids inside an insertion and allows anonymous rows', () => { + expect(() => applyProfilePatches([preset()], [{ preset: 'preset-standard', insert: [row('same'), row('same')] }])) + .toThrow('duplicate entry id "same"') + const path = join(temp(), 'anonymous.patch.yml') + writeFileSync(path, JSON.stringify([{ preset: 'preset-standard', insert: [{ name: 'anonymous' }] }])) + expect(children(applyProfilePatches([preset()], loadOverlayPatches('test', path)))) + .toEqual([{ id: expect.any(String) as string, name: 'anonymous' }]) + }) + + it('keeps anonymous scoped identities stable by file and position without naming ordinary anonymous rows', () => { + const dir = temp() + const file = join(dir, 'contribution.patch.yml') + const content = JSON.stringify([ + { insert: [{ name: 'ordinary-anonymous' }] }, + { preset: 'preset-standard', insert: [ + { name: 'anonymous' }, + { id: 'explicit', name: 'cordis:group', group: true, config: [{ name: 'nested-anonymous' }] }, + ] }, + { preset: 'preset-standard', insert: [{ name: 'anonymous' }] }, + ]) + writeFileSync(file, content) + const first = loadOverlayPatches('test', file) + const second = loadOverlayPatches('test', file) + expect(second).toEqual(first) + expect(first[0]?.insert?.[0]?.id).toBeUndefined() + expect(first[1]?.insert?.[1]?.id).toBe('explicit') + const nested = first[1]?.insert?.[1]?.config as EntryOptions[] + const ids = [first[1]?.insert?.[0]?.id, nested[0]?.id, first[2]?.insert?.[0]?.id] + expect(ids.every(id => typeof id === 'string' && id.length > 0)).toBe(true) + expect(new Set(ids).size).toBe(3) + const other = join(dir, 'other.patch.yml') + writeFileSync(other, content) + expect(loadOverlayPatches('test', other)[1]?.insert?.[0]?.id).not.toBe(ids[0]) + expect(children(applyProfilePatches([preset()], first))).toEqual(children(applyProfilePatches([preset()], second))) + expect(Object.keys(first[1]!)).toEqual(['preset', 'insert']) + }) + + it.each([ + { initial: [], insert: [] }, + { initial: [preset()], insert: [{ id: 'group', name: 'cordis:group', group: true, config: [row('duplicate'), row('duplicate')] }] }, + { initial: [{ ...preset(), config: null }], insert: [] }, + ])('attributes scoped errors after cloning and flattening through composition, dumps, and boot', async ({ initial, insert }) => { + const dir = temp() + const path = join(dir, 'cordis.yml') + writeFileSync(path, JSON.stringify(initial)) + const patchFile = join(dir, 'third-party.patch.yml') + writeFileSync(patchFile, JSON.stringify([{ preset: 'preset-standard', insert }])) + const patches = cloneProfilePatches(loadOverlayPatches('test', patchFile)) + expect(() => composeEntries([[{ insert: initial }], patches])).toThrow(patchFile) + expect(() => renderConfigDump('test', path, [{ label: 'third-party', patches }])).toThrow(patchFile) + await expect(boot('test', path, patches)).rejects.toThrow(patchFile) + }) + + it('keeps anonymous rows from the initial preset beside newly inserted rows', () => { + const initial = load(JSON.stringify([{ ...preset(), config: { id: 'standard', plugins: [{ name: 'anonymous-base' }] } }])) as EntryOptions[] + expect(children(applyProfilePatches(initial, [{ preset: 'preset-standard', insert: [row('contributed')] }]))) + .toEqual([{ name: 'anonymous-base' }, row('contributed')]) + }) + + it('attributes errors from a caller warning sink to the loaded scoped operation', () => { + const path = join(temp(), 'warning.patch.yml') + writeFileSync(path, JSON.stringify([{ preset: 'preset-standard', id: 'absent', disabled: true }])) + expect(() => applyProfilePatches([preset()], loadOverlayPatches('test', path), () => { throw 'warning sink unavailable' })) + .toThrow(`warning sink unavailable in ${path}`) + }) + + it('attributes ordinary and scoped warnings to their own dump layers', () => { + const dir = temp() + const path = join(dir, 'cordis.yml') + writeFileSync(path, JSON.stringify([preset()])) + const warnings: string[] = [] + renderConfigDump('test', path, [ + { label: 'ordinary', patches: [{ id: 'missing', disabled: true }] }, + { label: 'scoped', patches: [{ preset: 'preset-standard', id: 'missing', disabled: true }] }, + ], message => warnings.push(message)) + expect(warnings).toEqual([ + 'test: [ordinary] patch: entry "missing" not found', + 'test: [scoped] preset "preset-standard": patch: entry "missing" not found', + ]) + }) +}) + +const capturePreset = { + [EntryGroup.key]: true, + async apply(ctx: Context, config: { plugins: EntryOptions[] }) { await ctx.plugin(Group, config.plugins) }, +} + +it('boots, dumps, and reloads a third-party relative module inside a contributed group', async () => { + const dir = temp() + const bundleDir = join(dir, 'third-party') + mkdirSync(bundleDir) + writeFileSync(join(bundleDir, 'relative.mjs'), [ + 'export function apply(ctx, config) {', + ' ctx.effect(() => ctx.reflect.provide("presetPatchResult", config.value))', + '}', + '', + ].join('\n')) + const path = join(dir, 'cordis.yml') + writeFileSync(path, JSON.stringify([preset()])) + const patchPath = join(bundleDir, 'cordis.patch.yml') + writeFileSync(patchPath, [ + '- preset: preset-standard', + ' insert:', + ' - id: nested', + ' name: cordis:group', + ' group: true', + ' config:', + ' - id: contributed', + ' name: ./relative.mjs', + ' config:', + ' value: !!js ctx.get("fixtureValue")', + '', + ].join('\n')) + const patches = loadOverlayPatches('test', patchPath) + const ctx = await boot('test', path, patches, (context) => { + context.loader.builtins['test-preset'] = capturePreset + context.provide('fixtureValue', 'from-child-context') + }) + onTestFinished(() => ctx.fiber.dispose()) + expect(ctx.get('presetPatchResult')).toBe('from-child-context') + const declared = [...ctx.loader.entries()].find(entry => entry.options.id === 'preset-standard')! + const dump = renderConfigDump('test', path, [{ label: 'third-party', patches }]) + expect(load(dump, { schema: entryListSchema })).toEqual([declared.options]) + expect(dump).toContain(pathToFileURL(join(bundleDir, 'relative.mjs')).href) + expect(dump).toContain('!!js ctx.get("fixtureValue")') + await reconcileProfilePatches(ctx, [...patches, { + preset: 'preset-standard', id: 'contributed', config: { value: 'user' }, + }], 'test') + expect(ctx.get('presetPatchResult')).toBe('user') + await reconcileProfilePatches(ctx, [], 'test') + expect(ctx.get('presetPatchResult')).toBeUndefined() + await reconcileProfilePatches(ctx, [{ preset: 'preset-standard', id: 'contributed', disabled: true }], 'test') + expect(ctx.get('presetPatchResult')).toBeUndefined() + await reconcileProfilePatches(ctx, patches, 'test') + expect(ctx.get('presetPatchResult')).toBe('from-child-context') +}) + +it('rejects a malformed root configuration before compiling its scoped patch', async () => { + const dir = temp() + const path = join(dir, 'cordis.yml') + writeFileSync(path, '{}\n') + await expect(boot('test', path, [{ preset: 'preset-standard', insert: [] }])) + .rejects.toThrow('must be a top-level array of entries') +}) + +it('leaves lower-level nonprofile compatibility callers in control of their native patches', async () => { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + const patches = [{ id: 'custom', disabled: true }] + expect(prepareProfilePatches(ctx, patches, pathToFileURL(temp()).href)).toBe(patches) +}) + +it('anchors relative modules in an inserted preset definition and its nested groups', () => { + const dir = temp() + const path = join(dir, 'cordis.patch.yml') + writeFileSync(path, JSON.stringify([{ insert: [{ + ...preset([{ id: 'group', name: 'cordis:group', group: true, config: [{ id: 'child', name: './child.mjs' }] }]), + name: '@deepseek-ai/dsh-agent-preset', + }] }])) + const patches = loadOverlayPatches('test', path) + expect(patches[0]?.insert?.[0]?.config).toMatchObject({ plugins: [{ config: [{ + name: pathToFileURL(join(dir, 'child.mjs')).href, + }] }] }) +}) diff --git a/packages/boot/app-boot/tests/profile-resolution-service.spec.ts b/packages/boot/app-boot/tests/profile-resolution-service.spec.ts index d066329d36..be57535fd4 100644 --- a/packages/boot/app-boot/tests/profile-resolution-service.spec.ts +++ b/packages/boot/app-boot/tests/profile-resolution-service.spec.ts @@ -1,6 +1,6 @@ /** Package metadata queries share the active runtime resolution. */ -import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs' +import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs' import { createRequire } from 'node:module' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' @@ -9,7 +9,7 @@ import { getEnvironmentData } from 'node:worker_threads' import { Context } from '@deepseek-ai/cordis' import { afterEach, describe, expect, it } from 'vitest' import { PluginPackages } from '../src/profile-resolution/service.ts' -import type { RuntimeResolution } from '../src/profile.ts' +import { createRuntimeResolution, initProfile, loadProfileDirectory, type RuntimeResolution } from '../src/profile.ts' const roots: string[] = [] const contexts: Context[] = [] @@ -53,6 +53,71 @@ function resolution( } describe('profile package metadata service', () => { + it('refreshes the installed resolution from its own profile and installation', async () => { + const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-package-service-refresh-'))) + roots.push(root) + const installAnchor = join(root, 'install', 'package.json') + file(installAnchor, JSON.stringify({ name: '@deepseek-ai/dsh', version: '0.0.0' })) + const profileDir = join(root, 'profiles', 'test') + initProfile(profileDir, ['extra']) + const bundleDir = join(profileDir, 'node_modules', 'extra') + const privateDir = join(bundleDir, 'node_modules', 'metadata-lib') + file(join(bundleDir, 'package.json'), JSON.stringify({ + name: 'extra', version: '1.0.0', dependencies: { 'metadata-lib': '1.0.0' }, dsh: { bundle: { patch: './cordis.patch.yml' } }, + })) + file(join(bundleDir, 'cordis.patch.yml'), '[]\n') + const parentURL = pathToFileURL(join(profileDir, 'caller.mjs')).href + const ctx = new Context() + contexts.push(ctx) + await ctx.plugin(PluginPackages, { + resolution: await createRuntimeResolution({ installAnchor, home: root, profile: loadProfileDirectory('dsh', profileDir, installAnchor) }), + }) + expect(ctx.pluginPackages.packageOf('metadata-lib', parentURL)).toBeUndefined() + + pkg(privateDir, '1.0.0') + const manifest = JSON.parse(readFileSync(join(profileDir, 'package.json'), 'utf8')) as Record + writeFileSync(join(profileDir, 'package.json'), JSON.stringify({ ...manifest, dependencies: { extra: '1.0.0' } })) + await ctx.pluginPackages.refresh() + + expect(ctx.pluginPackages.packageOf('metadata-lib', parentURL)).toMatchObject({ dir: privateDir, version: '1.0.0' }) + expect(createRequire(join(profileDir, 'caller.cjs')).resolve('metadata-lib')).toBe(join(privateDir, 'index.cjs')) + }) + + it('refreshes a computed resolution without a profile', async () => { + const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-package-service-installation-'))) + roots.push(root) + const installAnchor = join(root, 'install', 'package.json') + file(installAnchor, JSON.stringify({ name: 'installation', version: '1.0.0', dependencies: { 'metadata-lib': '*' } })) + const ctx = new Context() + contexts.push(ctx) + await ctx.plugin(PluginPackages, { resolution: await createRuntimeResolution({ installAnchor, home: root }) }) + const parentURL = pathToFileURL(join(root, 'profiles', 'test', 'caller.cjs')).href + expect(ctx.pluginPackages.packageOf('metadata-lib', parentURL)).toBeUndefined() + const packageDir = join(root, 'install', 'node_modules', 'metadata-lib') + pkg(packageDir, '1.0.0') + + await ctx.pluginPackages.refresh() + + expect(ctx.pluginPackages.packageOf('metadata-lib', parentURL)).toMatchObject({ dir: packageDir, version: '1.0.0' }) + expect(createRequire(parentURL).resolve('metadata-lib')).toBe(join(packageDir, 'index.cjs')) + }) + + it('rejects refreshing a plain-data or absent resolution', async () => { + const root = mkdtempSync(join(tmpdir(), 'dsh-package-service-plain-')) + roots.push(root) + const packageDir = join(root, 'lib') + const ctx = new Context() + contexts.push(ctx) + await ctx.plugin(PluginPackages, { + resolution: resolution(join(root, 'profiles'), join(root, 'profiles', 'test'), packageDir, pkg(packageDir, '1.0.0'), '1.0.0'), + }) + await expect(ctx.pluginPackages.refresh()).rejects.toThrow('plugin-packages: the installed runtime resolution cannot be recomputed') + const bare = new Context() + contexts.push(bare) + await bare.plugin(PluginPackages) + await expect(bare.pluginPackages.refresh()).rejects.toThrow('plugin-packages: the installed runtime resolution cannot be recomputed') + }) + it('reads translated metadata from the selected local package without importing its entry', async () => { const root = mkdtempSync(join(tmpdir(), 'dsh-localized-package-service-')) roots.push(root) diff --git a/packages/boot/app-boot/tests/profile-resolution.spec.ts b/packages/boot/app-boot/tests/profile-resolution.spec.ts index a36347661d..82cf24b0ea 100644 --- a/packages/boot/app-boot/tests/profile-resolution.spec.ts +++ b/packages/boot/app-boot/tests/profile-resolution.spec.ts @@ -1618,6 +1618,14 @@ describe('runtime resolution', { concurrent: false }, () => { : entry), }) }).toThrow(/requires a process restart/u) + expect(() => { + registration.replace({ + ...first, + entries: first.entries.map(entry => entry.name === '@deepseek-ai/dsh-core' + ? { ...entry, declarer: join(f.root, 'another-bundle', 'package.json') } + : entry), + }) + }).toThrow(/requires a process restart/u) expect(() => { registration.replace({ ...first, localPackageNames: ['@deepseek-ai/dsh-core'] }) }).toThrow(/requires a process restart/u) @@ -1629,7 +1637,6 @@ describe('runtime resolution', { concurrent: false }, () => { }).toThrow(/cannot change its profile scope/u) registration.replace({ ...first, localPackageNames: ['new-local'] }) registration.replace({ ...first, localPackageNames: ['new-local'] }) - expect(() => { registration.replace(first) }).toThrow(/removing local package/u) const linked = { name: 'linked-plugin', realPath: join(f.root, 'work', 'a') } const withLocal = { ...first, localPackageNames: ['new-local'] } registration.replace({ ...withLocal, linkedRoots: [linked] }) @@ -1643,6 +1650,104 @@ describe('runtime resolution', { concurrent: false }, () => { )).toBe(f.installed) }) + it('removes profile mappings and local names while retaining changed profile mappings as restarts', async () => { + const f = fixture() + const firstDir = join(f.root, 'bundle-a', 'node_modules', 'private-lib') + const secondDir = join(f.root, 'bundle-b', 'node_modules', 'private-lib') + pkg(firstDir, 'private-lib', 1) + pkg(secondDir, 'private-lib', 2) + const base = await resolutionOf(f) + const entry = { + name: 'private-lib', packageDir: firstDir, version: '1.0.0', + declarer: join(firstDir, 'package.json'), scope: 'profile' as const, + } + const first = { ...base, entries: [...base.entries, entry], localPackageNames: ['local-lib'] } + const registration = installRuntimeInterception(first) + registrations.push(registration) + const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href + expect(registration.packageDir('private-lib', parent)).toBe(firstDir) + expect(() => { + registration.replace({ ...first, entries: [...base.entries, { + ...entry, packageDir: secondDir, version: '2.0.0', declarer: join(secondDir, 'package.json'), + }] }) + }).toThrow(/replacing "private-lib" requires a process restart/u) + expect(registration.packageDir('private-lib', parent)).toBe(firstDir) + registration.replace(base) + expect(registration.packageDir('private-lib', parent)).toBeUndefined() + expect(() => resolveFrom('private-lib', parent)).toThrow(expect.objectContaining({ code: 'ERR_MODULE_NOT_FOUND' })) + registration.replace(first) + expect(registration.packageDir('private-lib', parent)).toBe(firstDir) + }) + + it('accepts another profile declarer for the same normalized package directory and version', async () => { + const f = fixture() + const shared = join(f.root, 'shared', 'node_modules', 'private-lib') + pkg(shared, 'private-lib', 1) + const firstAnchor = pkg(join(f.root, 'shared', 'bundle-a'), 'bundle-a', 1, { 'private-lib': '*' }) + const nextAnchor = pkg(join(f.root, 'shared', 'bundle-b'), 'bundle-b', 1, { 'private-lib': '*' }) + const alias = join(f.root, 'private-lib-alias') + symlinkSync(shared, alias, process.platform === 'win32' ? 'junction' : 'dir') + try { + const base = await resolutionOf(f) + const entry = { name: 'private-lib', packageDir: shared, declarer: firstAnchor, version: '1.0.0', scope: 'profile' as const } + const registration = installRuntimeInterception({ ...base, entries: [...base.entries, entry] }) + registrations.push(registration) + const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href + const require = createRequire(parent) + expect(registration.packageDir('private-lib', parent)).toBe(shared) + expect(require.resolve('private-lib')).toBe(join(shared, 'index.cjs')) + expect(resolveFrom('private-lib', parent)).toBe(pathToFileURL(join(shared, 'index.js')).href) + + registration.replace({ ...base, entries: [...base.entries, { ...entry, packageDir: alias, declarer: nextAnchor }] }) + + expect(registration.packageDir('private-lib', parent)).toBe(alias) + expect(require.resolve('private-lib')).toBe(join(shared, 'index.cjs')) + expect(resolveFrom('private-lib', parent)).toBe(pathToFileURL(join(shared, 'index.js')).href) + } finally { + unlinkSync(alias) + } + }) + + it.each(['directory', 'version', 'scope'] as const)('rejects a retained profile mapping with a different %s', async (change) => { + const f = fixture() + const packageDir = join(f.root, 'bundle', 'node_modules', 'private-lib') + pkg(packageDir, 'private-lib', 1) + const base = await resolutionOf(f) + const entry = { name: 'private-lib', packageDir, declarer: join(packageDir, 'package.json'), version: '1.0.0', scope: 'profile' as const } + const registration = installRuntimeInterception({ ...base, entries: [...base.entries, entry] }) + registrations.push(registration) + const successor = { + ...entry, + packageDir: change === 'directory' ? join(f.root, 'different') : entry.packageDir, + version: change === 'version' ? '2.0.0' : entry.version, + scope: change === 'scope' ? 'installation' as const : entry.scope, + } + + expect(() => { registration.replace({ ...base, entries: [...base.entries, successor] }) }) + .toThrow(/replacing "private-lib" requires a process restart/u) + expect(registration.packageDir('private-lib', pathToFileURL(join(f.profile.dir, 'entry.mjs')).href)).toBe(packageDir) + }) + + it('recomputes from the captured directory without retaining synthetic profile layers', async () => { + const f = fixture() + const profileDir = f.profile.dir + const bundleDir = join(f.root, 'synthetic-bundle') + pkg(bundleDir, 'synthetic-bundle', 1, { 'private-lib': '*' }) + pkg(join(bundleDir, 'node_modules', 'private-lib'), 'private-lib', 1) + f.profile.layers.push({ packageName: 'synthetic-bundle', packageDir: bundleDir, patchPaths: [], patches: [] }) + const first = await createRuntimeResolution({ installAnchor: f.installAnchor, home: f.root, profile: f.profile }) + expect(first.entries.some(entry => entry.name === 'private-lib')).toBe(true) + f.profile.dir = join(f.root, 'another-profile') + file(join(f.profile.dir, 'package.json'), JSON.stringify({ name: 'another-profile' })) + + const latest = await first.computeLatestResolution() + + expect(latest.profileDir).toBe(profileDir) + expect(latest.entries.some(entry => entry.name === 'private-lib')).toBe(false) + expect(first.profileDir).toBe(profileDir) + expect(first.entries.some(entry => entry.name === 'private-lib')).toBe(true) + }) + it('leaves non-package and out-of-scope metadata lookups to native resolution', async () => { const f = fixture() const outside = join(f.root, 'outside') diff --git a/packages/boot/app-boot/tests/profile.spec.ts b/packages/boot/app-boot/tests/profile.spec.ts index 2c0aebb8e5..fbd47c20bf 100644 --- a/packages/boot/app-boot/tests/profile.spec.ts +++ b/packages/boot/app-boot/tests/profile.spec.ts @@ -386,6 +386,23 @@ describe('loadProfile', () => { ]) }) + it('removes a retired bundle from an application-owned profile and keeps the rest of the manifest', () => { + const anchor = stageInstallation({ + '@deepseek-ai/dsh-base': { patch: '[]\n' }, + 'custom-bundle': { patch: '[]\n' }, + }) + const dir = join(tmp(), 'app-profile') + initProfile(dir, ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-experimental-schedule-bundle', 'custom-bundle']) + writeProfileManifest(dir, { ...readProfileManifest('t', dir), dependencies: { 'custom-bundle': '^1.0.0' } }) + const profile = loadProfileDirectory('t', dir, anchor) + expect(profile.skippedBundles).toEqual([]) + expect(profile.layers.map(layer => layer.packageName)).toEqual(['@deepseek-ai/dsh-base', 'custom-bundle']) + expect(readProfileManifest('t', dir)).toMatchObject({ + dependencies: { 'custom-bundle': '^1.0.0' }, + dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', 'custom-bundle'] } }, + }) + }) + it.each(['missing package', 'invalid manifest', 'not a bundle', 'missing patch', 'invalid patch'])( 'skips a bundle with %s, retains selections, and retries it on reread', async (failure) => { const anchor = stageInstallation({ @@ -497,6 +514,7 @@ describe('createRuntimeResolution', () => { expect(resolution.entries.find(entry => entry.name === 'dep-of-a')?.packageDir).toBe(join(modules, 'dep-of-a')) expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false) await expect(createRuntimeResolution({ installAnchor: anchor, home })).resolves.toEqual(resolution) + await expect(resolution.computeLatestResolution()).resolves.toEqual(resolution) }) it('keeps selected bundle closures profile-local without overriding installation packages', async () => { diff --git a/packages/boot/app-boot/tests/user-patches.spec.ts b/packages/boot/app-boot/tests/user-patches.spec.ts index 465f9d82c6..6db0154321 100644 --- a/packages/boot/app-boot/tests/user-patches.spec.ts +++ b/packages/boot/app-boot/tests/user-patches.spec.ts @@ -414,11 +414,13 @@ describe('profile reconciliation settlement', () => { onTestFinished(async () => { release.resolve(undefined); await ctx.fiber.dispose() }) let settled = false const operation = reconcileProfilePatches(ctx, [], NAME).then(() => { settled = true }) + onTestFinished(async () => { release.resolve(undefined); await operation }) await started.promise - expect([...ctx.loader.entries()].some(entry => entry.options.id === 'held')).toBe(false) + expect([...ctx.loader.entries()].find(entry => entry.options.id === 'held')?.fiber?.uid).toBeNull() expect(settled).toBe(false) release.resolve(undefined) await operation + expect([...ctx.loader.entries()].some(entry => entry.options.id === 'held')).toBe(false) expect(settled).toBe(true) }) }) diff --git a/packages/boot/cmdline/README.i18n.yaml b/packages/boot/cmdline/README.i18n.yaml index 18fa26d696..6af53897e6 100644 --- a/packages/boot/cmdline/README.i18n.yaml +++ b/packages/boot/cmdline/README.i18n.yaml @@ -39,8 +39,8 @@ en: 720b38c4c9afeb93 zh: b43160d7176e5799 /deepseek-ai-dsh-cmdline/understand-the-implementation/source-map: - en: 9867efd4c939077c - zh: 00247b7fe19bc2eb + en: a5f51481ca8e8751 + zh: c512261b9a3bca74 /deepseek-ai-dsh-cmdline/further-exploration: en: 6ea3c827c59aff4c zh: 2977b4cfa7f73942 diff --git a/packages/boot/cmdline/README.md b/packages/boot/cmdline/README.md index f0c6636b34..eb093bb5bf 100644 --- a/packages/boot/cmdline/README.md +++ b/packages/boot/cmdline/README.md @@ -97,7 +97,6 @@ The parse path is one small family with two owners: `provideCmdline` freezes the | File | Role | |---|---| | [`src/index.ts`](src/index.ts) | `CmdlineArgs`/`AppExit` types, `provideCmdline`, `parseCmdline`, commander exit/output routing | -| — | No runtime invariant companion is published; `cmdlineArgs` is an immutable launcher fact that any number of ordinary plugins may read. App-owned providers and consumers use normal Cordis service injection, whose missing dependencies are already reported by Loader settlement. | diff --git a/packages/boot/cmdline/README.zh.md b/packages/boot/cmdline/README.zh.md index b852c59ee4..c2ec9b4a61 100644 --- a/packages/boot/cmdline/README.zh.md +++ b/packages/boot/cmdline/README.zh.md @@ -97,7 +97,6 @@ kind: "package-library" | 文件 | 职责 | |---|---| | [`src/index.ts`](src/index.ts) | `CmdlineArgs`/`AppExit` 类型、`provideCmdline`、`parseCmdline`、commander 退出/输出路由 | -| — | 不发布运行时不变式配套条目;`cmdlineArgs` 是不可变的启动器事实,任意数量的普通插件都可以读取它。应用自有提供方与消费方使用普通 Cordis 服务注入;Loader 结算已会报告缺失的依赖。 | diff --git a/packages/boot/cmdline/package.json b/packages/boot/cmdline/package.json index 4859752fb5..c6ea6cb37a 100644 --- a/packages/boot/cmdline/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/boot/config-editor/README.i18n.yaml b/packages/boot/config-editor/README.i18n.yaml index 5e26f64cec..265864b3ec 100644 --- a/packages/boot/config-editor/README.i18n.yaml +++ b/packages/boot/config-editor/README.i18n.yaml @@ -18,8 +18,8 @@ en: 1eedaa5d72b48177 zh: 39abc1d927a692d2 /deepseek-ai-dsh-config-editor/understand-the-implementation: - en: b6ecd942daccc797 - zh: 2f4637d725034f96 + en: 6c0265c949db473c + zh: fe1849f32f2d111c /deepseek-ai-dsh-config-editor/further-exploration: en: 0c9e1d1401a865cc zh: 6130d52636e84676 diff --git a/packages/boot/config-editor/README.md b/packages/boot/config-editor/README.md index a7ae05067a..e67bdad23a 100644 --- a/packages/boot/config-editor/README.md +++ b/packages/boot/config-editor/README.md @@ -42,7 +42,7 @@ The [editor](src/index.ts) reconciles external changes before deriving a candida Configuration reads share one composition for entries without profile config overrides. Overridden entries are composed separately with only their own override removed; profile inserts and other entries' overrides remain effective. Returned configs are detached, and compositions are not cached between reads. -No invariant companion is published: the editor keeps no independent configuration projection. Loader and the persisted profile patch own configuration state. +Host configuration reads, edits, and resets ignore preset-scoped operations, including those whose child IDs match Host entry IDs. diff --git a/packages/boot/config-editor/README.zh.md b/packages/boot/config-editor/README.zh.md index a5bfe0faed..62abcab4bc 100644 --- a/packages/boot/config-editor/README.zh.md +++ b/packages/boot/config-editor/README.zh.md @@ -42,7 +42,7 @@ kind: "package-reference" 读取配置时,没有 profile config 覆盖项的条目共用一次组合结果。有覆盖项的条目分别组合,仅移除自身的覆盖项;profile 插入的条目和其他条目的覆盖项仍然生效。返回的配置为独立副本,组合结果不跨读取缓存。 -此包不发布 invariant companion:编辑器不维护独立配置投影。Loader 和持久化的 profile patch 拥有配置状态。 +Host 配置读取、编辑和重置均忽略预设作用域操作,包括子项 ID 与 Host 条目 ID 相同的操作。 diff --git a/packages/boot/config-editor/package.json b/packages/boot/config-editor/package.json index 831becbac5..346d9d4442 100644 --- a/packages/boot/config-editor/package.json +++ b/packages/boot/config-editor/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-config-editor", "description": "Persist plugin configuration through profile patches and Loader reconciliation", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -36,7 +36,7 @@ "dependencies": { "@deepseek-ai/dsh-atomic-write": "workspace:*", "yaml": "^2.8.1", - "js-yaml": "^4.1.0" + "js-yaml": "^4.3.2" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", diff --git a/packages/boot/config-editor/src/index.ts b/packages/boot/config-editor/src/index.ts index ffec879f40..4a3e5be473 100644 --- a/packages/boot/config-editor/src/index.ts +++ b/packages/boot/config-editor/src/index.ts @@ -3,11 +3,11 @@ import { readFile } from 'node:fs/promises' import { join } from 'node:path' import { isDeepStrictEqual } from 'node:util' import { Context, FiberState, Service, resolveConfig } from '@deepseek-ai/cordis' -import { entryListSchema, type PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' import yaml from 'js-yaml' import type { Entry, EntryOptions } from '@deepseek-ai/cordis-plugin-loader' import type {} from '@deepseek-ai/dsh-hmr' -import { composeEntries, loadProfileDirectory, readProfilePatches, reconcileProfilePatches } from '@deepseek-ai/dsh-app-boot' +import { composeEntries, loadProfileDirectory, profilePatchPreset, readProfilePatches, reconcileProfilePatches, type ProfilePatch } from '@deepseek-ai/dsh-app-boot' import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write' import { isMap, isSeq, parseDocument, Scalar, visit } from 'yaml' @@ -22,7 +22,7 @@ function flatten(rows: EntryOptions[]): EntryOptions[] { return rows.flatMap(row => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config as EntryOptions[]) : []]) } -/** Persist complete raw configs and apply them through the normal Loader path. */ +/** Persist Host entry configs without changing preset-scoped operations, then reconcile through Loader. */ export class ConfigEditor extends Service { static inject = ['loader', 'profileContext'] @@ -50,8 +50,9 @@ export class ConfigEditor extends Service { const profile = this.ownerContext.profileContext const loaded = loadProfileDirectory('dsh', profile.dir, profile.installAnchor) const entries = this.entries() + const hostPatches = loaded.patches.filter(patch => profilePatchPreset(patch) === undefined) // An own config key can replace inherited config even when its value is undefined. - const overridden = new Set(loaded.patches.filter(patch => patch.insert === undefined && Object.hasOwn(patch, 'config')).map(patch => patch.id)) + const overridden = new Set(hostPatches.filter(patch => patch.insert === undefined && Object.hasOwn(patch, 'config')).map(patch => patch.id)) const composed = new Map() if (entries.some(entry => !overridden.has(entry.options.id))) { for (const row of flatten(composeEntries([...loaded.layers.map(layer => layer.patches), loaded.patches]))) { @@ -63,7 +64,7 @@ export class ConfigEditor extends Service { inherited: overridden.has(entry.options.id) ? this.inherited(entry, loaded) : structuredClone((composed.get(entry.options.id)?.config ?? {}) as Record), - override: structuredClone((loaded.patches.findLast( + override: structuredClone((hostPatches.findLast( row => row.id === entry.options.id && row.config !== undefined, )?.config ?? {}) as Record), })) @@ -71,7 +72,7 @@ export class ConfigEditor extends Service { private inherited(entry: Entry, loaded: ReturnType): Record { const patches = loaded.patches.map((patch) => { - if (patch.id !== entry.options.id || patch.insert !== undefined) return patch + if (patch.id !== entry.options.id || patch.insert !== undefined || profilePatchPreset(patch) !== undefined) return patch const rest = { ...patch }; Reflect.deleteProperty(rest, 'config') return rest }) @@ -115,12 +116,12 @@ export class ConfigEditor extends Service { if (!isSeq(document.contents)) throw new Error('Profile patch must be a YAML sequence') document.contents.flow = false const index = document.contents.items.findLastIndex((item, index) => isMap(item) - && document.getIn([index, 'id']) === entry.options.id && !item.has('insert') + && document.getIn([index, 'id']) === entry.options.id && !item.has('insert') && !item.has('preset') && (!item.has('name') || document.getIn([index, 'name']) === entry.options.name)) if (isDeepStrictEqual(next, inherited)) { for (let index = document.contents.items.length - 1; index >= 0; index--) { const row = document.contents.items[index] - if (!isMap(row) || document.getIn([index, 'id']) !== entry.options.id || row.has('insert')) continue + if (!isMap(row) || document.getIn([index, 'id']) !== entry.options.id || row.has('insert') || row.has('preset')) continue row.delete('config') if (row.items.length === Number(row.has('id')) + Number(row.has('name'))) document.delete(index) } @@ -134,7 +135,7 @@ export class ConfigEditor extends Service { } }) const profile = this.ownerContext.profileContext const loaded = loadProfileDirectory('dsh', profile.dir, profile.installAnchor) - const patches = readProfilePatches('dsh', profile, { ...loaded, patches: yaml.load(String(document), { schema: entryListSchema }) as PatchOptions[] }) + const patches = readProfilePatches('dsh', profile, { ...loaded, patches: yaml.load(String(document), { schema: entryListSchema }) as ProfilePatch[] }) const effective = flatten(composeEntries([patches])).find(row => row.id === entry.options.id) if (!isDeepStrictEqual(effective?.config ?? {}, next)) { throw new Error(`Configuration for "${entry.options.id}" is overridden by a home patch or command-line overlay`) diff --git a/packages/boot/hmr/README.i18n.yaml b/packages/boot/hmr/README.i18n.yaml index 7e4bce6b3b..4f1eef45bf 100644 --- a/packages/boot/hmr/README.i18n.yaml +++ b/packages/boot/hmr/README.i18n.yaml @@ -21,8 +21,8 @@ en: 45ee194ad781d603 zh: b3d7fcd8ed73717c /deepseek-ai-dsh-hmr/understand-the-implementation: - en: 5c7ff789d5d6b653 - zh: 46ef96a031ac87bd + en: 227c3c99c3c4b597 + zh: 853b656340cb8bce /deepseek-ai-dsh-hmr/model-experience: en: 215e7ba838619b7b zh: a311da3843709f90 @@ -39,8 +39,8 @@ en: 89e0624fafd701fb zh: 19d36a33c1d9af9d /deepseek-ai-dsh-hmr/known-limitations-and-deferred-work: - en: 379bc2c72fd9e53e - zh: 1fff1e0e676bb161 + en: 0f46d4df1dab7f3b + zh: d172835159a20cce /deepseek-ai-dsh-hmr/known-limitations-and-deferred-work/dev-note: en: 7972e4b2005ec9a4 zh: 754c49ec538c164c diff --git a/packages/boot/hmr/README.md b/packages/boot/hmr/README.md index c4405697c6..513abc07b2 100644 --- a/packages/boot/hmr/README.md +++ b/packages/boot/hmr/README.md @@ -56,7 +56,15 @@ Chokidar options, including polling, retain their existing meaning. Exact config `watchConfig()` registers an awaited configuration handler. `runExclusive()` serializes configuration changes and Loader updates with automatic reloads and rejects nested transactions. Package installation and removal run outside this queue. HMR does not acquire the package writer lock; manifest notifications reload only when the ordered `dsh.profile.bundles` list changes. Profile and home patch changes also trigger recomposition. File events received during a configuration transaction are processed afterward. Include refreshes and profile reconciliation reach plugins through ordinary Loader entry updates; Loader commits volatile-only changes in place. -App-boot owns profile parsing and patch precedence. HMR reads the launcher’s data-only `profileContext`, registers the profile manifest and both user patch watches during initialization, and waits for application readiness before processing changes. Its disposal closes the watchers and cancels reloads waiting for startup. HMR also owns module-cache replacement and reload scheduling. Configuration watchers start outside the active transaction context so later notifications can enter the queue. No invariant companion is published because the queue and watcher registrations have no independent persisted projection. +App-boot owns profile parsing and patch precedence. HMR reads the launcher’s data-only `profileContext`, registers the profile manifest and both user patch watches during initialization, and waits for application readiness before processing changes. Its disposal closes the watchers and cancels reloads waiting for startup. Configuration watchers start outside the active transaction context so later notifications can enter the queue. + +A changed `package.json` outside `node_modules` expires that package's cached configuration. A configuration-only manifest triggers no module reload; a manifest imported as a JSON module retains dependency-driven reloads, and one in the host dependency graph retains host reloads. Dedicated configuration watches keep their existing ownership. A new package entry takes effect when its Loader entry restarts. + +Loader entries retain their raw import results. HMR retains every distinct imported namespace for each entry name and configuration-tree base URL; an uninitialized entry does not overwrite another entry's namespace. HMR matches those objects to cached ModuleJob namespaces instead of resolving entry names again. Names without an imported namespace and `cordis:` builtins keep name-based resolution. Source reloads therefore use the loaded modules after `exports` or `main` changes. + +Entries sharing a runtime are replaced together, with each entry receiving the implementation for its own module. Duplicate entries for one module retain their separate plugin instances. On replacement failure, HMR removes partially activated replacements and restores the previous modules and plugin implementations. A successful reload updates every matching entry record, including disabled entries; a failed replacement leaves those records unchanged. Entries that have never loaded remain uninitialized. + +[package-manifest.ts](src/package-manifest.ts) owns the Node internal interfaces for package reads, scope, type, and nearest-manifest lookups. Each HMR instance owns its configuration cache and restores its installed hooks on disposal. Manifest invalidation clears the ESM `ResolveCache` and CommonJS `_pathCache`: neither records every consulted manifest, and entries may point outside their package directory. CommonJS loads resolve requests with Node's default resolver before entering the native loader, bypassing old request aliases while preserving evaluated modules. Synchronous resolve hooks receive the resulting filenames. Ordinary module replacement remains HMR's separate operation. Watched module paths use Node ESM resolution's `realpathSync()` spelling, including Windows short directory names, so file events match the module cache. @@ -86,7 +94,11 @@ Reloading a contributing plugin can change later request prefixes; HMR does not - Module replacement requires Node loader internals. Framework dependency changes call the host-provided `loader.exit()` hook; HMR itself does not restart the process. -- Replacing installed package versions still requires a restart through Plugin Manager. The browser Client module graph retains its separate browser-side loading mechanism. +- Replacing installed package versions still requires a restart through Plugin Manager; manifests below `node_modules` keep Node's cached configuration. The browser Client module graph retains its separate browser-side loading mechanism. +- TSX loaders and ordinary application-created Workers are outside the supported package-cache refresh scope. +- CommonJS requests must be resolvable by Node's default resolver. Synchronous hooks may postprocess the resulting filenames, as Desktop's Office resolver does; hooks requiring the original request name or introducing virtual requests are unsupported. Builtins retain Node's original loading behavior. +- Future work: retain package invalidation across HMR disposal and replacement. Restoring the native readers can expose their older cached configuration again. +- If a shared runtime's replacement modules have different plugin callbacks, an instance without a Loader entry cannot be assigned to one of them. HMR reports an ambiguity error and rolls back the reload. - `watchConfig()` resolves when Chokidar reports readiness. On darwin, libuv starts the FSEvents stream afterwards on its own thread, so a write that lands within milliseconds of registration is not reported until the next event in that directory; edits made after startup are unaffected. ### Dev Note diff --git a/packages/boot/hmr/README.zh.md b/packages/boot/hmr/README.zh.md index 6c82c3db8e..ba99733ccf 100644 --- a/packages/boot/hmr/README.zh.md +++ b/packages/boot/hmr/README.zh.md @@ -56,7 +56,15 @@ Chokidar 选项(包括轮询)保持原有含义。精确配置监听同时 `watchConfig()` 注册会被等待的配置处理器。`runExclusive()` 将配置变更、Loader 更新与自动重载串行化,并拒绝嵌套事务。包安装和删除在该队列之外执行。HMR 不获取包操作写锁;manifest 通知仅在有序的 `dsh.profile.bundles` 列表变化时触发重载。profile 与 home patch 变化也会触发重新组合。配置事务期间收到的文件事件在事务结束后处理。 Include 刷新和 profile 重载都通过普通的 Loader 条目更新到达插件;仅 volatile 变化由 Loader 就地提交。 -App-boot 负责 profile 解析和 patch 优先级规则。HMR 读取启动器提供的纯数据 `profileContext`,在初始化时注册 profile manifest 和两份用户 patch 的监听,并等待应用就绪后处理更改。销毁 HMR 时会关闭监听器并取消等待启动的重载。HMR 也负责模块缓存替换和重载调度。配置监听器在当前事务上下文之外启动,使后续通知可以进入队列。不发布 invariant 伴生入口,因为队列和监听注册没有独立的持久投影。 +App-boot 负责 profile 解析和 patch 优先级规则。HMR 读取启动器提供的纯数据 `profileContext`,在初始化时注册 profile manifest 和两份用户 patch 的监听,并等待应用就绪后处理更改。销毁 HMR 时会关闭监听器并取消等待启动的重载。配置监听器在当前事务上下文之外启动,使后续通知可以进入队列。 + +`node_modules` 之外的 `package.json` 变化使该包的配置缓存失效。仅作为配置的 manifest 不触发模块重载;作为 JSON 模块导入的 manifest 保留依赖驱动重载,位于宿主依赖图中的 manifest 保留宿主重载。专属配置监听保留原有归属。新包入口在 Loader entry 重启时生效。 + +Loader entry 保留原始导入结果。HMR 为每个 entry 名称与配置树 base URL 保留所有不同的已导入命名空间;尚未初始化的 entry 不会覆盖其他 entry 的命名空间。HMR 将这些对象与缓存中 ModuleJob 的模块命名空间按对象身份匹配,不再重新解析 entry 名称。没有已导入命名空间的名称和 `cordis:` 内置入口仍按名称解析。因此 `exports` 或 `main` 改变后,源码重载仍使用已加载模块。 + +共享同一运行时的 entry 一起替换,每个 entry 使用其自身模块对应的实现。同一模块的重复 entry 仍保有各自的插件实例。替换失败时,HMR 清理已部分激活的替换实例,并恢复原来的模块和插件实现。重载成功后更新所有匹配的 entry 记录,包括已停用的 entry;替换失败时这些记录保持不变。从未加载过的 entry 仍保持未初始化状态。 + +[package-manifest.ts](src/package-manifest.ts) 封装 package 读取、scope、type 和最近 manifest 查询所需的 Node internal 接口。每个 HMR 实例拥有自己的配置缓存,销毁时恢复它安装的 hook。manifest 失效会清空 ESM `ResolveCache` 和 CommonJS `_pathCache`:它们都不记录查询过的全部 manifest,入口也可能指向包目录之外。CommonJS 加载先用 Node 默认 resolver 解析请求,再进入原生 loader,绕过旧请求别名并保留已求值模块。同步 resolve hook 收到的是解析后的文件名。普通模块替换仍由 HMR 独立处理。 被监听模块的路径沿用 Node ESM 解析所用的 `realpathSync()` 表示,包括 Windows 短目录名,使文件事件与模块缓存匹配。 @@ -86,7 +94,11 @@ App-boot 负责 profile 解析和 patch 优先级规则。HMR 读取启动器提 - 模块替换需要 Node loader 内部接口。框架依赖变化调用宿主提供的 `loader.exit()` 钩子;HMR 本身不重启进程。 -- 通过插件管理器替换已安装包版本仍需要重启。浏览器 Client 模块图保留独立的浏览器侧加载机制。 +- 通过插件管理器替换已安装包版本仍需要重启;`node_modules` 之下的 manifest 保留 Node 缓存的配置。浏览器 Client 模块图保留独立的浏览器侧加载机制。 +- TSX loader 和普通应用自行创建的 Worker 不在包缓存刷新支持范围内。 +- CommonJS 请求必须能由 Node 默认 resolver 解析。同步 hook 可以像 Desktop 的 Office resolver 一样处理解析后的文件名;依赖原始请求名或引入虚拟请求的 hook 不受支持。内置模块保留 Node 原有的加载行为。 +- 后续工作:跨 HMR 销毁和替换保留包配置失效状态。恢复原生 reader 后,其旧配置缓存可能重新可见。 +- 如果共享运行时的替换模块具有不同的插件回调,没有 Loader entry 的实例就无法确定应使用哪一个。HMR 会报告歧义错误并回滚此次重载。 - `watchConfig()` 在 Chokidar 报告就绪时 resolve。darwin 上 libuv 随后才在自己的线程启动 FSEvents 流,因此注册后数毫秒内落地的写入要等到该目录的下一个事件才会被报告;启动之后的编辑不受影响。 ### 开发备注 diff --git a/packages/boot/hmr/package.json b/packages/boot/hmr/package.json index 5e61624165..d957e864ba 100644 --- a/packages/boot/hmr/package.json +++ b/packages/boot/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-hmr", "description": "Coordinated module and profile configuration hot reload", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -38,13 +38,14 @@ "dependencies": { "@babel/code-frame": "^7.29.0", "chokidar": "^4.0.3", - "picomatch": "^4.0.3", + "node-addon-require-builtin": "^0.1.6", + "picomatch": "^4.0.7", "@deepseek-ai/schemastery": "workspace:~" }, "devDependencies": { "@types/babel__code-frame": "^7.27.0", - "@types/picomatch": "^3.0.2", - "esbuild": "^0.28.1", + "@types/picomatch": "^4.0.3", + "esbuild": "^0.28.2", "@deepseek-ai/cordis-plugin-timer": "workspace:~", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/cordis-plugin-loader": "workspace:~", diff --git a/packages/boot/hmr/src/index.ts b/packages/boot/hmr/src/index.ts index 045890f022..cad957dbda 100644 --- a/packages/boot/hmr/src/index.ts +++ b/packages/boot/hmr/src/index.ts @@ -1,15 +1,17 @@ /** Serialized module and profile-configuration reloads. */ +import assert from 'node:assert/strict' import { AsyncLocalStorage } from 'node:async_hooks' import { watchConfig as watchExactConfig } from './watch-config.ts' -import { Context, Inject, Service, type Plugin } from '@deepseek-ai/cordis' -import { ModuleLoader, type ModuleJob, type ResolveResult } from '@deepseek-ai/cordis-plugin-loader' +import { Context, Inject, Service, type Fiber, type Plugin } from '@deepseek-ai/cordis' +import type { ModuleLoader, ModuleJob, ResolveResult } from '@deepseek-ai/cordis-plugin-loader' import type { Include } from '@deepseek-ai/cordis-plugin-include' import { FSWatcher, watch, type ChokidarOptions } from 'chokidar' -import { basename, dirname, join, relative, resolve } from 'node:path' +import { basename, dirname, join, relative, resolve, sep } from 'node:path' import { readFileSync, realpathSync } from 'node:fs' import { readProfileManifest, readProfilePatches, reconcileProfilePatches, PROFILE_PATCH_FILENAME } from '@deepseek-ai/dsh-app-boot' import type {} from '@deepseek-ai/dsh-cmdline' import { handleError } from './error.ts' +import { PackageManifests } from './package-manifest.ts' import type {} from '@deepseek-ai/cordis-plugin-timer' import { fileURLToPath, pathToFileURL } from 'node:url' import { createRequire } from 'node:module' @@ -79,9 +81,129 @@ async function loadDependencies(job: ModuleJob, ignored = new Set()) { /** Module location and runtime retained during a replacement. */ export interface Reload { filename: string + /** Original namespaces and URLs of all entry modules participating in this runtime replacement. */ + modules: ReloadModules runtime?: Plugin.Runtime | undefined } +/** Entry names with distinct namespaces; iteration falls back to undefined only for names without a loaded namespace. */ +class EntryNamespaces { + private readonly names = new Map>() + + constructor(private internal: ModuleLoader, private moduleUrls: ReadonlyMap) {} + + static getModuleUrls(internal: ModuleLoader): Map { + const moduleUrls = new Map() + for (const url of internal.loadCache.keys()) { + const job = internal.loadCache.get(url) + if (!job?.module) continue + try { + moduleUrls.set(job.module.getNamespace(), url) + } catch (_error) { + // Node also caches unfinished jobs whose namespace is not available yet. + } + } + return moduleUrls + } + + add(name: string, moduleNamespace: unknown): this { + const namespaces = this.names.get(name) ?? new Set() + namespaces.add(moduleNamespace) + this.names.set(name, namespaces) + return this + } + + *[Symbol.iterator](): Generator<[name: string, moduleNamespace: unknown]> { + for (const [name, namespaces] of this.names) { + for (const moduleNamespace of namespaces) { + if (moduleNamespace === undefined && namespaces.size > 1) continue + yield [name, moduleNamespace] + } + } + } + + async resolve(name: string, baseUrl: string, moduleNamespace: unknown): Promise> { + if (moduleNamespace === undefined || name.startsWith('cordis:')) { + switch (this.internal.version) { + case 'v1': return await this.internal.resolve(name, baseUrl, {}) + case 'v2': return this.internal.resolveSync(baseUrl, { specifier: name, attributes: {} }) + } + } + const url = this.moduleUrls.get(moduleNamespace) + if (url === undefined) throw new Error(`HMR cannot locate the loaded module for ${name} from ${baseUrl}`) + return { url } + } +} + +type ReloadFiber = { + fiber: Fiber + entry: Fiber['entry'] + config: unknown + moduleNamespace: unknown +} + +type ReloadModule = { filename: string; moduleNamespace: unknown; plugin: Plugin } + +/** Entry modules belonging to one runtime, keyed by their original namespaces across replacement. */ +class ReloadModules { + private readonly modules = new Map() + + static include(reloads: ReadonlyMap, plugin: Plugin, job: ModuleJob, runtime: Plugin.Runtime | undefined): boolean { + let info = reloads.get(plugin) + if (info === undefined && runtime) info = [...reloads.values()].find(info => info.runtime === runtime) + if (info === undefined) return false + info.modules.add(job, plugin) + return true + } + + add(job: ModuleJob, plugin: Plugin): this { + assert(job.module, `HMR pending module is missing: ${job.url}`) + const moduleNamespace: unknown = job.module.getNamespace() + this.modules.set(moduleNamespace, { filename: job.url, moduleNamespace, plugin }) + return this + } + + async importRemaining(loader: Context['loader'], getOuterStack: () => string[], primary: ReloadModule): Promise { + const replacements = new ReloadModules() + for (const [originalNamespace, { filename }] of this.modules) { + if (filename === primary.filename) { + replacements.modules.set(originalNamespace, primary) + continue + } + const moduleNamespace: unknown = await loader.import(filename, getOuterStack) + const plugin = loader.unwrapExports(moduleNamespace) as Plugin + replacements.modules.set(originalNamespace, { filename, moduleNamespace, plugin }) + } + return replacements + } + + *getActiveImplementations(ctx: Context, plugin: Plugin, fibers: readonly ReloadFiber[]): Generator<[ReloadFiber, Plugin]> { + const callbacks = new Set([...this.modules.values()].map(value => ctx.registry.resolve(value.plugin))) + const implementations = fibers.map((previousFiber) => { + if (previousFiber.entry === undefined) return [previousFiber, plugin] as const + const replacement = this.modules.get(previousFiber.moduleNamespace) + const implementation = replacement ? replacement.plugin : ctx.loader.unwrapExports(previousFiber.moduleNamespace) as Plugin + callbacks.add(ctx.registry.resolve(implementation)) + return [previousFiber, implementation] as const + }) + for (const [previousFiber, implementation] of implementations) { + if (previousFiber.fiber.parent.fiber.uid === null) continue + if (previousFiber.entry === undefined && callbacks.size > 1) { + throw new Error('HMR replacement is ambiguous for a plugin instance without a Loader entry') + } + yield [previousFiber, implementation] + } + } + + updateEntries(loader: Context['loader']): void { + for (const [originalNamespace, { moduleNamespace }] of this.modules) { + for (const entry of loader.entries()) { + if (entry.moduleNamespace === originalNamespace) entry.moduleNamespace = moduleNamespace + } + } + } +} + /** Hot reload service with Cordis-compatible module configuration and events. */ @Inject('loader') @Inject('timer') @@ -131,6 +253,7 @@ class Hmr extends Service { private applicationReady: Promise = Promise.resolve(true) private closing = false private readonly configPaths = new Set() + private readonly manifests = new PackageManifests() /** Serialize a caller-owned mutation with all automatic reload paths. * @param operation Work that must not overlap module or configuration replacement. @@ -185,22 +308,13 @@ class Hmr extends Service { this.baseDir = fileURLToPath(new URL(config.base || '.', ctx.baseUrl)) } - /** - * Resolve a module specifier to a URL, compatible with Node 22-24. - */ - private async _resolve(specifier: string, parentURL: string, attrs: ImportAttributes): Promise { - switch (this.internal.version) { - case 'v1': return await this.internal.resolve(specifier, parentURL, attrs) - case 'v2': return this.internal.resolveSync(parentURL, { specifier, attributes: attrs }) - } - } - async* [Service.init](): AsyncGenerator<() => Promise, void, unknown> { yield async () => { this.closing = true await this.watcher?.close() // A configuration reload may remove its own HMR entry. if (!this.executing.getStore()) await this.operations + this.manifests.dispose() } const profile = this.ownerContext.get('profileContext') @@ -276,15 +390,20 @@ class Hmr extends Service { const filename = canonicalPath(resolve(watchBaseDir, path)) const configuredFilename = resolve(this.baseDir, path) if (this.configPaths.has(filename) || this.configPaths.has(configuredFilename)) continue + const isManifest = basename(filename) === 'package.json' && !filename.includes(`${sep}node_modules${sep}`) + if (isManifest) { + this.manifests.invalidate(filename) + } const url = pathToFileURL(filename).href if (this.externals.has(url)) { fullReload = true continue } - if (this.internal.loadCache.has(url)) { + if (this.internal.loadCache.has(url) || this.internal.loadCache.has(url, 'json')) { this.stashed.add(url) continue } + if (isManifest) continue const include = [...loader.entries()].map(entry => entry.subtree as Include | undefined) .find(tree => tree?.filename === filename || tree?.filename === configuredFilename) if (include !== undefined) includes.add(include) @@ -408,23 +527,24 @@ class Hmr extends Service { const pending = new Map() const reloads = new Map() + const moduleUrls = EntryNamespaces.getModuleUrls(this.internal) // Build a map of plugin names per config tree URL. // Plugin entry files are treated as atomic reload units. - const nameMap = new Map>() + const nameMap = new Map() for (const entry of this.ctx.loader.entries()) { const baseUrl = entry.parent.tree.ctx.baseUrl if (baseUrl === undefined) throw new Error('HMR entry tree has no base URL') - const names = nameMap.get(baseUrl) ?? new Set() - names.add(entry.options.name) + const names = nameMap.get(baseUrl) ?? new EntryNamespaces(this.internal, moduleUrls) + names.add(entry.options.name, entry.moduleNamespace) nameMap.set(baseUrl, names) } - // Resolve each plugin name to its file URL and check if it needs reload + // Find each plugin's loaded URL and check if it needs reload. for (const [baseUrl, names] of nameMap) { - for (const name of names) { + for (const [name, moduleNamespace] of names) { try { - const { url } = await this._resolve(name, baseUrl, {}) + const { url } = await names.resolve(name, baseUrl, moduleNamespace) if (this.declined.has(url)) continue const job = this.internal.loadCache.get(url) const plugin = this.ctx.loader.unwrapExports(job?.module?.getNamespace()) as Plugin | undefined @@ -443,15 +563,27 @@ class Hmr extends Service { const dependencies = [...await loadDependencies(job, this.declined)] this.declined.add(job.url) + if (dependencies.length === 0) { + pending.delete(job) + continue + } if (!dependencies.some(dep => this.accepted.has(dep))) continue dependencies.forEach(dep => this.accepted.add(dep)) + const runtime = this.ctx.registry.get(plugin) + if (ReloadModules.include(reloads, plugin, job, runtime)) continue reloads.set(plugin, { filename: job.url, - runtime: this.ctx.registry.get(plugin), + runtime, + modules: new ReloadModules().add(job, plugin), }) } + // Re-export roots of a replaced runtime must not retain bindings to its old modules. + for (const [job, plugin] of pending) { + if (ReloadModules.include(reloads, plugin, job, this.ctx.registry.get(plugin))) this.accepted.add(job.url) + } + /** * Clear module caches for all accepted files before re-importing. * @@ -502,14 +634,23 @@ class Hmr extends Service { fibers: [...info.runtime?.fibers ?? []].map((fiber) => { const entry = fiber.entry?.fiber?.uid === fiber.uid ? fiber.entry : undefined const config: unknown = entry === undefined ? fiber._config : entry.options.config - return { fiber, entry, config } + return { fiber, entry, config, moduleNamespace: entry?.moduleNamespace } }), })) - const attempts: Array<(typeof generations)[number] & { replacement: Plugin }> = [] + type ReloadAttempt = (typeof generations)[number] & { + replacement: Plugin + replacements: ReloadModules + activated: Fiber[] + } + const attempts: ReloadAttempt[] = [] try { for (const generation of generations) { - const replacement = this.ctx.loader.unwrapExports(await this.ctx.loader.import(generation.filename, this.getOuterStack)) as Plugin - attempts.push({ ...generation, replacement }) + const moduleNamespace: unknown = await this.ctx.loader.import(generation.filename, this.getOuterStack) + const replacement = this.ctx.loader.unwrapExports(moduleNamespace) as Plugin + const replacements = await generation.modules.importRemaining(this.ctx.loader, this.getOuterStack, { + filename: generation.filename, moduleNamespace, plugin: replacement, + }) + attempts.push({ ...generation, replacement, replacements, activated: [] }) } } catch (e) { handleError(this.ctx, e) @@ -517,11 +658,14 @@ class Hmr extends Service { throw e } - const reload = async (plugin: Plugin, fibers: (typeof generations)[number]['fibers']) => { - const activated = [] - for (const previousFiber of fibers) { - if (previousFiber.fiber.parent.fiber.uid === null) continue - const fiber = previousFiber.fiber.parent.registry.plugin(plugin, previousFiber.config, this.getOuterStack).ctx.fiber + const reload = async ( + plugin: Plugin, + fibers: (typeof generations)[number]['fibers'], + replacements = new ReloadModules(), + activated: Fiber[] = [], + ) => { + for (const [previousFiber, implementation] of replacements.getActiveImplementations(this.ctx, plugin, fibers)) { + const fiber = previousFiber.fiber.parent.registry.plugin(implementation, previousFiber.config, this.getOuterStack).ctx.fiber if (previousFiber.entry !== undefined) { fiber.entry = previousFiber.entry previousFiber.entry.fiber = fiber @@ -533,7 +677,7 @@ class Hmr extends Service { const removed = new Set() try { - for (const { previous: plugin, replacement, filename, runtime, fibers } of attempts) { + for (const { previous: plugin, replacement, filename, runtime, fibers, replacements, activated } of attempts) { if (!runtime) continue const path = relative(this.baseDir, fileURLToPath(filename)) @@ -547,7 +691,7 @@ class Hmr extends Service { } try { - await reload(replacement, fibers) + await reload(replacement, fibers, replacements, activated) this.ctx.logger.info('reload plugin at %C', path) } catch (err) { this.ctx.logger.warn('failed to reload plugin at %C', path) @@ -558,14 +702,17 @@ class Hmr extends Service { } catch (error) { // Restore caches and re-register old plugins after a replacement failure. rollback() - for (const { previous: plugin, replacement, fibers } of attempts) { + for (const { previous: plugin, fibers, activated } of attempts) { if (!removed.has(plugin)) continue try { - const replacementRuntime = this.ctx.registry.get(replacement) - const replacementFibers = [...replacementRuntime?.fibers ?? []] - this.ctx.registry.delete(replacement) - // Failed startup errors remain on fibers after their teardown finishes. - await Promise.allSettled(replacementFibers.map(fiber => fiber.await())) + for (const { runtime } of activated) { + if (runtime && this.ctx.registry.get(runtime.callback) === runtime) { + const replacementFibers = [...runtime.fibers] + this.ctx.registry.delete(runtime.callback) + // Failed startup errors remain on fibers after their teardown finishes. + await Promise.allSettled(replacementFibers.map(fiber => fiber.await())) + } + } await reload(plugin, fibers) } catch (err) { this.ctx.logger.warn(err) @@ -575,6 +722,9 @@ class Hmr extends Service { } await this.ctx.loader.await() + for (const { replacements } of attempts) { + replacements.updateEntries(this.ctx.loader) + } this.ctx.emit('hmr/reload', reloads) this.stashed = new Set() } diff --git a/packages/boot/hmr/src/package-manifest.ts b/packages/boot/hmr/src/package-manifest.ts new file mode 100644 index 0000000000..0c41fbb42c --- /dev/null +++ b/packages/boot/hmr/src/package-manifest.ts @@ -0,0 +1,330 @@ +/** Expire Node's cached package configuration for package directories whose manifest changed. */ +import { isUtf8 } from 'node:buffer' +import { readFileSync, realpathSync } from 'node:fs' +import { createRequire, isBuiltin } from 'node:module' +import { basename, dirname, join, sep, toNamespacedPath } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +/** Package fields Node's resolver consumes; exports and imports hold JSON values. */ +interface PackageConfig { + name?: string + main?: string + type: string + exports?: unknown + imports?: unknown +} + +/** Serialized fields returned by the native modules binding: name, main, type, imports, exports, manifest path. */ +type PackageTuple = [string | undefined, string | undefined, string, string | undefined, string | undefined, string] + +/** + * Methods of `internalBinding('modules')` that read package configuration through Node's native cache. Node's + * package reader, CommonJS loader, and ESM resolver and format detection look them up at each call. + */ +interface PackageBinding { + readPackageJSON(this: void, path: string, isEsm?: boolean, base?: string, specifier?: string): PackageTuple | undefined + getPackageScopeConfig(this: void, url: string): PackageTuple | string + getPackageType(this: void, url: string): string | undefined +} + +/** + * `internal/modules/package_json_reader` export that keeps its own nearest-manifest cache; the CommonJS loader calls it + * through the module object, so it is the only caller of the binding's nearest-manifest lookup. + */ +interface PackageReader { + getNearestParentPackageJSON(this: void, path: string): { data: PackageConfig; exists: boolean; path: string } | undefined +} + +/** Node's ESM resolution cache: a Map from parent URL to the serialized request keys resolved from it. */ +interface ResolveCacheClass { + prototype: { get: (this: Map, key: string, parent: string) => unknown } +} + +interface CommonJsModule { + _pathCache: Record + _resolveFilename(request: string, parent: NodeJS.Module | null | undefined, isMain?: boolean): string + _load(request: string, parent: NodeJS.Module | null | undefined, isMain?: boolean, ...options: unknown[]): unknown +} + +interface NativeAccess { + binding: PackageBinding + reader: PackageReader + ResolveCache: ResolveCacheClass + cjs: CommonJsModule + esmLoader: { resolveSync(...args: unknown[]): unknown } +} + +/** Snapshot own methods; each disposer restores its method only while its replacement is still installed. */ +function createReplaceHelper unknown }>(target: T) { + const rawImpl = { ...target } + return { + rawImpl, + replaceMethod(key: K, replacement: T[K]): () => void { + target[key] = replacement + return () => { if (target[key] === replacement) target[key] = rawImpl[key] } + }, + } +} + +/** Same lookup as the vendored Loader's `requireInternal`: `--expose-internals` first, then the builtin addon. */ +function requireInternal(id: string): unknown { + const require = createRequire(import.meta.url) + if (process.execArgv.includes('--expose-internals')) { + try { + return require(id) + } catch (_error) { + // Some internals, such as internal/bootstrap/realm, stay private under --expose-internals. + } + } + try { + return (require('node-addon-require-builtin') as { requireBuiltin(id: string): unknown }).requireBuiltin(id) + } catch (_error) { + // Without the addon, the caller reports the missing interface. + } +} + +function loadNodeInternals(): NativeAccess { + const realm = requireInternal('internal/bootstrap/realm') as { internalBinding(id: 'modules'): PackageBinding } + const esm = requireInternal('internal/modules/esm/loader') as { + getOrInitializeCascadedLoader(): NativeAccess['esmLoader'] + } + return { + binding: realm.internalBinding('modules'), + reader: requireInternal('internal/modules/package_json_reader') as PackageReader, + ResolveCache: (requireInternal('internal/modules/esm/module_map') as { ResolveCache: ResolveCacheClass }).ResolveCache, + cjs: (requireInternal('internal/modules/cjs/loader') as { Module: CommonJsModule }).Module, + esmLoader: esm.getOrInitializeCascadedLoader(), + } +} + +/** + * Read the ESM loader's resolution cache by observing one lookup through its prototype. + * The prototype method is restored before this returns. + */ +function captureEsmResolveCache(native: NativeAccess, parentURL: string): Map | undefined { + const prototype = native.ResolveCache.prototype + const original = prototype.get + const seen = new Set>() + prototype.get = function (this: Map, key: string, parent: string): unknown { + seen.add(this) + return Reflect.apply(original, this, [key, parent]) + } + try { + const loader = native.esmLoader + /* v8 ignore else -- CI coverage runs the Node 24 loader; the Node 22 matrix exercises the v1 signature */ + if ('getOrCreateModuleJob' in loader) loader.resolveSync(parentURL, { specifier: 'node:path', attributes: {} }) + else loader.resolveSync('node:path', parentURL, {}) + } finally { + prototype.get = original + } + return [...seen][0] +} + +interface Configuration { + data: PackageConfig + serialized: PackageTuple +} + +interface ImportContext { + base: string + specifier: string | undefined +} + +/** Package directories whose manifests are read from disk instead of Node's native cache. */ +export class PackageManifests { + private readonly directories = new Set() + private readonly configurations = new Map() + /** Real directory of each directory a lookup reached; linked consumers name a package by its link path. */ + private readonly realDirectories = new Map() + private native: NativeAccess | undefined + private rawBinding!: PackageBinding + private rawReader!: PackageReader + private rawCommonJs!: Pick + private readonly restorers: Array<() => void> = [] + + /** + * Expire cached configuration for one package directory. Loaded modules stay evaluated; later resolutions and + * format checks read the manifest on disk. + * @param manifest - absolute path of the changed `package.json`. + */ + invalidate(manifest: string): void { + const native = this.installPackageHooks() + const directory = dirname(manifest) + this.directories.add(directory) + this.configurations.clear() + this.realDirectories.clear() + // A package's entry may resolve outside its directory; cache keys do not record the manifests they consulted. + for (const key of Object.keys(native.cjs._pathCache)) Reflect.deleteProperty(native.cjs._pathCache, key) + const directoryURL = pathToFileURL(directory).href + '/' + const cache = captureEsmResolveCache(native, directoryURL) + /* v8 ignore else -- asynchronous loader hooks own a separate cache; loader-thread invalidation is deferred */ + if (cache !== undefined) Map.prototype.clear.call(cache) + } + + /** Restore every replaced Node method this instance installed. */ + dispose(): void { + for (const restore of this.restorers.splice(0).reverse()) restore() + this.native = undefined + this.realDirectories.clear() + } + + private isPathWithinDirectory(path: string, directory: string): boolean { + return path === directory || path.startsWith(join(directory, sep)) + } + + private isPathInInvalidatedDirectory(path: string): boolean { + const parent = dirname(path) + let real = this.realDirectories.get(parent) + if (real === undefined) { + try { real = realpathSync(parent) } catch (_error) { + // A missing directory keeps its configured spelling; it has no manifest to read through a link. + real = parent + } + this.realDirectories.set(parent, real) + } + const target = join(real, basename(path)) + for (const directory of this.directories) if (this.isPathWithinDirectory(target, directory)) return true + return false + } + + private isUrlInInvalidatedDirectory(url: string): boolean { + try { return this.isPathInInvalidatedDirectory(fileURLToPath(url)) } catch (_error) { + // Only file URLs can name an invalidated package directory. + return false + } + } + + private installPackageHooks(): NativeAccess { + if (this.native !== undefined) return this.native + const native = loadNodeInternals() + this.native = native + const binding = createReplaceHelper(native.binding) + const reader = createReplaceHelper(native.reader) + const commonJs = createReplaceHelper>(native.cjs) + this.rawBinding = binding.rawImpl + this.rawReader = reader.rawImpl + this.rawCommonJs = commonJs.rawImpl + this.restorers.push( + binding.replaceMethod('readPackageJSON', this.hookBindingReadPackageJSON.bind(this)), + binding.replaceMethod('getPackageScopeConfig', this.hookBindingGetPackageScopeConfig.bind(this)), + binding.replaceMethod('getPackageType', this.hookBindingGetPackageType.bind(this)), + reader.replaceMethod('getNearestParentPackageJSON', this.hookReaderGetNearestParentPackageJSON.bind(this)), + commonJs.replaceMethod('_load', this.hookCommonJsLoad.bind(this)), + ) + return native + } + + private hookCommonJsLoad(...[request, parent, isMain, ...options]: Parameters) { + const native = this.installPackageHooks() + // Absolute filenames bypass Node's private request cache without evicting evaluated modules. + const filename = request.startsWith('node:') || isBuiltin(request) + ? request : native.cjs._resolveFilename(request, parent, isMain) + return this.rawCommonJs._load.call(native.cjs, filename, parent, isMain, ...options) + } + + private hookBindingReadPackageJSON(path: string, isEsm?: boolean, base?: string, specifier?: string) { + return this.isPathInInvalidatedDirectory(path) + ? this.readCachedPackageConfig( + path, + isEsm && base !== undefined ? { base, specifier } : undefined, + )?.serialized + : this.rawBinding.readPackageJSON(path, isEsm, base, specifier) + } + + private hookBindingGetPackageScopeConfig(url: string) { + if (!this.isUrlInInvalidatedDirectory(url)) return this.rawBinding.getPackageScopeConfig(url) + const found = this.findNearestPackageConfig(fileURLToPath(url), true) + if (typeof found === 'string') return this.rawBinding.getPackageScopeConfig(pathToFileURL(found).href) + return found.configuration?.serialized ?? found.path + } + + private hookBindingGetPackageType(url: string) { + if (!this.isUrlInInvalidatedDirectory(url)) return this.rawBinding.getPackageType(url) + const found = this.findNearestPackageConfig(fileURLToPath(url), true) + return typeof found === 'string' + ? this.rawBinding.getPackageType(pathToFileURL(found).href) + : found.configuration?.data.type + } + + private hookReaderGetNearestParentPackageJSON(path: string) { + if (!this.isPathInInvalidatedDirectory(path)) return this.rawReader.getNearestParentPackageJSON(path) + const found = this.findNearestPackageConfig(path, false) + if (typeof found === 'string') return this.rawReader.getNearestParentPackageJSON(found) + // A lookup that stops without a manifest keeps the running Node version's own absent result. + if (found.configuration === undefined) return this.rawReader.getNearestParentPackageJSON(path) + return { data: found.configuration.data, exists: true, path: found.path } + } + + private readCachedPackageConfig(path: string, context?: ImportContext): Configuration | undefined { + if (!this.configurations.has(path)) this.configurations.set(path, this.readPackageConfigFromDisk(path, context)) + return this.configurations.get(path) + } + + /** A string names the first manifest outside the invalidated directories; Node answers for it. */ + private findNearestPackageConfig(path: string, includeRoot: boolean): { path: string; configuration?: Configuration } | string { + let directory = dirname(path) + while (true) { + const manifest = join(directory, 'package.json') + const parent = dirname(directory) + if (basename(directory) === 'node_modules' || (!includeRoot && parent === directory)) return { path: manifest } + if (!this.isPathInInvalidatedDirectory(manifest)) return manifest + const configuration = this.readCachedPackageConfig(manifest) + if (configuration !== undefined) return { path: toNamespacedPath(manifest), configuration } + /* v8 ignore next -- root-scope queries run in an isolated child to contain non-termination */ + if (parent === directory) return { path: manifest } + directory = parent + } + } + + /** @param context - optional ESM import details used only when reporting invalid package configuration. */ + private readPackageConfigFromDisk(path: string, context?: ImportContext): Configuration | undefined { + let bytes: Buffer + try { bytes = readFileSync(path) } catch (_error) { + // Node reports a missing manifest as absent configuration. + return undefined + } + const throwInvalidPackageConfig = (): never => { + // The native reader throws a plain Error carrying only this code. + const base = context?.base + const importing = base === undefined ? '' + : ` while importing "${context?.specifier}" from ${base.startsWith('file:') ? fileURLToPath(base) : base}` + throw Object.assign(new Error(`Invalid package config ${toNamespacedPath(path)}${importing}.`), { code: 'ERR_INVALID_PACKAGE_CONFIG' }) + } + if (!isUtf8(bytes)) return throwInvalidPackageConfig() + const source = bytes.toString('utf8') + let parsed: unknown + try { parsed = JSON.parse(source.charCodeAt(0) === 0xfeff ? source.slice(1) : source) } catch (_error) { + return throwInvalidPackageConfig() + } + if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return throwInvalidPackageConfig() + const fields = parsed as Record + if (fields.name !== undefined && typeof fields.name !== 'string') return throwInvalidPackageConfig() + if (fields.type !== undefined && typeof fields.type !== 'string') return throwInvalidPackageConfig() + for (const key of ['name', 'type', 'exports', 'imports']) { + const value = fields[key] + if (typeof value === 'string' && !value.isWellFormed()) return throwInvalidPackageConfig() + } + const normalizePackageJsonField = (value: unknown): unknown => + value !== null && (typeof value === 'string' || typeof value === 'object') ? value : undefined + const data: PackageConfig = { + ...typeof fields.name === 'string' ? { name: fields.name } : {}, + ...typeof fields.main === 'string' && fields.main.isWellFormed() ? { main: fields.main } : {}, + type: fields.type === 'module' || fields.type === 'commonjs' ? fields.type : 'none', + exports: normalizePackageJsonField(fields.exports), + imports: normalizePackageJsonField(fields.imports), + } + const serializePackageJsonField = (value: unknown): string | undefined => + value === undefined ? undefined : typeof value === 'string' ? value : JSON.stringify(value) + return { + data, + serialized: [ + data.name, + data.main, + data.type, + serializePackageJsonField(data.imports), + serializePackageJsonField(data.exports), + toNamespacedPath(path), + ], + } + } +} diff --git a/packages/boot/hmr/src/watch-config.ts b/packages/boot/hmr/src/watch-config.ts index 6a03be2d76..6c62610dec 100644 --- a/packages/boot/hmr/src/watch-config.ts +++ b/packages/boot/hmr/src/watch-config.ts @@ -73,12 +73,24 @@ export async function watchConfig( watcher.on('unlink', onChange) const ready = Promise.withResolvers() let pending = true + let disposed = false watcher.once('ready', () => { pending = false; ready.resolve() }) - watcher.on('error', (error) => { + const onError = (error: unknown): void => { + // A watcher error after disposal is not actionable: this registration's + // path is gone and no refresh will run. + if (disposed) return if (pending) { pending = false; ready.reject(error) } else { ctx.logger.warn(error) } - }) + } + watcher.on('error', onError) const dispose = async () => { - await watcher.close() + disposed = true + // `close()` drops every listener but leaves a scheduled write-settle poll; + // its straggler stats a file this teardown is deleting, which Windows + // reports as EPERM, and an 'error' emission with no listener is rethrown + // as an uncaught exception. Re-attach across the close. + const closing = watcher.close() + watcher.on('error', onError) + await closing paths.delete(target.filename) if (!inTransaction()) await running } diff --git a/packages/boot/hmr/tests/fixtures/package-root-scope.mjs b/packages/boot/hmr/tests/fixtures/package-root-scope.mjs new file mode 100644 index 0000000000..76e637424c --- /dev/null +++ b/packages/boot/hmr/tests/fixtures/package-root-scope.mjs @@ -0,0 +1,22 @@ +/** Isolate root-scope traversal; the filesystem root is read but never modified. */ +import assert from 'node:assert/strict' +import { createRequire } from 'node:module' +import { join, parse } from 'node:path' +import { pathToFileURL } from 'node:url' +import { PackageManifests } from '../../src/package-manifest.ts' + +const [root] = process.argv.slice(2) +const reader = createRequire(import.meta.url)('internal/modules/package_json_reader') +const source = join(root, 'missing', 'entry.js') +const url = pathToFileURL(source).href +const expectedScope = reader.getPackageScopeConfig(url) +const expectedNearest = reader.getNearestParentPackageJSON(source) +const manifests = new PackageManifests() +try { + await manifests.invalidate(join(parse(root).root, 'package.json')) + assert.deepEqual(reader.getPackageScopeConfig(url), expectedScope) + assert.deepEqual(reader.getNearestParentPackageJSON(source), expectedNearest) + process.stdout.write('scope traversal completed\n') +} finally { + manifests.dispose() +} diff --git a/packages/boot/hmr/tests/modules.spec.ts b/packages/boot/hmr/tests/modules.spec.ts index 93b96a007c..5cf710ea07 100644 --- a/packages/boot/hmr/tests/modules.spec.ts +++ b/packages/boot/hmr/tests/modules.spec.ts @@ -58,6 +58,42 @@ async function fixture(version: 'v1' | 'v2' = 'v2') { return { ctx, cache, imports, imported, reload, module, url, resolve } } +it('does not reactivate a runtime removed during dependency traversal', async () => { + const { ctx, module, imports, imported, reload } = await fixture() + const dependency = module('dependency.mjs') + const original = { apply() {} } + const replacement = { apply: vi.fn() } + const job = module('plugin.mjs', original, [dependency]) + const entry = ctx.loader.resolve(await ctx.loader.create({ name: job.url })) + await ctx.loader.await() + const fiber = entry.fiber! + const reached = Promise.withResolvers() + const linked = Promise.withResolvers() + Object.defineProperty(job, 'linked', { configurable: true, get() { + reached.resolve(undefined) + return linked.promise + } }) + onTestFinished(() => { linked.resolve([dependency]) }) + imports.set(job.url, replacement) + imported.mockClear() + reload.stashed.add(dependency.url) + const operation = reload.partialReload() + try { + await reached.promise + ctx.registry.delete(original) + await fiber.await() + expect(ctx.registry.get(original)).toBeUndefined() + linked.resolve([dependency]) + await operation + expect(imported).toHaveBeenCalledWith(job.url, expect.any(Function)) + expect(replacement.apply).not.toHaveBeenCalled() + expect(ctx.registry.get(replacement)).toBeUndefined() + } finally { + linked.resolve([dependency]) + await operation + } +}) + it.each(['v1', 'v2'] as const)('replaces a %s module and retains the latest Loader entry configuration', async (version) => { const { ctx, module, imports, reload } = await fixture(version) const mounted: string[] = [] @@ -166,6 +202,34 @@ it('does not replace a plugin whose dependencies have not changed', async () => expect(imported).not.toHaveBeenCalled() }) +it('keeps a node_modules entry cached when it shares a runtime with a replaced source entry', async () => { + const { ctx, module, imports, imported, cache, reload } = await fixture() + const original = { apply: vi.fn() } + const replacement = { apply: vi.fn() } + const source = module('source.mjs', original) + const installedNamespace = { apply: original.apply } + const installed = module('node_modules/addon/index.mjs', installedNamespace) + await ctx.loader.root.update([ + { id: 'source', name: source.url }, + { id: 'installed', name: installed.url }, + ]) + await ctx.loader.await() + expect(ctx.loader.resolve('source').fiber?.runtime).toBe(ctx.loader.resolve('installed').fiber?.runtime) + imports.set(source.url, replacement) + imported.mockClear() + reload.stashed.add(source.url) + + await reload.partialReload() + + expect(imported).toHaveBeenCalledExactlyOnceWith(source.url, expect.any(Function)) + expect(replacement.apply).toHaveBeenCalledOnce() + expect(cache.get(installed.url)).toBe(installed) + expect(ctx.loader.resolve('installed').moduleNamespace).toBe(installedNamespace) + expect(ctx.loader.resolve('installed').fiber?.runtime?.callback).toBe(original.apply) + expect(ctx.loader.resolve('source').fiber?.runtime?.callback).toBe(replacement.apply) + expect(reload.accepted).not.toContain(installed.url) +}) + it('replaces a plugin when a linked dependency changes, excluding framework modules', async () => { const { ctx, module, imports, reload } = await fixture() const dependency = module('dependency.mjs') @@ -237,14 +301,15 @@ it('leaves disposed child instances to their replacing parent', async () => { expect(entry.fiber?._config).toEqual({ value: 'entry' }) }) -it('invalidates a disabled module without activating it', async () => { - const { ctx, module, imports, reload } = await fixture() +it.each(['v1', 'v2'] as const)('invalidates a disabled %s module without activating it', async (version) => { + const { ctx, module, imports, resolve, reload } = await fixture(version) const job = module('disabled.mjs', { apply() {} }) const id = await ctx.loader.create({ name: job.url, disabled: true }) const apply = vi.fn() imports.set(job.url, { apply }) reload.stashed.add(job.url) await reload.partialReload() + expect(resolve.mock.calls).toEqual(version === 'v1' ? [[job.url, ctx.baseUrl, {}]] : [[job.url]]) expect(ctx.loader.resolve(id).fiber).toBeUndefined() expect(apply).not.toHaveBeenCalled() }) @@ -263,6 +328,45 @@ it('does not disturb an unchanged runtime after an earlier replacement fails', a expect(untouched).toHaveBeenCalledOnce() }) +it('cleans up a shared replacement runtime once when a later plugin activation fails', async () => { + const { ctx, module, imports, cache, reload } = await fixture() + const mounted: string[] = [] + const disposed: string[] = [] + const original = { apply(_ctx: Context, config: { label: string }) { mounted.push(config.label) } } + const replacement = { apply(ctx: Context, config: { label: string }) { + ctx.effect(() => () => { disposed.push(config.label) }) + } } + const first = module('shared.mjs', original) + const second = module('failing.mjs', { apply() {} }) + await ctx.loader.root.update([ + { id: 'one', name: first.url, config: { label: 'one' } }, + { id: 'two', name: first.url, config: { label: 'two' } }, + { id: 'failing', name: second.url }, + ]) + await ctx.loader.await() + imports.set(first.url, replacement) + imports.set(second.url, { apply() { throw new Error('later activation failed') } }) + const remove = vi.spyOn(ctx.registry, 'delete') + const event = vi.fn() + ctx.on('hmr/reload', event) + reload.stashed.add(first.url) + reload.stashed.add(second.url) + + await expect(reload.partialReload()).rejects.toThrow('later activation failed') + + expect(disposed.sort()).toEqual(['one', 'two']) + expect(mounted).toEqual(['one', 'two', 'one', 'two']) + expect(remove.mock.calls.filter(([plugin]) => plugin === replacement.apply)).toHaveLength(1) + expect(ctx.registry.get(replacement)).toBeUndefined() + const restored = ctx.registry.get(original) + expect([...restored!.fibers]).toHaveLength(2) + expect(ctx.loader.resolve('one').fiber?.runtime).toBe(restored) + expect(ctx.loader.resolve('two').fiber?.runtime).toBe(restored) + expect(cache.get(first.url)).toBe(first) + expect(cache.get(second.url)).toBe(second) + expect(event).not.toHaveBeenCalled() +}) + it('restores the prior plugin when the replacement does not export a plugin', async () => { const { ctx, module, imported, reload } = await fixture() const original = { apply: vi.fn() } @@ -292,7 +396,7 @@ it('ignores framework entries and reports unresolved entry modules', async () => const framework = module('framework.mjs', { apply() {} }) const uncached = module('uncached.mjs', { apply() {} }) await ctx.loader.create({ name: framework.url }) - await ctx.loader.create({ name: uncached.url }) + await ctx.loader.create({ name: uncached.url, disabled: true }) await ctx.loader.await() reload.externals.add(framework.url) resolve.mockImplementation((name) => { diff --git a/packages/boot/hmr/tests/package-manifest-dispatch.spec.ts b/packages/boot/hmr/tests/package-manifest-dispatch.spec.ts new file mode 100644 index 0000000000..3a4f518bf8 --- /dev/null +++ b/packages/boot/hmr/tests/package-manifest-dispatch.spec.ts @@ -0,0 +1,669 @@ +/** HMR dispatch of package manifest changes through a real Loader and a controlled watcher. */ +import { randomUUID } from 'node:crypto' +import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { Context, type Plugin } from '@deepseek-ai/cordis' +import Loader from '@deepseek-ai/cordis-plugin-loader' +import Timer from '@deepseek-ai/cordis-plugin-timer' +import { FSWatcher, type ChokidarOptions } from 'chokidar' +import { expect, it, onTestFinished, vi } from 'vitest' +import Hmr from '../src/index.ts' + +const watchers = vi.hoisted(() => [] as FSWatcher[]) +// File notifications are delivered only when a test emits them. +vi.mock('chokidar', async (original) => { + const actual = await original() + return { ...actual, watch: (_paths: string | string[], options: ChokidarOptions = {}) => { + const watcher = new actual.FSWatcher(options) + watchers.push(watcher) + queueMicrotask(() => watcher.emit('ready')) + return watcher + } } +}) + +function file(path: string, source: string): void { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, source) +} + +function plugin(marker: string): string { + return `import { value } from '#dep'; +export function apply(ctx) { + ctx.get('manifestTrace').push('start:${marker}:' + value); + ctx.effect(() => () => { ctx.get('manifestTrace').push('stop:${marker}'); }); +} +` +} + +function manifest(entry: string, dep: string): string { + return JSON.stringify({ name: 'addon', type: 'module', exports: `./${entry}.mjs`, imports: { '#dep': `./${dep}.mjs` } }) +} + +async function fixture(source = plugin('a'), options: { lateHmr?: boolean; packageJson?: string } = {}) { + const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-hmr-manifest-dispatch-'))) + const ctx = new Context() + onTestFinished(async () => { + try { await ctx.fiber.dispose() } finally { rmSync(root, { recursive: true, force: true }) } + }) + const pkg = join(root, 'plugins', 'addon') + file(join(pkg, 'package.json'), options.packageJson ?? manifest('a', 'dep-1')) + file(join(pkg, 'a.mjs'), source) + file(join(pkg, 'b.mjs'), plugin('b')) + file(join(pkg, 'dep-1.mjs'), 'export const value = 1') + file(join(pkg, 'dep-2.mjs'), 'export const value = 2') + file(join(root, 'app', 'package.json'), '{"type":"module"}') + mkdirSync(join(root, 'app', 'node_modules'), { recursive: true }) + symlinkSync(pkg, join(root, 'app', 'node_modules', 'addon'), process.platform === 'win32' ? 'junction' : 'dir') + const trace: string[] = [] + ctx.baseUrl = pathToFileURL(join(root, 'app')).href + '/' + ctx.provide('manifestTrace', trace) + await ctx.plugin(Loader) + await ctx.plugin(Timer) + const startHmr = async () => { + const start = watchers.length + const fiber = (await ctx.plugin(Hmr, { root: ['../plugins'], ignored: [], debounce: 0 })).ctx.fiber + return { fiber, watcher: watchers.slice(start).at(-1)! } + } + const initialHmr = options.lateHmr ? undefined : await startHmr() + const entry = ctx.loader.resolve(await ctx.loader.create({ name: 'addon' })) + await ctx.loader.await() + let hmr = initialHmr ?? await startHmr() + const warn = vi.spyOn(ctx.logger, 'warn') + onTestFinished(() => { warn.mockRestore() }) + const restartHmr = async () => { + await hmr.fiber.dispose() + hmr = await startHmr() + } + const emit = async (...paths: string[]) => { + const dispatch = vi.spyOn(ctx.hmr, 'runExclusive') + try { + for (const path of paths) hmr.watcher.emit('change', path) + await vi.waitFor(() => { expect(dispatch).toHaveBeenCalledOnce() }) + const result = dispatch.mock.results[0]! + expect(result.type).toBe('return') + await result.value + } finally { + dispatch.mockRestore() + } + } + return { ctx, pkg, trace, emit, entry, warn, restartHmr } +} + +it('does not reload for a manifest alone and resolves the new entry when the Loader entry restarts', async () => { + const f = await fixture() + expect(f.trace).toEqual(['start:a:1']) + file(join(f.pkg, 'package.json'), manifest('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + expect(f.trace).toEqual(['start:a:1']) + + await f.entry.update({ disabled: true }) + await f.entry.update({ disabled: false }) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:b:2']) +}) + +const jsonPlugin = `import manifest from './package.json' with { type: 'json' }; +export function apply(ctx) { + ctx.get('manifestTrace').push('start:json:' + manifest.imports['#dep']); + ctx.effect(() => () => { ctx.get('manifestTrace').push('stop:json'); }); +} +` + +it('reloads a plugin that imports its changed manifest as a JSON module', async () => { + const f = await fixture(jsonPlugin) + expect(f.trace).toEqual(['start:json:./dep-1.mjs']) + file(join(f.pkg, 'package.json'), manifest('a', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + expect(f.trace).toEqual(['start:json:./dep-1.mjs', 'stop:json', 'start:json:./dep-2.mjs']) +}) + +it('requests a host reload for a changed manifest in the host module graph', async () => { + const f = await fixture(jsonPlugin) + const filename = join(f.pkg, 'package.json') + const externals = Reflect.get(f.ctx.hmr, 'externals') as Set + externals.add(pathToFileURL(filename).href) + const exit = vi.spyOn(f.ctx.loader, 'exit').mockImplementation(() => {}) + onTestFinished(() => { exit.mockRestore() }) + file(filename, manifest('a', 'dep-2')) + await f.emit(filename) + expect(exit).toHaveBeenCalledOnce() + expect(f.trace).toEqual(['start:json:./dep-1.mjs']) +}) + +it('leaves a configuration-owned manifest to its dedicated watcher', async () => { + const f = await fixture(jsonPlugin) + const filename = join(f.pkg, 'package.json') + const dispose = await f.ctx.hmr.watchConfig(filename, async () => {}) + try { + file(filename, manifest('a', 'dep-2')) + await f.emit(filename) + expect(f.trace).toEqual(['start:json:./dep-1.mjs']) + } finally { + await dispose() + } +}) + +it('resolves package imports from the changed manifest when a source change reloads the plugin', async () => { + const f = await fixture() + file(join(f.pkg, 'package.json'), manifest('a', 'dep-2')) + file(join(f.pkg, 'a.mjs'), `${plugin('a')}// edited\n`) + await f.emit(join(f.pkg, 'a.mjs'), join(f.pkg, 'package.json')) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a:2']) +}) + +it('keeps cached package imports when only source changes', async () => { + const f = await fixture() + file(join(f.pkg, 'package.json'), manifest('a', 'dep-2')) + file(join(f.pkg, 'a.mjs'), `${plugin('a')}// edited\n`) + await f.emit(join(f.pkg, 'a.mjs')) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a:1']) +}) + +it('leaves manifests below node_modules to their package lifecycle', async () => { + const f = await fixture() + const installed = join(dirname(f.pkg), 'node_modules', 'dep', 'package.json') + file(installed, '{"name":"dep"}') + const invalidate = vi.spyOn(Reflect.get(f.ctx.hmr, 'manifests') as { invalidate(path: string): void }, 'invalidate') + onTestFinished(() => { invalidate.mockRestore() }) + await f.emit(installed) + expect(invalidate).not.toHaveBeenCalled() +}) + +it.each(['exports', 'main'] as const)('keeps reloading the loaded entry after %s moves and selects the new entry on restart', async (field) => { + const packageJson = (entry: string, dep: string) => JSON.stringify({ + name: 'addon', type: 'module', [field]: `./${entry}.mjs`, imports: { '#dep': `./${dep}.mjs` }, + }) + const f = await fixture(plugin('a'), { packageJson: packageJson('a', 'dep-1') }) + const originalNamespace = f.entry.moduleNamespace + file(join(f.pkg, 'package.json'), packageJson('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + expect(f.trace).toEqual(['start:a:1']) + expect(f.entry.moduleNamespace).toBe(originalNamespace) + file(join(f.pkg, 'a.mjs'), plugin('a-edited')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:2']) + expect(f.entry.moduleNamespace).not.toBe(originalNamespace) + expect(f.entry.moduleNamespace).toBe(await f.ctx.loader.import(pathToFileURL(join(f.pkg, 'a.mjs')).href)) + await f.entry.update({ disabled: true }) + await f.entry.update({ disabled: false }) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:2', 'stop:a-edited', 'start:b:2']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('retains the loaded entry through repeated reloads when the new entry is already cached', async () => { + const f = await fixture() + file(join(f.pkg, 'package.json'), manifest('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + await f.ctx.loader.import('addon') + const originalNamespace = f.entry.moduleNamespace + file(join(f.pkg, 'a.mjs'), plugin('a-first')) + await f.emit(join(f.pkg, 'a.mjs')) + const firstNamespace = f.entry.moduleNamespace + expect(firstNamespace).not.toBe(originalNamespace) + file(join(f.pkg, 'a.mjs'), plugin('a-second')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-first:2', 'stop:a-first', 'start:a-second:2']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it.each(['apply', 'apply as default'])('retains a re-export entry using its namespace when it exports %s', async (exported) => { + const f = await fixture(`export { ${exported} } from './b.mjs';\n`) + const implementation: unknown = await f.ctx.loader.import(pathToFileURL(join(f.pkg, 'b.mjs')).href) + expect(implementation).toMatchObject({ apply: f.entry.fiber?.runtime?.callback }) + expect(f.entry.moduleNamespace).not.toBe(implementation) + file(join(f.pkg, 'c.mjs'), plugin('c')) + file(join(f.pkg, 'package.json'), manifest('c', 'dep-1')) + await f.emit(join(f.pkg, 'package.json')) + file(join(f.pkg, 'dep-1.mjs'), 'export const value = 2') + await f.emit(join(f.pkg, 'dep-1.mjs')) + expect(f.trace).toEqual(['start:b:1', 'stop:b', 'start:b:2']) + expect(f.entry.moduleNamespace).toBe(await f.ctx.loader.import(pathToFileURL(join(f.pkg, 'a.mjs')).href)) + await f.entry.update({ disabled: true }) + await f.entry.update({ disabled: false }) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:b:1', 'stop:b', 'start:b:2', 'stop:b', 'start:c:2']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('tracks an entry loaded before HMR starts', async () => { + const f = await fixture(plugin('a'), { lateHmr: true }) + file(join(f.pkg, 'package.json'), manifest('b', 'dep-1')) + await f.emit(join(f.pkg, 'package.json')) + file(join(f.pkg, 'a.mjs'), plugin('a-late')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-late:1']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('keeps entry identity when HMR is recreated between source reloads', async () => { + const f = await fixture() + file(join(f.pkg, 'a.mjs'), plugin('a-first')) + await f.emit(join(f.pkg, 'a.mjs')) + const firstNamespace = f.entry.moduleNamespace + await f.restartHmr() + expect(f.entry.moduleNamespace).toBe(firstNamespace) + file(join(f.pkg, 'a.mjs'), plugin('a-second')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-first:1', 'stop:a-first', 'start:a-second:1']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('leaves an unimported disabled entry without a Node cache entry alone', async () => { + const f = await fixture() + const disabled = f.ctx.loader.resolve(await f.ctx.loader.create({ + name: pathToFileURL(join(f.pkg, 'b.mjs')).href, disabled: true, + })) + expect(disabled.moduleNamespace).toBeUndefined() + file(join(f.pkg, 'a.mjs'), plugin('a-edited')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(disabled.moduleNamespace).toBeUndefined() + expect(disabled.fiber).toBeUndefined() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:1']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('leaves a cordis builtin outside the Node namespace index without reporting an error', async () => { + const f = await fixture() + const builtin = { apply: vi.fn() } + f.ctx.loader.builtins.fixture = builtin + const entry = f.ctx.loader.resolve(await f.ctx.loader.create({ name: 'cordis:fixture' })) + await f.ctx.loader.await() + expect(entry.moduleNamespace).toBe(builtin) + file(join(f.pkg, 'a.mjs'), plugin('a-edited')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(builtin.apply).toHaveBeenCalledOnce() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:1']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('updates disabled entry namespaces through repeated reloads without activating them', async () => { + const f = await fixture() + const originalNamespace = f.entry.moduleNamespace + await f.entry.update({ disabled: true }) + await f.ctx.loader.await() + file(join(f.pkg, 'a.mjs'), plugin('disabled-first')) + await f.emit(join(f.pkg, 'a.mjs')) + const firstNamespace = f.entry.moduleNamespace + expect(firstNamespace).not.toBe(originalNamespace) + expect(firstNamespace).toBe(await f.ctx.loader.import('addon')) + expect(f.trace).toEqual(['start:a:1', 'stop:a']) + file(join(f.pkg, 'a.mjs'), plugin('disabled-second')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.trace).toEqual(['start:a:1', 'stop:a']) + expect(f.warn).not.toHaveBeenCalled() + await f.entry.update({ disabled: false }) + await f.ctx.loader.await() + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:disabled-second:1']) +}) + +it('reports a resolution error for an unimported entry through the existing warning path', async () => { + const f = await fixture() + await f.ctx.loader.create({ name: './missing-plugin.mjs', disabled: true }) + file(join(f.pkg, 'a.mjs'), plugin('a-edited')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.warn).toHaveBeenCalledWith(expect.objectContaining({ code: 'ERR_MODULE_NOT_FOUND' })) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:1']) +}) + +it('reports a recorded namespace missing from the cache while reloading another entry', async () => { + const f = await fixture() + const originalFiber = f.entry.fiber + const originalNamespace = f.entry.moduleNamespace + await f.ctx.loader.create({ name: pathToFileURL(join(f.pkg, 'b.mjs')).href }) + await f.ctx.loader.await() + expect(Map.prototype.delete.call(f.ctx.loader.internal!.loadCache, pathToFileURL(join(f.pkg, 'a.mjs')).href)).toBe(true) + file(join(f.pkg, 'b.mjs'), plugin('b-edited')) + await f.emit(join(f.pkg, 'b.mjs')) + expect(f.warn).toHaveBeenCalledWith(expect.objectContaining({ + message: `HMR cannot locate the loaded module for addon from ${f.ctx.baseUrl}`, + })) + expect(f.entry.fiber).toBe(originalFiber) + expect(f.entry.moduleNamespace).toBe(originalNamespace) + expect(f.trace).toEqual(['start:a:1', 'start:b:1', 'stop:b', 'start:b-edited:1']) +}) + +it.each(['import', 'activation'] as const)('retains the prior namespace after replacement %s fails and can reload again', async (phase) => { + const f = await fixture() + const originalNamespace = f.entry.moduleNamespace + const originalCallback = f.entry.fiber?.runtime?.callback + const message = `replacement ${phase} failed` + file(join(f.pkg, 'a.mjs'), phase === 'import' + ? `throw new Error(${JSON.stringify(message)});\n${plugin('failed')}` + : `export function apply() { throw new Error(${JSON.stringify(message)}); }`) + await expect(f.emit(join(f.pkg, 'a.mjs'))).rejects.toThrow(message) + expect(f.entry.moduleNamespace).toBe(originalNamespace) + expect(await f.ctx.loader.import('addon')).toBe(originalNamespace) + expect(f.entry.fiber?.runtime?.callback).toBe(originalCallback) + expect(f.trace).toEqual(phase === 'import' ? ['start:a:1'] : ['start:a:1', 'stop:a', 'start:a:1']) + file(join(f.pkg, 'a.mjs'), plugin('recovered')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.entry.moduleNamespace).not.toBe(originalNamespace) + expect(f.trace.at(-1)).toBe('start:recovered:1') +}) + +it('reloads a native CommonJS entry and retains its new import namespace', async () => { + const f = await fixture() + const filename = join(f.pkg, 'common.cjs') + const source = (version: number) => `exports.apply = ctx => { + ctx.get('manifestTrace').push('start:cjs:${version}'); + ctx.effect(() => () => { ctx.get('manifestTrace').push('stop:cjs:${version}'); }); + };` + file(filename, source(1)) + const entry = f.ctx.loader.resolve(await f.ctx.loader.create({ name: pathToFileURL(filename).href })) + await f.ctx.loader.await() + const originalNamespace = entry.moduleNamespace + file(filename, source(2)) + await f.emit(filename) + expect(entry.moduleNamespace).not.toBe(originalNamespace) + expect(entry.moduleNamespace).toBe(await f.ctx.loader.import(pathToFileURL(filename).href)) + expect(f.trace).toEqual(['start:a:1', 'start:cjs:1', 'stop:cjs:1', 'start:cjs:2']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('handles an entry still evaluating while another entry reloads', async () => { + const f = await fixture() + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const key = 'dsh-hmr-pending-entry-' + randomUUID() + Reflect.set(globalThis, key, { entered: () => { entered.resolve(undefined) }, wait: release.promise }) + f.ctx.provide('releasePendingEntry', () => { release.resolve(undefined) }) + const filename = join(f.pkg, 'pending.mjs') + file(filename, `const gate = globalThis[${JSON.stringify(key)}]; gate.entered(); await gate.wait;\n${plugin('pending')}`) + const loading = f.ctx.loader.create({ name: pathToFileURL(filename).href }) + onTestFinished(async () => { + release.resolve(undefined) + try { await loading } finally { Reflect.deleteProperty(globalThis, key) } + }) + try { + await entered.promise + file(join(f.pkg, 'a.mjs'), plugin('a-edited').replace('export function apply(ctx) {', + "export function apply(ctx) { ctx.get('releasePendingEntry')();")) + await f.emit(join(f.pkg, 'a.mjs')) + const entry = f.ctx.loader.resolve(await loading) + await f.ctx.loader.await() + expect(entry.moduleNamespace).toBe(await f.ctx.loader.import(pathToFileURL(filename).href)) + expect(f.trace).toEqual(['start:a:1', 'stop:a', 'start:a-edited:1', 'start:pending:1']) + expect(f.warn).not.toHaveBeenCalled() + } finally { + release.resolve(undefined) + await loading + } +}) + +it('keeps an active entry namespace when a later same-name entry has never been enabled', async () => { + const f = await fixture() + await f.ctx.loader.root.update([ + ...f.ctx.loader.root.data, + { id: 'disabled-shadow', name: 'addon', disabled: true }, + ]) + await f.ctx.loader.await() + const disabled = f.ctx.loader.resolve('disabled-shadow') + expect(disabled.moduleNamespace).toBeUndefined() + expect(disabled.fiber).toBeUndefined() + const before = [...f.trace] + file(join(f.pkg, 'package.json'), manifest('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + expect(f.trace).toEqual(before) + file(join(f.pkg, 'a.mjs'), plugin('a-edited')) + await f.emit(join(f.pkg, 'a.mjs')) + expect(f.trace).toEqual([...before, 'stop:a', 'start:a-edited:2']) + expect(disabled.fiber).toBeUndefined() + expect(f.warn).not.toHaveBeenCalled() +}) + +type DispatchFixture = Awaited> + +async function addConfiguredEntry(f: DispatchFixture, id: string, name: string, disabled = false) { + const options = { id, name, disabled } + const entry = f.ctx.loader.resolve(await f.ctx.loader.create(options)) + await f.ctx.loader.await() + return entry +} + +function cachedNamespace(f: DispatchFixture, filename: string): unknown { + const url = pathToFileURL(join(f.pkg, filename)).href + const job = f.ctx.loader.internal!.loadCache.get(url) + expect(job?.module).toBeDefined() + return job!.module!.getNamespace() +} + +function liveFibers(runtime: Plugin.Runtime | null | undefined) { + expect(runtime).toBeDefined() + expect(runtime).not.toBeNull() + return [...runtime!.fibers].filter(fiber => fiber.uid !== null) +} + +async function sharedEntryFixture(kind: 'default' | 'named') { + const source = kind === 'default' + ? "export { apply as default } from './b.mjs';\n" + : "export { apply } from './b.mjs';\n" + const f = await fixture(source) + const secondFile = kind === 'default' ? 'c.mjs' : 'b.mjs' + if (kind === 'default') file(join(f.pkg, secondFile), source) + const second = await addConfiguredEntry(f, 'shared-second', pathToFileURL(join(f.pkg, secondFile)).href) + const firstPlugin = f.ctx.loader.unwrapExports(f.entry.moduleNamespace) as Plugin + const secondPlugin = f.ctx.loader.unwrapExports(second.moduleNamespace) as Plugin + expect(f.entry.moduleNamespace).not.toBe(second.moduleNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(second.moduleNamespace).toBe(cachedNamespace(f, secondFile)) + if (kind === 'default') expect(firstPlugin).toBe(secondPlugin) + else expect(firstPlugin).not.toBe(secondPlugin) + expect(f.entry.fiber?.runtime).toBe(second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(2) + expect(f.trace).toEqual(['start:b:1', 'start:b:1']) + return { ...f, second, secondFile, firstPlugin } +} + +it('reloads both active namespaces for one package name independently after exports move', async () => { + const f = await fixture() + const firstNamespace = f.entry.moduleNamespace + const firstRuntime = f.entry.fiber?.runtime + file(join(f.pkg, 'package.json'), manifest('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + const second = await addConfiguredEntry(f, 'active-after-move', 'addon') + const secondNamespace = second.moduleNamespace + const secondRuntime = second.fiber?.runtime + expect(firstNamespace).not.toBe(secondNamespace) + expect(firstRuntime).not.toBe(secondRuntime) + expect(liveFibers(firstRuntime)).toHaveLength(1) + expect(liveFibers(secondRuntime)).toHaveLength(1) + expect(f.trace).toEqual(['start:a:1', 'start:b:2']) + + file(join(f.pkg, 'a.mjs'), plugin('a-independent')) + await f.emit(join(f.pkg, 'a.mjs')) + const reloadedFirst = f.entry.moduleNamespace + expect(reloadedFirst).not.toBe(firstNamespace) + expect(reloadedFirst).toBe(cachedNamespace(f, 'a.mjs')) + expect(second.moduleNamespace).toBe(secondNamespace) + expect(second.fiber?.runtime).toBe(secondRuntime) + expect(f.trace).toEqual(['start:a:1', 'start:b:2', 'stop:a', 'start:a-independent:2']) + + file(join(f.pkg, 'b.mjs'), plugin('b-independent')) + await f.emit(join(f.pkg, 'b.mjs')) + expect(f.entry.moduleNamespace).toBe(reloadedFirst) + expect(second.moduleNamespace).not.toBe(secondNamespace) + expect(second.moduleNamespace).toBe(cachedNamespace(f, 'b.mjs')) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(1) + expect(liveFibers(second.fiber?.runtime)).toHaveLength(1) + expect(f.trace).toEqual([ + 'start:a:1', 'start:b:2', 'stop:a', 'start:a-independent:2', 'stop:b', 'start:b-independent:2', + ]) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('refreshes both default re-export namespaces sharing one plugin without multiplying instances', async () => { + const f = await sharedEntryFixture('default') + const firstNamespace = f.entry.moduleNamespace + const secondNamespace = f.second.moduleNamespace + const oldFibers = liveFibers(f.entry.fiber?.runtime) + const before = f.trace.length + file(join(f.pkg, 'dep-1.mjs'), 'export const value = 2') + await f.emit(join(f.pkg, 'dep-1.mjs')) + + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.second.moduleNamespace).not.toBe(secondNamespace) + expect(f.entry.moduleNamespace).not.toBe(f.second.moduleNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(f.second.moduleNamespace).toBe(cachedNamespace(f, 'c.mjs')) + expect(f.ctx.loader.unwrapExports(f.entry.moduleNamespace)).toBe(f.ctx.loader.unwrapExports(f.second.moduleNamespace)) + expect(f.entry.fiber?.runtime).toBe(f.second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(2) + expect(oldFibers.every(fiber => fiber.uid === null)).toBe(true) + expect(f.trace.slice(before).sort()).toEqual(['start:b:2', 'start:b:2', 'stop:b', 'stop:b']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('refreshes named re-export and implementation namespaces sharing a callback without multiplying instances', async () => { + const f = await sharedEntryFixture('named') + const firstNamespace = f.entry.moduleNamespace + const secondNamespace = f.second.moduleNamespace + const oldFibers = liveFibers(f.entry.fiber?.runtime) + const before = f.trace.length + file(join(f.pkg, 'b.mjs'), plugin('implementation-updated')) + await f.emit(join(f.pkg, 'b.mjs')) + + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.second.moduleNamespace).not.toBe(secondNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(f.second.moduleNamespace).toBe(cachedNamespace(f, 'b.mjs')) + expect(f.ctx.loader.unwrapExports(f.entry.moduleNamespace)).not.toBe(f.ctx.loader.unwrapExports(f.second.moduleNamespace)) + expect(f.entry.fiber?.runtime).toBe(f.second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(2) + expect(oldFibers.every(fiber => fiber.uid === null)).toBe(true) + expect(f.trace.slice(before).sort()).toEqual([ + 'start:implementation-updated:1', 'start:implementation-updated:1', 'stop:b', 'stop:b', + ]) + expect(f.warn).not.toHaveBeenCalled() +}) + +function activeTraceCount(trace: string[], marker: string): number { + return trace.filter(event => event.startsWith(`start:${marker}:`)).length + - trace.filter(event => event === `stop:${marker}`).length +} + +it('assigns each entry its own replacement when a shared runtime splits into distinct callbacks', async () => { + const f = await sharedEntryFixture('default') + const firstNamespace = f.entry.moduleNamespace + const secondNamespace = f.second.moduleNamespace + const oldFibers = liveFibers(f.entry.fiber?.runtime) + const before = f.trace.length + file(join(f.pkg, 'a.mjs'), plugin('a-split')) + file(join(f.pkg, 'c.mjs'), plugin('c-split')) + await f.emit(join(f.pkg, 'a.mjs'), join(f.pkg, 'c.mjs')) + + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.second.moduleNamespace).not.toBe(secondNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(f.second.moduleNamespace).toBe(cachedNamespace(f, 'c.mjs')) + expect(f.entry.moduleNamespace).toMatchObject({ apply: f.entry.fiber?.runtime?.callback }) + expect(f.second.moduleNamespace).toMatchObject({ apply: f.second.fiber?.runtime?.callback }) + expect(f.entry.fiber?.runtime).not.toBe(f.second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(1) + expect(liveFibers(f.second.fiber?.runtime)).toHaveLength(1) + expect(oldFibers.every(fiber => fiber.uid === null)).toBe(true) + expect(f.trace.slice(before).sort()).toEqual(['start:a-split:1', 'start:c-split:1', 'stop:b', 'stop:b']) + expect(f.warn).not.toHaveBeenCalled() +}) + +it('restores shared entries and a programmatic instance when split replacements are ambiguous', async () => { + const f = await sharedEntryFixture('default') + const firstNamespace = f.entry.moduleNamespace + const secondNamespace = f.second.moduleNamespace + const programmatic = (await f.ctx.plugin(f.firstPlugin)).ctx.fiber + expect(programmatic.entry).toBeUndefined() + expect(liveFibers(f.ctx.registry.get(f.firstPlugin))).toHaveLength(3) + file(join(f.pkg, 'a.mjs'), plugin('a-split')) + file(join(f.pkg, 'c.mjs'), plugin('c-split')) + await expect(f.emit(join(f.pkg, 'a.mjs'), join(f.pkg, 'c.mjs'))).rejects.toThrow( + 'HMR replacement is ambiguous for a plugin instance without a Loader entry', + ) + + expect(f.entry.moduleNamespace).toBe(firstNamespace) + expect(f.second.moduleNamespace).toBe(secondNamespace) + expect(cachedNamespace(f, 'a.mjs')).toBe(firstNamespace) + expect(cachedNamespace(f, 'c.mjs')).toBe(secondNamespace) + const restored = f.ctx.registry.get(f.firstPlugin) + const fibers = liveFibers(restored) + expect(fibers).toHaveLength(3) + expect(fibers.filter(fiber => fiber.entry === undefined)).toHaveLength(1) + expect(f.entry.fiber?.runtime).toBe(restored) + expect(f.second.fiber?.runtime).toBe(restored) + expect(activeTraceCount(f.trace, 'b')).toBe(3) + expect(activeTraceCount(f.trace, 'a-split')).toBe(0) + expect(activeTraceCount(f.trace, 'c-split')).toBe(0) +}) + +it.each(['import', 'activation'] as const)('restores every shared entry when one replacement %s fails and can reload again', async (phase) => { + const f = await sharedEntryFixture('default') + const firstNamespace = f.entry.moduleNamespace + const secondNamespace = f.second.moduleNamespace + const message = `shared replacement ${phase} failed` + file(join(f.pkg, 'a.mjs'), plugin('a-replacement')) + file(join(f.pkg, 'c.mjs'), phase === 'import' + ? `throw new Error(${JSON.stringify(message)});\n${plugin('c-failed')}` + : `export function apply() { throw new Error(${JSON.stringify(message)}); }`) + await expect(f.emit(join(f.pkg, 'a.mjs'), join(f.pkg, 'c.mjs'))).rejects.toThrow(message) + + expect(f.entry.moduleNamespace).toBe(firstNamespace) + expect(f.second.moduleNamespace).toBe(secondNamespace) + expect(cachedNamespace(f, 'a.mjs')).toBe(firstNamespace) + expect(cachedNamespace(f, 'c.mjs')).toBe(secondNamespace) + const restored = f.ctx.registry.get(f.firstPlugin) + expect(f.entry.fiber?.runtime).toBe(restored) + expect(f.second.fiber?.runtime).toBe(restored) + expect(liveFibers(restored)).toHaveLength(2) + expect(activeTraceCount(f.trace, 'b')).toBe(2) + expect(activeTraceCount(f.trace, 'a-replacement')).toBe(0) + expect(activeTraceCount(f.trace, 'c-failed')).toBe(0) + + file(join(f.pkg, 'c.mjs'), plugin('c-recovered')) + await f.emit(join(f.pkg, 'a.mjs'), join(f.pkg, 'c.mjs')) + expect(f.entry.moduleNamespace).not.toBe(firstNamespace) + expect(f.second.moduleNamespace).not.toBe(secondNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(f.second.moduleNamespace).toBe(cachedNamespace(f, 'c.mjs')) + expect(f.entry.moduleNamespace).toMatchObject({ apply: f.entry.fiber?.runtime?.callback }) + expect(f.second.moduleNamespace).toMatchObject({ apply: f.second.fiber?.runtime?.callback }) + expect(f.entry.fiber?.runtime).not.toBe(f.second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(1) + expect(liveFibers(f.second.fiber?.runtime)).toHaveLength(1) + expect(activeTraceCount(f.trace, 'b')).toBe(0) + expect(activeTraceCount(f.trace, 'a-replacement')).toBe(1) + expect(activeTraceCount(f.trace, 'c-recovered')).toBe(1) +}) + +it('deduplicates a repeated namespace without activating a same-name uninitialized entry', async () => { + const f = await fixture() + const second = await addConfiguredEntry(f, 'duplicate-active', 'addon') + const disabled = await addConfiguredEntry(f, 'duplicate-disabled', 'addon', true) + const originalNamespace = f.entry.moduleNamespace + expect(second.moduleNamespace).toBe(originalNamespace) + expect(disabled.moduleNamespace).toBeUndefined() + expect(disabled.fiber).toBeUndefined() + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(2) + file(join(f.pkg, 'package.json'), manifest('b', 'dep-2')) + await f.emit(join(f.pkg, 'package.json')) + const before = f.trace.length + file(join(f.pkg, 'a.mjs'), plugin('deduplicated')) + await f.emit(join(f.pkg, 'a.mjs')) + + expect(f.entry.moduleNamespace).not.toBe(originalNamespace) + expect(f.entry.moduleNamespace).toBe(cachedNamespace(f, 'a.mjs')) + expect(second.moduleNamespace).toBe(f.entry.moduleNamespace) + expect(f.entry.fiber?.runtime).toBe(second.fiber?.runtime) + expect(liveFibers(f.entry.fiber?.runtime)).toHaveLength(2) + expect(disabled.moduleNamespace).toBeUndefined() + expect(disabled.fiber).toBeUndefined() + expect(f.trace.slice(before).sort()).toEqual([ + 'start:deduplicated:2', 'start:deduplicated:2', 'stop:a', 'stop:a', + ]) + expect(f.warn).not.toHaveBeenCalled() +}) diff --git a/packages/boot/hmr/tests/package-manifest.spec.ts b/packages/boot/hmr/tests/package-manifest.spec.ts new file mode 100644 index 0000000000..52847f1cdc --- /dev/null +++ b/packages/boot/hmr/tests/package-manifest.spec.ts @@ -0,0 +1,457 @@ +/** Package configuration read by Node's resolver follows an invalidated manifest on disk. */ +import { spawn } from 'node:child_process' +import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs' +import NodeModule, { createRequire, registerHooks } from 'node:module' +import { tmpdir } from 'node:os' +import { dirname, join, toNamespacedPath } from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { ModuleLoader } from '@deepseek-ai/cordis-plugin-loader' +import { afterEach, describe, expect, it } from 'vitest' +import { PackageManifests } from '../src/package-manifest.ts' + +const require = createRequire(import.meta.url) +const addon = require('node-addon-require-builtin') as { requireBuiltin(id: string): unknown } +const reader = addon.requireBuiltin('internal/modules/package_json_reader') as { + read: (path: string, options?: { isESM: boolean; base: string; specifier: string }) => { exists: boolean; type: string } + getPackageScopeConfig: (url: string) => { exists: boolean; type: string; pjsonPath: string } + getNearestParentPackageJSON: (path: string) => { data: { type: string }; path: string } | undefined +} +const formats = addon.requireBuiltin('internal/modules/esm/get_format') as { + defaultGetFormatWithoutErrors(url: URL, context?: object): string | null +} +const binding = (addon.requireBuiltin('internal/bootstrap/realm') as { + internalBinding(id: 'modules'): Record +}).internalBinding('modules') + +const cleanup: Array<() => void | Promise> = [] +afterEach(async () => { for (const dispose of cleanup.splice(0).reverse()) await dispose() }) + +function file(path: string, source: string): void { + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, source) +} + +function fixture() { + const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-hmr-manifest-'))) + cleanup.push(() => { rmSync(root, { recursive: true, force: true }) }) + const dir = join(root, 'packages', 'pkg') + const manifest = join(dir, 'package.json') + const importer = join(root, 'importer', 'entry.mjs') + file(join(root, 'importer', 'package.json'), '{"type":"module","dependencies":{"pkg":"*"}}') + file(importer, 'export {}') + // A workspace-style link: the importer resolves `pkg` from its own node_modules to the source directory. + mkdirSync(join(root, 'importer', 'node_modules'), { recursive: true }) + const link = join(root, 'importer', 'node_modules', 'pkg') + symlinkSync(dir, link, process.platform === 'win32' ? 'junction' : 'dir') + for (const name of ['a', 'b']) { + file(join(dir, `${name}.mjs`), `export const marker = '${name}'`) + file(join(dir, `${name}.cjs`), `module.exports = { marker: '${name}' }`) + } + const manifests = new PackageManifests() + cleanup.push(() => { manifests.dispose() }) + const invalidate = (path: string) => Promise.resolve().then(manifests.invalidate.bind(manifests, path)) + return { root, dir, manifest, importer, importerURL: pathToFileURL(importer).href, manifests, invalidate } +} + +function resolveEsm(specifier: string, parentURL: string): string { + const loader = ModuleLoader.fromInternal()! + return (loader.version === 'v2' + ? loader.resolveSync(parentURL, { specifier, attributes: {} }) + : loader.resolveSync(specifier, parentURL, {})).url +} + +describe('package manifest invalidation', { concurrent: false }, () => { + it('preserves filename-based resolve hooks and node-prefixed builtins after invalidation', async () => { + const f = fixture() + file(f.manifest, '{}') + const importerRequire = createRequire(f.importer) + const nativeFs = importerRequire('node:fs') as typeof import('node:fs') + const missingBuiltin = 'node:dsh-hmr-missing' + const missingFailure = outcome(() => importerRequire(missingBuiltin)) + expect(missingFailure).toHaveProperty('code', 'ERR_UNKNOWN_BUILTIN_MODULE') + const target = join(f.dir, 'a.cjs') + const specifier = join(f.dir, 'alias.cjs') + file(specifier, 'module.exports = { marker: "alias" }') + cleanup.push(() => { Reflect.deleteProperty(importerRequire.cache, target) }) + let hooks: ReturnType | undefined = registerHooks({ + resolve(request, context, nextResolve) { + const resolved = nextResolve(request, context) + return resolved.url === pathToFileURL(specifier).href ? { ...resolved, url: pathToFileURL(target).href } : resolved + }, + }) + cleanup.push(() => { hooks?.deregister() }) + try { + const original = importerRequire(specifier) as { marker: string } + expect(original).toEqual({ marker: 'a' }) + await f.invalidate(f.manifest) + expect(importerRequire(specifier)).toBe(original) + } finally { + hooks.deregister() + hooks = undefined + } + + const descriptor = Object.getOwnPropertyDescriptor(importerRequire.cache, 'fs') + const restore = () => { + if (descriptor) Object.defineProperty(importerRequire.cache, 'fs', descriptor) + else Reflect.deleteProperty(importerRequire.cache, 'fs') + } + cleanup.push(restore) + const fake = new NodeModule('fs') + const fakeExports = { marker: 'fake fs' } + fake.exports = fakeExports + try { + importerRequire.cache.fs = fake + expect(importerRequire('fs')).toBe(fakeExports) + expect(importerRequire('node:fs')).toBe(nativeFs) + expect(outcome(() => importerRequire(missingBuiltin))).toEqual(missingFailure) + } finally { + restore() + } + }) + + it('loads changed CommonJS exports without evicting previously loaded modules', async () => { + const f = fixture() + const a = join(f.dir, 'a.cjs') + const b = join(f.dir, 'b.cjs') + const unrelated = join(f.root, 'unrelated.cjs') + file(unrelated, 'module.exports = { marker: "unrelated" }') + file(f.manifest, JSON.stringify({ name: 'pkg', exports: './a.cjs' })) + const importerRequire = createRequire(f.importer) + cleanup.push(() => { + for (const path of [a, b, unrelated]) Reflect.deleteProperty(importerRequire.cache, path) + }) + const original = importerRequire('pkg') as { marker: string } + const other = importerRequire(unrelated) as { marker: string } + const originalModule = importerRequire.cache[a] + const otherModule = importerRequire.cache[unrelated] + expect(original).toEqual({ marker: 'a' }) + expect(originalModule).toBeDefined() + expect(otherModule).toBeDefined() + + file(f.manifest, JSON.stringify({ name: 'pkg', exports: './b.cjs' })) + await f.invalidate(f.manifest) + expect(importerRequire.resolve('pkg')).toBe(b) + const current = importerRequire('pkg') as { marker: string } + expect(importerRequire.cache[a]).toBe(originalModule) + expect(importerRequire.cache[unrelated]).toBe(otherModule) + expect(original).toEqual({ marker: 'a' }) + expect(importerRequire(a)).toBe(original) + expect(importerRequire(unrelated)).toBe(other) + expect(current).toEqual({ marker: 'b' }) + }) + + it.each(['symlink', 'main'] as const)('refreshes a cached external %s entry after its manifest changes', async (entry) => { + const f = fixture() + const external = join(f.root, 'packages', 'outside', 'entry.mjs') + file(external, 'export {}') + if (entry === 'symlink') { + symlinkSync(dirname(external), join(f.dir, 'linked'), process.platform === 'win32' ? 'junction' : 'dir') + file(f.manifest, JSON.stringify({ name: 'pkg', exports: './linked/entry.mjs' })) + } else { + file(f.manifest, JSON.stringify({ name: 'pkg', main: external })) + } + const importerRequire = createRequire(f.importer) + expect(importerRequire.resolve('pkg')).toBe(external) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(external).href) + + file(f.manifest, JSON.stringify({ name: 'pkg', main: './b.mjs', exports: './b.mjs' })) + await f.invalidate(f.manifest) + expect.soft(importerRequire.resolve('pkg')).toBe(join(f.dir, 'b.mjs')) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'b.mjs')).href) + }) + + it('resolves changed exports and main only after the manifest is invalidated', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', exports: { import: './a.mjs', require: './a.cjs' } })) + const importerRequire = createRequire(f.importer) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'a.mjs')).href) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'a.cjs')) + + file(f.manifest, JSON.stringify({ name: 'pkg', exports: { import: './b.mjs', require: './b.cjs' } })) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'a.mjs')).href) + f.manifests.invalidate(f.manifest) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'b.mjs')).href) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'b.cjs')) + + file(f.manifest, JSON.stringify({ name: 'pkg', main: './a.cjs' })) + f.manifests.invalidate(f.manifest) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'a.cjs')) + }) + + it('updates package type, scope, and nearest-package reads inside the directory', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'commonjs' })) + const source = join(f.dir, 'lib', 'entry.js') + file(source, 'export {}') + expect(formats.defaultGetFormatWithoutErrors(pathToFileURL(source))).toBe('commonjs') + expect(reader.getNearestParentPackageJSON(source)?.data.type).toBe('commonjs') + + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module' })) + f.manifests.invalidate(f.manifest) + expect(formats.defaultGetFormatWithoutErrors(pathToFileURL(source))).toBe('module') + expect(reader.getNearestParentPackageJSON(source)?.data.type).toBe('module') + expect(reader.getPackageScopeConfig(pathToFileURL(source).href)).toMatchObject({ + exists: true, type: 'module', pjsonPath: toNamespacedPath(f.manifest), + }) + }) + + it('resolves changed package imports from inside the directory', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module', imports: { '#target': './a.mjs' } })) + const parent = pathToFileURL(join(f.dir, 'inside.mjs')).href + file(join(f.dir, 'inside.mjs'), 'export {}') + expect(resolveEsm('#target', parent)).toBe(pathToFileURL(join(f.dir, 'a.mjs')).href) + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module', imports: { '#target': './b.mjs' } })) + f.manifests.invalidate(f.manifest) + expect(resolveEsm('#target', parent)).toBe(pathToFileURL(join(f.dir, 'b.mjs')).href) + }) + + it('reports an invalid manifest with the native error and recovers after it is fixed', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', exports: './a.mjs' })) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'a.mjs')).href) + file(f.manifest, '{') + f.manifests.invalidate(f.manifest) + expect(() => reader.read(f.manifest)).toThrow(expect.objectContaining({ code: 'ERR_INVALID_PACKAGE_CONFIG' })) + file(f.manifest, JSON.stringify({ name: 'pkg', exports: './b.mjs' })) + f.manifests.invalidate(f.manifest) + expect(resolveEsm('pkg', f.importerURL)).toBe(pathToFileURL(join(f.dir, 'b.mjs')).href) + }) + + it('leaves other package directories to Node and restores native readers on disposal', () => { + const f = fixture() + const other = join(f.root, 'packages', 'other', 'package.json') + file(other, JSON.stringify({ name: 'other', type: 'commonjs' })) + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'commonjs' })) + expect(reader.read(other).type).toBe('commonjs') + const original = binding.readPackageJSON + f.manifests.invalidate(f.manifest) + file(other, JSON.stringify({ name: 'other', type: 'module' })) + expect(reader.read(other).type).toBe('commonjs') + expect(binding.readPackageJSON).not.toBe(original) + f.manifests.dispose() + expect(binding.readPackageJSON).toBe(original) + }) + + it('reports invalid JSON with its non-file ESM base after invalidation', async () => { + const f = fixture() + file(f.manifest, '{') + await f.invalidate(f.manifest) + const base = 'data:text/javascript,export{}' + const failure = outcome(() => reader.read(f.manifest, { isESM: true, base, specifier: 'pkg' })) + expect(failure).toHaveProperty('code', 'ERR_INVALID_PACKAGE_CONFIG') + expect(failure).toHaveProperty('message', expect.stringContaining(base)) + }) +}) + +function outcome(run: () => unknown): unknown { + try { return run() } catch (error) { + const failure = error as Error & { code?: string } + return { code: failure.code, message: failure.message } + } +} + +describe('package manifest reader parity', { concurrent: false }, () => { + it('terminates scope lookup after invalidating the filesystem root without modifying it', async () => { + const f = fixture() + const child = spawn(process.execPath, [ + '--expose-internals', + fileURLToPath(new URL('./fixtures/package-root-scope.mjs', import.meta.url)), + f.root, + ], { stdio: ['ignore', 'pipe', 'pipe'] }) + const completed = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => { + child.once('error', reject) + child.once('close', (code, signal) => { resolve({ code, signal }) }) + }) + cleanup.push(async () => { + if (child.exitCode === null && child.signalCode === null) child.kill() + await completed + }) + let stdout = '', stderr = '' + child.stdout.setEncoding('utf8').on('data', (chunk: string) => { stdout += chunk }) + child.stderr.setEncoding('utf8').on('data', (chunk: string) => { stderr += chunk }) + const result = await completed + expect(result.signal, stderr).toBeNull() + expect(result.code, stderr).toBe(0) + expect(stdout).toBe('scope traversal completed\n') + }) + + it('reads valid JSON with a non-file ESM base like Node', async () => { + const f = fixture() + file(f.manifest, '{}') + const options = { isESM: true, base: 'data:text/javascript,export{}', specifier: 'pkg' } + const expected = outcome(() => reader.read(f.manifest, options)) + await f.invalidate(f.manifest) + expect(outcome(() => reader.read(f.manifest, options))).toEqual(expected) + }) + + it('refreshes a cached real directory after a package link moves', async () => { + const f = fixture() + const other = join(f.root, 'packages', 'other') + const link = join(f.root, 'importer', 'node_modules', 'pkg') + const linkedManifest = join(link, 'package.json') + file(f.manifest, '{"type":"commonjs"}') + file(join(other, 'package.json'), '{"type":"commonjs"}') + unlinkSync(link) + symlinkSync(other, link, process.platform === 'win32' ? 'junction' : 'dir') + await f.invalidate(f.manifest) + expect(reader.read(linkedManifest).type).toBe('commonjs') + + unlinkSync(link) + symlinkSync(f.dir, link, process.platform === 'win32' ? 'junction' : 'dir') + file(f.manifest, '{"type":"module"}') + await f.invalidate(f.manifest) + expect(reader.read(linkedManifest).type).toBe('module') + }) + + it.each([ + '{}', + '{"name":"pkg","main":"./a.cjs","type":"commonjs"}', + '{"name":"pkg","exports":{"import":"./a.mjs","require":"./a.cjs"}}', + '{"name":"pkg","exports":false,"main":"./a.cjs"}', + '{"name":"pkg","imports":{"#item":"./a.mjs"},"type":"module"}', + '{"name":"pkg","exports":"./a.mjs"}', + '{', 'null', '[]', '1', '{"name":1}', '{"type":1}', '{"type":"other"}', + '{"name":"\\ud800"}', '{"main":"\\ud800"}', '{"type":"\\ud800"}', '{"exports":"\\ud800"}', + ])('reads %s like the native reader', (source) => { + const f = fixture() + file(f.manifest, source) + const expected = outcome(() => reader.read(f.manifest)) + f.manifests.invalidate(f.manifest) + expect(outcome(() => reader.read(f.manifest))).toEqual(expected) + }) + + it('reports an invalid manifest found while importing like the native resolver', () => { + const f = fixture() + const errorOf = (run: () => unknown): Error => { + try { run() } catch (error) { return error as Error } + throw new Error('expected the lookup to fail') + } + file(f.manifest, '{') + const expected = errorOf(() => resolveEsm('pkg', f.importerURL)) + f.manifests.invalidate(f.manifest) + const actual = errorOf(() => resolveEsm('pkg', f.importerURL)) + expect([String(actual), Object.keys(actual)]).toEqual([String(expected), Object.keys(expected)]) + expect(String(errorOf(() => reader.read(f.manifest)))).toBe(String(expected).replace(/ while importing .*(?=\.$)/u, '').replace(join(f.root, 'importer', 'node_modules', 'pkg'), f.dir)) + }) + + it('reads invalid UTF-8 like the native reader', () => { + const f = fixture() + mkdirSync(f.dir, { recursive: true }) + writeFileSync(f.manifest, Buffer.concat([Buffer.from('{"name":"'), Buffer.from([0xff]), Buffer.from('"}')])) + const expected = outcome(() => reader.read(f.manifest)) + f.manifests.invalidate(f.manifest) + expect(outcome(() => reader.read(f.manifest))).toEqual(expected) + }) + + it('refreshes a changed CommonJS main after a cached lookup', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', main: './a.cjs' })) + const importerRequire = createRequire(f.importer) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'a.cjs')) + file(f.manifest, JSON.stringify({ name: 'pkg', main: './b.cjs' })) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'a.cjs')) + f.manifests.invalidate(f.manifest) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'b.cjs')) + }) + + it('updates the package type of extensionless files', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'commonjs' })) + const extensionless = pathToFileURL(join(f.dir, 'bin', 'tool')) + file(join(f.dir, 'bin', 'tool'), '') + const before = formats.defaultGetFormatWithoutErrors(extensionless) + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module' })) + f.manifests.invalidate(f.manifest) + expect(formats.defaultGetFormatWithoutErrors(extensionless)).not.toBe(before) + expect(formats.defaultGetFormatWithoutErrors(extensionless)).toBe('module') + }) + + it('continues to the enclosing scope after the manifest is deleted', () => { + const f = fixture() + const enclosing = join(f.root, 'packages', 'package.json') + file(enclosing, JSON.stringify({ name: 'packages', type: 'commonjs' })) + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module' })) + const source = join(f.dir, 'lib', 'entry.js') + file(source, '') + expect(reader.getPackageScopeConfig(pathToFileURL(source).href).type).toBe('module') + rmSync(f.manifest) + f.manifests.invalidate(f.manifest) + expect(reader.getPackageScopeConfig(pathToFileURL(source).href)).toMatchObject({ + exists: true, type: 'commonjs', pjsonPath: toNamespacedPath(enclosing), + }) + expect(formats.defaultGetFormatWithoutErrors(pathToFileURL(join(f.dir, 'bin', 'tool')))).toBe( + formats.defaultGetFormatWithoutErrors(pathToFileURL(join(f.root, 'packages', 'tool'))), + ) + expect(reader.getNearestParentPackageJSON(source)?.path).toBe(toNamespacedPath(enclosing)) + }) + + it('stops a scope lookup at a node_modules boundary like Node', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', type: 'module' })) + const nested = join(f.dir, 'node_modules', 'dep', 'entry.js') + file(nested, '') + const expected = [ + outcome(() => reader.getPackageScopeConfig(pathToFileURL(nested).href)), + outcome(() => reader.getNearestParentPackageJSON(nested)), + ] + f.manifests.invalidate(f.manifest) + expect([ + outcome(() => reader.getPackageScopeConfig(pathToFileURL(nested).href)), + outcome(() => reader.getNearestParentPackageJSON(nested)), + ]).toEqual(expected) + }) + + it('answers missing directories and non-file URLs natively', () => { + const f = fixture() + file(f.manifest, '{}') + const missing = join(f.root, 'missing', 'package.json') + const nativeScope = binding.getPackageScopeConfig as (url: string) => unknown + const expected = [outcome(() => reader.read(missing)), outcome(() => nativeScope('node:fs'))] + f.manifests.invalidate(f.manifest) + expect([outcome(() => reader.read(missing)), outcome(() => (binding.getPackageScopeConfig as (url: string) => unknown)('node:fs'))]) + .toEqual(expected) + }) + + it('falls back to the builtin addon for internals that --expose-internals does not expose', () => { + const f = fixture() + file(f.manifest, JSON.stringify({ name: 'pkg', main: './a.cjs' })) + const importerRequire = createRequire(f.importer) + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'a.cjs')) + const argv = [...process.execArgv] + process.execArgv.push('--expose-internals') + try { + file(f.manifest, JSON.stringify({ name: 'pkg', main: './b.cjs' })) + f.manifests.invalidate(f.manifest) + } finally { + process.execArgv.splice(0, process.execArgv.length, ...argv) + } + expect(importerRequire.resolve('pkg')).toBe(join(f.dir, 'b.cjs')) + }) + + it('keeps readers replaced after installation when disposed', async () => { + const f = fixture() + file(f.manifest, '{}') + const previousRead = binding.readPackageJSON + const previousScope = binding.getPackageScopeConfig + const previousType = binding.getPackageType + const previousNearest = reader.getNearestParentPackageJSON + try { + await f.invalidate(f.manifest) + const later = () => undefined + binding.readPackageJSON = later + reader.getNearestParentPackageJSON = later + f.manifests.dispose() + expect(binding.readPackageJSON).toBe(later) + expect(reader.getNearestParentPackageJSON).toBe(later) + expect(binding.getPackageScopeConfig).toBe(previousScope) + expect(binding.getPackageType).toBe(previousType) + } finally { + f.manifests.dispose() + binding.readPackageJSON = previousRead + binding.getPackageScopeConfig = previousScope + binding.getPackageType = previousType + reader.getNearestParentPackageJSON = previousNearest + } + }) +}) diff --git a/packages/boot/hmr/tests/watch-config.spec.ts b/packages/boot/hmr/tests/watch-config.spec.ts index db31c0b654..7d0caf035b 100644 --- a/packages/boot/hmr/tests/watch-config.spec.ts +++ b/packages/boot/hmr/tests/watch-config.spec.ts @@ -321,6 +321,27 @@ describe('HMR exact config paths', () => { expect(warn).toHaveBeenCalledWith(failure) }) + it('absorbs a watcher error emitted after close removed its listeners', async () => { + const dir = mkdtempSync(join(tmpdir(), 'dsh-patch-close-error-')) + hmrRoots.push(dir) + const filename = join(dir, 'plugins.yml') + const ctx = await bootHmr(dir) + onTestFinished(() => ctx.fiber.dispose()) + const watcher = new FSWatcher() + const previousFactory = configWatch.create + onTestFinished(() => { configWatch.create = previousFactory }) + configWatch.create = () => { queueMicrotask(() => { watcher.emit('ready') }); return watcher } + const dispose = await watchConfig(ctx, filename, {}, () => {}) + await dispose() + expect(watcher.listenerCount('error')).toBe(1) + + // Mirror the real close(): it drops every listener before a pending write-settle poll fires. + expect(() => watcher.emit('error', Object.assign( + new Error("EPERM: operation not permitted, stat 'C:\\Temp\\dsh\\.credentials.yaml'"), + { code: 'EPERM', syscall: 'stat' }, + ))).not.toThrow() + }) + it('closes a ready watcher when its context has already been disposed', async () => { const dir = mkdtempSync(join(tmpdir(), 'dsh-patch-disposed-')) hmrRoots.push(dir) diff --git a/packages/boot/plugin-manager/README.i18n.yaml b/packages/boot/plugin-manager/README.i18n.yaml index adecd51753..ffb3a5a57e 100644 --- a/packages/boot/plugin-manager/README.i18n.yaml +++ b/packages/boot/plugin-manager/README.i18n.yaml @@ -15,8 +15,8 @@ en: 87679c51aa3693f5 zh: 11684a48a5806afb /deepseek-ai-dsh-plugin-manager/use-this-package: - en: d0f7d42dffe6d3c7 - zh: b4bf81958cda876d + en: af5bf0a205b5a2a1 + zh: 0114039816454a1a /deepseek-ai-dsh-plugin-manager/use-this-package/version-compatibility-and-exemptions: en: 920c0acb3b9feaf3 zh: ba6e832a256880a7 @@ -24,8 +24,8 @@ en: d5643a06f300c30a zh: 9348b6a7676a2849 /deepseek-ai-dsh-plugin-manager/understand-the-implementation: - en: 54c085aff506248b - zh: a046e4e4c9374729 + en: 21ccb07cfee8974c + zh: 5790cd89d9c14e9e /deepseek-ai-dsh-plugin-manager/further-exploration: en: c6538243d8702a0d zh: 04a82d923353c75f @@ -45,11 +45,11 @@ en: c29f60be7f3edc1a zh: d9b9388b96ef95cc /deepseek-ai-dsh-plugin-manager/known-limitations-and-deferred-work: - en: fde2d9491fe5dd4f - zh: ef6e6db15c4f19d2 + en: 7d7ea29063e358ca + zh: e3399db77a97f62a /deepseek-ai-dsh-plugin-manager/known-limitations-and-deferred-work/failure-behavior: - en: 61450cf6e03c2819 - zh: 4d52f5306825787b + en: 4447e4df8d62b274 + zh: c76443e2edb97b1d /deepseek-ai-dsh-plugin-manager/known-limitations-and-deferred-work/dev-note: en: e67f633dc6fe7773 zh: 3bb0c20c93f16f98 diff --git a/packages/boot/plugin-manager/README.md b/packages/boot/plugin-manager/README.md index 7d2f57e9cd..a06fa4a35e 100644 --- a/packages/boot/plugin-manager/README.md +++ b/packages/boot/plugin-manager/README.md @@ -41,21 +41,29 @@ A plugin toggle updates only `disabled` in the last matching override in the pro A selected bundle that cannot load remains in `listBundles` with an `error`; `enabled` records the saved selection, not successful loading. The plugin page shows the error and allows deselection. A broken bundle cannot be enabled. Management bundles remain protected if their files become unreadable. +Scoped bundle rows carry both their declared `rowId` and outer `preset` target. The manager reads declarations without composing the bundle alone, so rows remain visible while Off. Their optional `composition` state comes from the current preset inventory; they have no editable root `entryId`, and their `preset-managed` read-only reason directs configuration to the preset or bundle. Enabling audits every affected preset using its registered declaring row, including failures the registry retained instead of throwing; unrelated unchanged preset failures become warnings. Bundle toggles validate the saved composition even without HMR; composition failures keep the saved selection and return a diagnostic immediately. Remove refuses while a current or retained preset generation uses a contributed row, matched by defining preset row, child row ID, and resolved module, or any module belonging to the bundle package itself. This protection lasts after Off until those generations release the modules; unrelated rows using a shared dependency do not prevent removal. Anonymous scoped insertions receive the same protection. Missing module files do not make a different contribution match; identical retained declarations remain protected when their files are unavailable. + `listBundles` includes optional display `meta` for each bundle and its declared plugin rows, including disabled bundles. The Client selects a language from these values. The separate `description` field is the bundle's raw `package.json.description`; a metadata diagnostic does not block management. `source` gives a profile dependency as a spec `pnpm add` accepts: a git address or URL as recorded without the user information of an http(s) URL (a query string stays as recorded), a `file:` or `link:` path made absolute against the profile or the home directory for `~`, and a registry range, tag, or alias after the package name, as is any spec of a package installed under another name; a bundle the installation supplies has none, even when the profile also depends on it, because the installation's copy is the one that loads. The `plugin_manager` tool omits UI display metadata from list results. +`listBundles` marks shipped optional bundles and on-demand catalog entries as `official`, independently of package installation. `availability` identifies a readable installation-provided or profile package, or `missing`; on-demand catalog entries exclude undeclared transitive copies. `installed` only records whether the profile declares the dependency. An absent on-demand entry carries embedded localized metadata and a host-derived `installTarget` containing the running DSH version and install spec, without registry access. Profile-declared packages with unreadable files retain their declaration and expose the normal error. + `inspect(spec, options)` reads what a spec names before anything installs: a registry name is asked of the registry through `pnpm view`, run in the profile directory so the same proxy and authentication settings apply as to the install; an absolute path has its `package.json` read; a git address or tarball answers only its form and the `host` it is fetched from. The answer carries the name, version, description, whether the package declares a bundle, and the `registry` that answered, or a `problem`: `invalid-spec`, `already-installed`, `not-found`, `not-a-package`, `not-a-bundle`, `network`, or `unknown`, with the `registries` asked. A caller's `signal` or `inspectTimeoutMs` ends the lookup. +Published installations offer `package@`. Source Web and development Desktop offer `link:` from the running CLI installation, including its development symlink. Package names resolve through workspace manifests; current working directory and prerelease suffixes do not select a checkout. Missing packages, bundle patches, or built entry points produce a diagnostic without registry fallback. Prepare the checkout with `pnpm install` and `pnpm run build`. Linked packages use that checkout’s dependencies and peers; rebuilding and restarting remain necessary after code changes. A registry copy or another checkout’s link can need replacement even when its version matches. + +Official on-demand installs and explicit updates call the same `installBundle` operation as other bundles. Registry targets use `saveExact: true`; development targets retain the ordinary `link:` dependency. Updates pass the current `enabled` selection; replacing an existing dependency reports `restart-required`. Turning Off only deselects the layer, and Remove uses the existing removal checks. Installation failure restores the profile manifest and lockfile; activation failure after installation can retain the saved dependency and selection. The existing peer-compatibility check still applies, without an additional Official-only version admission rule. + `installBundle` checks GitHub repositories with `git ls-remote` before starting pnpm, using the profile directory and the installer's Git and proxy configuration. `githubConnectionTimeoutMs` defaults to 5000 ms and limits only this check, not package download or builds. The check disables credential helpers and prompts; only network failures and timeouts stop installation, reporting `failedAt: 'spec-host'` with the existing failure kind and diagnostic log. Authentication, repository lookup and other failures are left to pnpm, including its HTTPS-to-SSH fallback. Cancellation and manager disposal stop the check and its descendants. Registry packages, paths, tarballs and other Git hosts skip this check. A reachable repository can still fail during download or bundle validation. Registries are asked in turn. The plan starts at `options.registry`, else the configured `registry` (`null` is the one pnpm's own configuration names), and continues through `fallbackRegistries` while a registry is unreachable, times out, or answers that it has no such package or version, which a mirror not yet synced does. A registry outside the configured set is asked alone, so a private registry never falls through to a public one; pnpm's own registry counts as part of the set only while what it names, read through `pnpm config get registry` before each plan, is npm's own registry or one of the fallbacks, and is otherwise asked alone as a private one. A registry pnpm's own configuration already names is asked once. The lookup runs `pnpm view` with `--registry` and without pnpm's own retries, so a dead registry is reported within `inspectTimeoutMs` and the next one is asked; pnpm prints its refusal as JSON on stdout, which is read like stderr. The install keeps pnpm's retry settings. `registries()` answers the configured set and what pnpm names, for a picker. The [registry Agent Note](../../../.agents/notes/implemented/architecture/2026-09-18-plugin-install-registries.md) owns the rationale. The browser-safe `@deepseek-ai/dsh-plugin-manager/registry` entry exports `OFFICIAL_NPM_REGISTRY` and `NPMMIRROR_REGISTRY` for consumers that identify those public registries. -`installBundle` accepts a caller-generated `requestId`, under which `plugin-manager/install-log` streams each pnpm run's output and `plugin-manager/install-state` announces `installing`, `cancelling`, and `applying`; `installing` is announced once per registry asked, with the `attempt`'s registry, position, and the plan's length. The install asks the same plan as the lookup, from `options.registry`, running `pnpm add` with `--registry` and restoring the profile files between attempts; it moves on for the failures another registry can change and stops at any other, and at a failure whose error line names the host a git or tarball spec is fetched from, which no registry stands in for; `failedAt` says which of the two the last failed run could not reach. `cancelInstall(requestId)` stops the run and answers `cancelled` only after the Git check or pnpm exited and the files are back, `too-late` once the bundle is being applied, and `not-running` for any other id; the install call then reports `application: 'cancelled'`. A run that fails, is cancelled, or adds a package without a bundle patch restores `package.json` and `pnpm-lock.yaml` as they were; `packageResult.kind` classifies the last run from its exit and output, `registries` names every registry asked, and `bundle` names the package a finished run added. `listBundles` carries each bundle's one-liner (the package `description`), the rows its patch declares with their live entries, and the built-in rows it overrides; it lists the profile's own bundles, the bundles the installation supplies, and a selected name without a bundle patch as a `not-bundle` problem, while an unselected plain dependency is left out. A bundle the launcher's `OPTIONAL_BUNDLES` names is `optional`: shipped switched off for the person to turn on, never removable, and selected by no shipped template ([rationale](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)). Every completed operation emits `plugin-manager/changed`; a patch generation applied outside the manager, by HMR's watcher after a CLI or hand edit, announces nothing, so the page learns of it on its next read. +`installBundle` accepts a caller-generated `requestId`, under which `plugin-manager/install-log` streams each pnpm run's output and `plugin-manager/install-state` announces `installing`, `cancelling`, and `applying`; `installing` is announced once per registry asked, with the `attempt`'s registry, position, and the plan's length. The install asks the same plan as the lookup, from `options.registry`, running `pnpm add` with `--registry` and restoring the profile files between attempts; it moves on for the failures another registry can change and stops at any other, and at a failure whose error line names the host a git or tarball spec is fetched from, which no registry stands in for; `failedAt` says which of the two the last failed run could not reach. `cancelInstall(requestId)` stops the run and answers `cancelled` only after the Git check or pnpm exited and the files are back, `too-late` once the bundle is being applied, and `not-running` for any other id; the install call then reports `application: 'cancelled'`. A run that fails, is cancelled, or adds a package without a bundle patch restores `package.json` and `pnpm-lock.yaml` as they were; `packageResult.kind` classifies the last run from its exit and output, `registries` names every registry asked, `bundle` names the package a finished run added, and `version` its manifest version, which can differ from the version `inspect` read, for example when pnpm's `minimumReleaseAge` holds back a recent release. `listBundles` carries each bundle's one-liner (the package `description`), the rows its patch declares with their live entries, and the built-in rows it overrides; it lists the profile's own bundles, the bundles the installation supplies, and a selected name without a bundle patch as a `not-bundle` problem, while an unselected plain dependency is left out. A bundle the launcher's `OPTIONAL_BUNDLES` names is `optional`: shipped switched off for the person to turn on, never removable, and selected by no shipped template ([rationale](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)). Every completed operation emits `plugin-manager/changed`; a patch generation applied outside the manager, by HMR's watcher after a CLI or hand edit, announces nothing, so the page learns of it on its next read. `waitForInstall(requestId)` lets a client recover a lost response by waiting for the active installation, including its non-cancellable application phase. It returns the same outcome as the original call, or `null` if the request is not active. Completed results are not retained; `null` establishes neither success nor cancellation. -When pnpm 11 blocks dependency scripts, the failed installation reports every pending package name in the profile under `pendingBuilds`, including names left by earlier attempts; a failed run restores `package.json` and `pnpm-lock.yaml` but deliberately not `pnpm-workspace.yaml`, where pnpm records them. The Web plugin page offers **Allow these scripts and retry**; the tool can grant permission on the user's behalf through `approvedBuilds` on `install_bundle`, after the user approves those scripts in the conversation. The service validates pending names; it does not verify conversation approval. Approval persists by package name in this profile, permits commands with the host user's permissions, and survives another installation failure. Only currently undecided names can be approved; existing denials and wildcard rules cannot be overridden through this action. Approval rejects YAML anchors or aliases inside `allowBuilds`. Retry preserves the original activation choice. +When pnpm 11 blocks dependency scripts, the failed installation reports every pending package name in the profile under `pendingBuilds`, including names left by earlier attempts and names the run reported as ignored. pnpm writes those entries into `pnpm-workspace.yaml` only when it can prompt, so the manager records the reported names itself; a failed run restores `package.json` and `pnpm-lock.yaml` but deliberately not `pnpm-workspace.yaml`. The Web plugin page offers **Allow these scripts and retry**; the tool can grant permission on the user's behalf through `approvedBuilds` on `install_bundle`, after the user approves those scripts in the conversation. The service validates pending names; it does not verify conversation approval. Approval persists by package name in this profile, permits commands with the host user's permissions, and survives another installation failure. Only currently undecided names can be approved; existing denials and wildcard rules cannot be overridden through this action. Approval rejects YAML anchors or aliases inside `allowBuilds`. Retry preserves the original activation choice. ### Version compatibility and exemptions @@ -89,9 +97,13 @@ The CLI exposes `dsh plugin --profile version-exemptions`, `allow-vers
Implementation internals — click to expand -The service and `dsh plugin` share the package operations in [operations.ts](src/operations.ts). The launcher supplies the current profile; [DSH HMR](../hmr/README.md) serializes module reloads, file watching and management writes. Each refresh re-reads bundle selection and patch layers, updates the original root Include, and awaits removed plugin resources as well as the remaining Loader tree. CLI and service operations share the profile manifest writer lock to prevent concurrent package and manifest writes. HMR does not acquire that lock. Pnpm runs outside the HMR queue; installation selects the bundle after pnpm succeeds, while removal deselects and unloads the bundle before pnpm runs. A service run whose captured output stays silent for `idleTimeoutMs` is terminated, reports `timedOut` alongside its exit status, is classified `timeout` whatever status the signal left behind, and is not retried on the next registry, which bounds how long one operation can hold the profile lock; the CLI inherits the terminal, captures no output, and stays unbounded for its operator to interrupt. A run completes when its process exits, and the pipes then drain under a bounded grace period, so a descendant that inherited them cannot hold the operation open. The diagnostic file is closed before the call settles, including preparation or spawn failures. A terminated run stops its whole process tree and waits for it, because a lifecycle script outlives the pnpm process that started it (issue #4981). Each operation records the pnpm run it starts in `.plugin-manager/run.json` and removes the record when the run ends. A lock whose holder process exited is taken over by the next writer, but that process's pnpm tree can still be running, so an operation that finds a record waits up to five seconds for the recorded run to stop and otherwise fails with a diagnostic that names the process and the record, without running pnpm. Dependency-only changes do not trigger configuration reloads. +The service and `dsh plugin` share the package operations in [operations.ts](src/operations.ts). The launcher supplies the current profile; [DSH HMR](../hmr/README.md) serializes module reloads, file watching and management writes. Each configuration refresh re-reads bundle selection and patch layers, updates the original root Include, and awaits removed plugin resources as well as the remaining Loader tree. CLI and service operations share the profile manifest writer lock to prevent concurrent package and manifest writes. HMR does not acquire that lock. Pnpm runs outside the HMR queue; installation selects the bundle after pnpm succeeds, while removal deselects and unloads the bundle before pnpm runs. -Results contain the last attempted stage, target, saved-state change, application status and error codes. Web dictionaries render management text; pnpm and Loader diagnostics remain unmodified. Unrelated pre-existing inactive entries return warnings; new or changed failures and inactive explicit enablement targets fail the operation. A failed or cancelled installation restores the manifest and lockfile it snapshotted before pnpm ran ([rationale](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md)); a failed removal retains its partial changes and diagnostics. Installations are tracked by request id until their call settles, so a cancellation names one run and joins its settlement without taking the profile lock. The CLI inherits authentication variables and terminal descriptors; service operations use a scrubbed environment and captured output. No invariant companion is published because the manager reads files and Loader state directly and owns no independent state projection. +Installing a new package, enabling a bundle, disabling it with HMR after its plugins stop, and a successful removal publish the profile's runtime resolution. Shared dependencies may pass to another bundle's declaration when their normalized directory, version, and scope stay unchanged. Without HMR, a deselected startup bundle still runs: while any such bundle is absent from the saved selection, subsequent package operations retain the entire current table and report `restart-required`. Other new installations and removals of non-running bundles can still publish without HMR; activation waits for restart. Overwriting an installed package publishes nothing and reports `restart-required` ([decision](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.md)). + +A service run whose captured output stays silent for `idleTimeoutMs` is terminated, reports `timedOut` alongside its exit status, is classified `timeout` whatever status the signal left behind, and is not retried on the next registry, which bounds how long one operation can hold the profile lock; the CLI inherits the terminal, captures no output, and stays unbounded for its operator to interrupt. A run completes when its process exits, and the pipes then drain under a bounded grace period, so a descendant that inherited them cannot hold the operation open. The diagnostic file is closed before the call settles, including preparation or spawn failures. A terminated run stops its whole process tree and waits for it, because a lifecycle script outlives the pnpm process that started it (issue #4981). Each operation records the pnpm run it starts in `.plugin-manager/run.json` and removes the record when the run ends. A lock whose holder process exited is taken over by the next writer, but that process's pnpm tree can still be running, so an operation that finds a record waits up to five seconds for the recorded run to stop and otherwise fails with a diagnostic that names the process and the record, without running pnpm. Dependency-only changes do not trigger configuration reloads. + +Results contain the last attempted stage, target, saved-state change, application status and error codes. Web dictionaries render management text; pnpm and Loader diagnostics remain unmodified. Unrelated pre-existing inactive entries return warnings; new or changed failures and inactive explicit enablement targets fail the operation. A failed or cancelled package run, or failed bundle validation, restores the manifest and lockfile snapshotted before pnpm ran ([rationale](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.md)); later failures retain saved changes as described in [Failure behavior](#failure-behavior). Installations are tracked by request id until their call settles, so a cancellation names one run and joins its settlement without taking the profile lock. The CLI inherits authentication variables and terminal descriptors; service operations use a scrubbed environment and captured output.
@@ -129,7 +141,7 @@ Tool results append to the transcript. Enabling or disabling other tools can cha - Web approves the entire displayed pending group; it has no per-package selection. - Package replacements require restarting the process to load a fresh JavaScript module generation. - Startup-only profiles cannot remove packages used to start the current process; stop it and use `dsh plugin`. -- The manager cannot disable its own management components, change another profile, or edit an agent preset's composition. +- The manager cannot disable its own management components, change another profile, or directly edit individual rows inside an agent preset. - A failed removal may leave dependencies partially changed, and a failed or cancelled installation can leave downloaded files under `node_modules` or the pnpm store. Inactive dependencies with missing files remain removable. Diagnostic logs remain under the profile's `.plugin-manager/logs` directory. - Management results describe Host activation. Browser synchronization failures appear separately in the Settings plugin list. - Desktop package operations remain owned by the Desktop shell. @@ -143,9 +155,10 @@ Failures preserve completed steps and report the actual remaining state. Profile |---|---| | Install: pnpm or bundle validation fails | Restore `package.json` and `pnpm-lock.yaml` as snapshotted before pnpm ran; files pnpm downloaded may remain. Report installation failure. | | Enable: saving selection or loading fails | Keep the installed dependency and any saved selection. Report enablement failure; allow repair, disablement or removal. | +| Resolution publication after a successful package operation fails | Keep completed disk changes and the previous runtime table. Report `application: 'failed'` with the publication diagnostic; pnpm's zero exit code does not mean publication succeeded. | | Remove: any step fails | Stop at the failed step. Preserve completed changes, retain remaining dependencies for retry, and report removal failure. Do not re-enable the bundle. | -Installation finishes after pnpm and bundle validation succeed; subsequent enablement failure does not undo installation. Removal proceeds in order: remove the bundle from `dsh.profile.bundles`, unload its runtime contributions, then run `pnpm remove`. A failed step prevents subsequent steps. +Installation finishes after pnpm and bundle validation succeed; subsequent publication or enablement failure does not undo installation. Removal proceeds in order: remove the bundle from `dsh.profile.bundles`, unload its runtime contributions, run `pnpm remove`, then publish runtime resolution unless a deselected startup bundle still runs without HMR. A failed step prevents subsequent steps; publication failure after removal does not restore deleted packages. A selected name that neither the profile's dependencies nor the installation holds, such as a retired bundle, is also removable; its removal stops after deselection and runs no pnpm. Restoration rewrites only the two snapshotted files; user-authored patch configuration, application data, diagnostic logs and files pnpm downloaded remain untouched, and the next package operation prunes packages no manifest references. diff --git a/packages/boot/plugin-manager/README.zh.md b/packages/boot/plugin-manager/README.zh.md index 451dc0e0ea..73463641ee 100644 --- a/packages/boot/plugin-manager/README.zh.md +++ b/packages/boot/plugin-manager/README.zh.md @@ -41,21 +41,29 @@ kind: "package-reference" 已选择但无法加载的组合包仍会出现在 `listBundles` 中,并携带 `error`;`enabled` 表示保存的选择,不代表加载成功。插件页面显示错误并允许取消选择。损坏的组合包无法启用。管理组合包的文件变得不可读后仍受保护。 +限定到 preset 的组合包行同时携带声明的 `rowId` 和外层 `preset` 目标。管理器读取声明,不单独组合该 bundle,因此 Off 状态下仍能列出这些行。可选的 `composition` 状态来自当前 preset 清单;这些行没有可编辑的根条目 `entryId`,其 `preset-managed` 只读原因指引用户通过预设或组合包进行配置。启用根据注册的声明行检查所有受影响的 preset,包括注册表保留而未抛出的故障;无关且未变化的 preset 故障作为警告返回。即使没有 HMR,组合包开关也会校验保存后的组合;组合失败会保留已保存的选择并立即返回诊断。当前或被保留的 preset 代仍使用贡献行(按定义 preset 的行、子行 ID 和解析后的模块匹配),或属于组合包自身的任意模块时,Remove 都会拒绝。这项保护在 Off 后仍持续,直到这些代释放模块;无关行使用共享依赖不会阻止移除。匿名作用域插入行受到相同保护。模块文件缺失不会导致另一贡献项被误认为匹配;文件不可用时,相同的保留声明仍受保护。 + `listBundles` 为各组合包及其声明的插件行提供可选的展示 `meta`,包括已禁用的组合包。Client 从这些值中选择语言。单独的 `description` 字段是该组合包原始的 `package.json.description`;元信息诊断不会阻止管理操作。`source` 以 `pnpm add` 接受的 spec 给出 profile 依赖:git 地址或 URL 按记录给出,但去掉 http(s) URL 的 user information(查询字符串保持原样);`file:` 或 `link:` 路径按 profile 目录解析为绝对路径,`~` 展开为主目录;注册表版本范围、标签或别名接在包名的 `@` 之后,以别名安装的包的 spec 也是如此。安装提供的组合包没有该字段,即使 profile 也依赖它,因为实际加载的是安装中的副本。`plugin_manager` 工具的列表结果不包含 UI 展示元信息。 +`listBundles` 将随附可选组合包和按需目录项标为 `official`,与包的安装状态独立。`availability` 表示可读取的安装随附包、profile 包,或 `missing`;按需目录项不计入未声明的传递依赖副本。`installed` 仅表示 profile 是否声明该依赖。尚未安装的按需条目携带内嵌的本地化元信息,以及 Host 生成、包含当前 DSH 版本与安装 spec 的 `installTarget`,无需访问注册表。profile 已声明但文件不可读的包保留依赖声明,并展示原有错误。 + `inspect(spec, options)` 在任何东西安装之前读出 spec 指向什么:注册表包名通过 `pnpm view` 询问注册表,在 profile 目录中运行,因而与安装使用同样的代理与认证设置;绝对路径读取其 `package.json`;git 地址或 tarball 只答复自己的形式和它被拉取的 `host`。答复携带名称、版本、描述、该包是否声明组合包,以及作答的 `registry`,否则给出 `problem`:`invalid-spec`、`already-installed`、`not-found`、`not-a-package`、`not-a-bundle`、`network` 或 `unknown`,并附上问过的 `registries`。调用方的 `signal` 或 `inspectTimeoutMs` 会结束查询。 +已发布的安装提供 `package@<当前 DSH 版本>`。源码 Web 与开发 Desktop 提供 `link:<工作区包的绝对目录>`,目录来自当前 CLI 的安装位置,包括其开发符号链接。包名通过工作区清单解析;当前工作目录和预发布版本后缀不用于选择 checkout。包、bundle patch 或已构建入口缺失时报告诊断,不回退到注册表。使用 `pnpm install` 和 `pnpm run build` 准备 checkout。链接包使用该 checkout 的依赖与 peer;代码改变后仍需重新构建并重启。即使版本相同,注册表副本或其他 checkout 的链接也可能需要替换。 + +官方按需安装与显式更新调用和其他组合包相同的 `installBundle` 操作。注册表目标使用 `saveExact: true`;开发目标保留普通 `link:` 依赖。更新传入当前 `enabled` 选择状态;替换已有依赖会报告 `restart-required`。关闭仅取消选择配置层,移除使用原有移除检查。安装失败恢复 profile 清单和锁文件;安装成功后激活失败可能保留已保存的依赖和选择。原有 peer 兼容性检查继续生效,不增加仅针对官方条目的版本准入规则。 + `installBundle` 在启动 pnpm 前通过 `git ls-remote` 检查 GitHub 仓库,使用 profile 目录及安装器的 Git 与代理配置。`githubConnectionTimeoutMs` 默认为 5000 毫秒,只限制这次检查,不限制包下载或构建。检查禁用凭据助手和认证提示;只有网络失败与超时会停止安装,通过现有失败类型与诊断日志返回,并标记 `failedAt: 'spec-host'`。认证、仓库查找及其他失败继续交给 pnpm,包括其 HTTPS 到 SSH 的回退。取消安装或销毁管理器会停止检查及其子进程。注册表包、本地路径、压缩包和其他 Git 主机跳过此检查。仓库可达后,下载或组合包验证仍可能失败。 注册表按顺序询问。计划从 `options.registry` 开始,否则从配置的 `registry`(`null` 即 pnpm 自身配置指定的那个)开始,并在一个注册表不可达、超时或答复没有这个包或版本(尚未同步的镜像会如此)时继续问 `fallbackRegistries`。配置集合之外的注册表只问它自己,因而私有源永远不会落到公共源;pnpm 自身的注册表只在它指向的地址(每次做计划前经 `pnpm config get registry` 读出)是 npm 官方源或某个备选源时才算集合成员,否则视为私有源只问它自己。pnpm 自身配置已经指向的注册表只问一次。查询以 `--registry` 运行 `pnpm view` 且不带 pnpm 自身的重试,所以死掉的注册表会在 `inspectTimeoutMs` 内报告并转问下一个;pnpm 把拒绝以 JSON 打印在 stdout,读法与 stderr 相同。安装保留 pnpm 的重试设置。`registries()` 把配置集合和 pnpm 指向的地址答复给选择器。[注册表 Agent Note](../../../.agents/notes/implemented/architecture/2026-09-18-plugin-install-registries.zh.md) 拥有理由。 可在浏览器使用的 `@deepseek-ai/dsh-plugin-manager/registry` 入口导出 `OFFICIAL_NPM_REGISTRY` 和 `NPMMIRROR_REGISTRY`,供消费方识别这两个公共注册表。 -`installBundle` 接受调用方生成的 `requestId`,`plugin-manager/install-log` 在其下流式转发每次 pnpm 运行的输出,`plugin-manager/install-state` 通告 `installing`、`cancelling` 与 `applying`;每问一个注册表通告一次 `installing`,`attempt` 带上这次的注册表、序号和计划长度。安装沿用查询的计划,从 `options.registry` 开始,以 `--registry` 运行 `pnpm add`,两次尝试之间恢复 profile 文件;换一个注册表能改变的失败才转问下一个,其他失败即停,错误行点名 git 或 tarball spec 自身拉取主机的失败也停,因为没有注册表能替代那台主机;`failedAt` 说明最后一次失败的运行连不上的是二者中的哪一个。`cancelInstall(requestId)` 停止运行,只在 Git 检查或 pnpm 退出且文件恢复后答复 `cancelled`,组合包已在应用时答复 `too-late`,其他 id 答复 `not-running`;安装调用随后报告 `application: 'cancelled'`。失败、被取消或装入了没有组合包 patch 的包的运行,会把 `package.json` 与 `pnpm-lock.yaml` 恢复原样;`packageResult.kind` 按退出方式与输出对最后一次运行分类,`registries` 列出问过的每个注册表,`bundle` 给出完成的运行新增的包。`listBundles` 携带每个组合包的一句话简介(包的 `description`)、其 patch 声明的行及其存活条目,以及它覆盖的内置行;它列出 profile 自己的组合包、安装提供的组合包,以及被选中却没有组合包 patch 的名字(作为 `not-bundle` 问题),未选中的普通依赖不列出。启动器的 `OPTIONAL_BUNDLES` 点名的组合包是 `optional`:随安装提供、默认关闭、由用户开启,永不可卸载,也不被任何随附模板选中([理由](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md))。每个完成的操作都会发出 `plugin-manager/changed`;在管理器之外应用的一代 patch(HMR 监视到 CLI 或手工编辑后)不发通知,页面要到下一次读取才知道。 +`installBundle` 接受调用方生成的 `requestId`,`plugin-manager/install-log` 在其下流式转发每次 pnpm 运行的输出,`plugin-manager/install-state` 通告 `installing`、`cancelling` 与 `applying`;每问一个注册表通告一次 `installing`,`attempt` 带上这次的注册表、序号和计划长度。安装沿用查询的计划,从 `options.registry` 开始,以 `--registry` 运行 `pnpm add`,两次尝试之间恢复 profile 文件;换一个注册表能改变的失败才转问下一个,其他失败即停,错误行点名 git 或 tarball spec 自身拉取主机的失败也停,因为没有注册表能替代那台主机;`failedAt` 说明最后一次失败的运行连不上的是二者中的哪一个。`cancelInstall(requestId)` 停止运行,只在 Git 检查或 pnpm 退出且文件恢复后答复 `cancelled`,组合包已在应用时答复 `too-late`,其他 id 答复 `not-running`;安装调用随后报告 `application: 'cancelled'`。失败、被取消或装入了没有组合包 patch 的包的运行,会把 `package.json` 与 `pnpm-lock.yaml` 恢复原样;`packageResult.kind` 按退出方式与输出对最后一次运行分类,`registries` 列出问过的每个注册表,`bundle` 给出完成的运行新增的包,`version` 给出它的清单版本,它可能与 `inspect` 读到的版本不同,例如 pnpm 的 `minimumReleaseAge` 暂缓了最近的发布。`listBundles` 携带每个组合包的一句话简介(包的 `description`)、其 patch 声明的行及其存活条目,以及它覆盖的内置行;它列出 profile 自己的组合包、安装提供的组合包,以及被选中却没有组合包 patch 的名字(作为 `not-bundle` 问题),未选中的普通依赖不列出。启动器的 `OPTIONAL_BUNDLES` 点名的组合包是 `optional`:随安装提供、默认关闭、由用户开启,永不可卸载,也不被任何随附模板选中([理由](../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md))。每个完成的操作都会发出 `plugin-manager/changed`;在管理器之外应用的一代 patch(HMR 监视到 CLI 或手工编辑后)不发通知,页面要到下一次读取才知道。 `waitForInstall(requestId)` 让客户端在响应丢失后等待活动安装的结果,包括不可取消的应用阶段。它返回与原调用相同的结果,请求不在活动中时返回 `null`。已完成的结果不保留;`null` 不表示成功或已取消。 -pnpm 11 拦下依赖脚本时,失败的安装在 `pendingBuilds` 里报告 profile 中所有待决定的包名,包括先前尝试留下的;失败的运行会恢复 `package.json` 与 `pnpm-lock.yaml`,但有意不恢复 pnpm 记录这些名字的 `pnpm-workspace.yaml`。Web 插件页提供**允许这些脚本并重试**;工具可以在用户于对话中批准这些脚本后,通过 `install_bundle` 的 `approvedBuilds` 代为授权。服务只校验待决定的名字,不核实对话中的批准。授权按包名保存在当前 profile,允许以宿主用户的权限执行命令,并在再次安装失败后保留。只能批准当前未决定的名字;已有的拒绝与通配规则不能通过此操作覆盖。`allowBuilds` 里出现 YAML 锚点或别名时拒绝授权。重试保留原来的启用选择。 +pnpm 11 拦下依赖脚本时,失败的安装在 `pendingBuilds` 里报告 profile 中所有待决定的包名,包括先前尝试留下的,以及本次运行报告为已忽略的。pnpm 只在能够交互提示时才把这些条目写入 `pnpm-workspace.yaml`,因此由管理器自行记录本次运行报告的包名;失败的运行会恢复 `package.json` 与 `pnpm-lock.yaml`,但有意不恢复 `pnpm-workspace.yaml`。Web 插件页提供**允许这些脚本并重试**;工具可以在用户于对话中批准这些脚本后,通过 `install_bundle` 的 `approvedBuilds` 代为授权。服务只校验待决定的名字,不核实对话中的批准。授权按包名保存在当前 profile,允许以宿主用户的权限执行命令,并在再次安装失败后保留。只能批准当前未决定的名字;已有的拒绝与通配规则不能通过此操作覆盖。`allowBuilds` 里出现 YAML 锚点或别名时拒绝授权。重试保留原来的启用选择。 ### 版本兼容性与豁免 @@ -89,9 +97,13 @@ CLI 提供 `dsh plugin --profile version-exemptions`、`allow-version
实现细节——点击展开 -服务与 `dsh plugin` 共用 [operations.ts](src/operations.ts) 中的包管理操作。启动器提供当前 profile;[DSH HMR](../hmr/README.zh.md) 串行执行模块重载、文件监听和管理写入。每次刷新重新读取组合包选择与 patch 层,更新原有根 Include,并等待已移除插件释放资源及剩余 Loader 树稳定。CLI 与 service 操作共用 profile manifest 写锁,防止并发包操作和 manifest 写入。HMR 不获取该锁。pnpm 在 HMR 队列之外执行;安装在 pnpm 成功后选入组合包,删除则在执行 pnpm 前取消选入并完成卸载。service 运行若在 `idleTimeoutMs` 内没有任何捕获输出即被终止,与退出状态一并报告 `timedOut`,不论信号留下什么退出状态都归类为 `timeout`,且不再转问下一个注册表,因此单次操作占用 profile 锁的时长有上界;CLI 继承终端、不捕获输出,因此不受此上界约束,由操作者中断。运行以进程退出为完成点,随后只在一个有界的宽限窗口内排空管道,因此继承管道的孙进程无法让操作挂起。诊断文件在调用结算前关闭,包括准备或创建进程失败的情况。被终止的运行会停止整棵进程树并等待其消失,因为生命周期脚本的存活时间超过启动它的 pnpm 进程(issue #4981)。每个操作把它启动的 pnpm 运行记录在 `.plugin-manager/run.json` 中,并在运行结束时删除该记录。持有者进程已退出的锁会被下一个写入方接管,但该进程的 pnpm 进程树可能仍在运行,因此发现记录的操作最多等待五秒让记录中的运行停止,否则不运行 pnpm,并以指明该进程与记录文件的诊断失败。仅依赖字段变化不会触发配置重载。 +服务与 `dsh plugin` 共用 [operations.ts](src/operations.ts) 中的包管理操作。启动器提供当前 profile;[DSH HMR](../hmr/README.zh.md) 串行执行模块重载、文件监听和管理写入。每次配置刷新重新读取组合包选择与 patch 层,更新原有根 Include,并等待已移除插件释放资源及剩余 Loader 树稳定。CLI 与 service 操作共用 profile manifest 写锁,防止并发包操作和 manifest 写入。HMR 不获取该锁。pnpm 在 HMR 队列之外执行;安装在 pnpm 成功后选入组合包,删除则在执行 pnpm 前取消选入并完成卸载。 -结果包含最后尝试的阶段、目标、磁盘变化、应用状态和错误码。Web 词典呈现管理文案;pnpm 与 Loader 的诊断保持原样。无关的已有故障作为警告返回;新出现、配置变化后的故障,以及显式启用目标未激活,都会使操作失败。失败或被取消的安装会恢复 pnpm 运行前快照的 manifest 与 lockfile([理由](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md));失败的删除保留部分改动和诊断。安装按 request id 跟踪到调用结束,因此取消只针对一次运行,并且不取 profile 锁就能等待它结束。CLI 继承认证环境和终端描述符;service 使用清理后的环境并捕获输出。管理器直接读取文件和 Loader 状态,不维护第二份目标状态注册表,因此不发布单独的运行时不变式伴生入口。 +安装新包、启用组合包、有 HMR 时停用组合包并等插件退出后、以及删除成功后,都会发布 profile 的 runtime resolution。共享依赖的规范化目录、版本和作用域不变时,可以改由另一个组合包声明。没有 HMR 时,已取消选入的启动组合包仍在运行:只要保存的选择列表中缺少任何这样的组合包,后续包操作就保留整张当前表,并报告 `restart-required`。其他新安装和未运行组合包的删除仍可在没有 HMR 时发布;激活等待重启。覆盖已安装的包不发布,并报告 `restart-required`([决策](../../../.agents/notes/implemented/architecture/2026-09-30-profile-package-refresh-and-manifest-invalidation.zh.md))。 + +service 运行若在 `idleTimeoutMs` 内没有任何捕获输出即被终止,与退出状态一并报告 `timedOut`,不论信号留下什么退出状态都归类为 `timeout`,且不再转问下一个注册表,因此单次操作占用 profile 锁的时长有上界;CLI 继承终端、不捕获输出,因此不受此上界约束,由操作者中断。运行以进程退出为完成点,随后只在一个有界的宽限窗口内排空管道,因此继承管道的孙进程无法让操作挂起。诊断文件会在调用结束前关闭,包括准备或创建进程失败的情况。被终止的运行会停止整棵进程树并等待其消失,因为生命周期脚本的存活时间超过启动它的 pnpm 进程(issue #4981)。每个操作把它启动的 pnpm 运行记录在 `.plugin-manager/run.json` 中,并在运行结束时删除该记录。持有者进程已退出的锁会被下一个写入方接管,但该进程的 pnpm 进程树可能仍在运行,因此发现记录的操作最多等待五秒让记录中的运行停止,否则不运行 pnpm,并以指明该进程与记录文件的诊断失败。仅依赖字段变化不会触发配置重载。 + +结果包含最后尝试的阶段、目标、磁盘变化、应用状态和错误码。Web 词典呈现管理文案;pnpm 与 Loader 的诊断保持原样。无关的已有故障作为警告返回;新出现、配置变化后的故障,以及显式启用目标未激活,都会使操作失败。包管理运行失败或被取消,或组合包校验失败时,会恢复 pnpm 运行前快照的 manifest 与 lockfile([理由](../../../.agents/notes/implemented/architecture/2026-09-15-guided-plugin-installation.zh.md));后续失败按[失败行为](#failure-behavior)所述保留已保存的改动。安装按 request id 跟踪到调用结束,因此取消只针对一次运行,并且不取 profile 锁就能等待它结束。CLI 继承认证环境和终端描述符;service 使用清理后的环境并捕获输出。管理器直接读取文件和 Loader 状态,不维护第二份目标状态注册表,因此不发布单独的运行时不变式伴生入口。
@@ -129,7 +141,7 @@ CLI 提供 `dsh plugin --profile version-exemptions`、`allow-version - Web 一次批准显示出来的整组待决定包,没有逐包选择。 - 替换已有包后需要重启进程,以加载新的 JavaScript 模块版本。 - 仅启动时加载的 profile 不能删除当前进程启动时使用的包;停止进程后使用 `dsh plugin`。 -- 管理器不能关闭自身所需的管理组件、修改其他 profile 或编辑 agent 预设组合。 +- 管理器不能关闭自身所需的管理组件、修改其他 profile 或直接编辑 agent 预设内部的单个条目。 - 失败的删除可能留下部分依赖改动,失败或被取消的安装可能在 `node_modules` 或 pnpm 缓存中留下已下载文件。文件缺失的未启用依赖仍可删除。诊断日志保留在 profile 的 `.plugin-manager/logs` 目录中。 - 管理结果描述 Host 激活状态。浏览器同步失败会在设置的插件列表中单独显示。 - Desktop 包管理操作仍由 Desktop shell 负责。 @@ -143,9 +155,10 @@ CLI 提供 `dsh plugin --profile version-exemptions`、`allow-version |---|---| | 安装:pnpm 执行或组合包校验失败 | 恢复 pnpm 运行前快照的 `package.json` 与 `pnpm-lock.yaml`;pnpm 已下载的文件可能保留。报告安装失败。 | | 启用:保存选择项或加载失败 | 保留已安装的依赖和已保存的选择项。报告启用失败,允许修正、停用或卸载。 | +| 包操作成功后解析表发布失败 | 保留已完成的磁盘改动和上一张运行时表。报告 `application: 'failed'` 及发布诊断;pnpm 的零退出码不代表发布成功。 | | 卸载:任一步失败 | 停在失败步骤,保留已完成的改动和待重试删除的依赖,报告卸载失败。不重新启用组合包。 | -pnpm 执行和组合包校验成功即完成安装,后续启用失败不撤销安装。卸载依次执行:从 `dsh.profile.bundles` 移除组合包、卸载运行时贡献、执行 `pnpm remove`。任一步失败都不继续执行后续步骤。 +pnpm 执行和组合包校验成功即完成安装,后续发布或启用失败不撤销安装。卸载依次执行:从 `dsh.profile.bundles` 移除组合包、卸载运行时贡献、执行 `pnpm remove`,然后发布 runtime resolution,除非没有 HMR 且已取消选入的启动组合包仍在运行。任一步失败都不继续执行后续步骤;删除后的发布失败不会恢复已删除的包。 profile 依赖和安装都不持有的已选名称(例如已下线的组合包)同样可卸载;其卸载在取消选择后结束,不运行 pnpm。 恢复只重写这两份快照文件;用户编写的 patch 配置、应用数据、诊断日志以及 pnpm 已下载的文件保持原样,没有 manifest 引用的包由下一次包操作清理。 diff --git a/packages/boot/plugin-manager/package.json b/packages/boot/plugin-manager/package.json index 6e6104d60a..4a83a1781a 100644 --- a/packages/boot/plugin-manager/package.json +++ b/packages/boot/plugin-manager/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-plugin-manager", "description": "Current-profile plugin and bundle management shared by dsh CLI, Web and agent tools", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -59,7 +59,7 @@ "@deepseek-ai/dsh-app-boot": "workspace:*", "@deepseek-ai/dsh-atomic-write": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", - "execa": "^10.0.0", + "execa": "^10.0.1", "yaml": "^2.9.0", "zod": "^4.4.3", "@deepseek-ai/dsh-util-values": "workspace:*" @@ -75,7 +75,8 @@ "@deepseek-ai/dsh-brand": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", - "@deepseek-ai/dsh-user-approval": "workspace:*" + "@deepseek-ai/dsh-user-approval": "workspace:*", + "@deepseek-ai/dsh-agent-preset-registry": "workspace:*" }, "peerDependenciesMeta": { "@deepseek-ai/dsh-hmr": { @@ -83,9 +84,13 @@ }, "@deepseek-ai/dsh-user-approval": { "optional": true + }, + "@deepseek-ai/dsh-agent-preset-registry": { + "optional": true } }, "devDependencies": { + "chokidar": "^4.0.3", "@deepseek-ai/dsh-package-manifest": "workspace:*", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/cordis-plugin-loader": "workspace:~", @@ -102,6 +107,10 @@ "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-projection": "workspace:*", "@deepseek-ai/dsh-sandbox": "workspace:*", - "@deepseek-ai/dsh-user-approval": "workspace:*" + "@deepseek-ai/dsh-user-approval": "workspace:*", + "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", + "@deepseek-ai/dsh-agent-preset": "workspace:*", + "@deepseek-ai/dsh-scope": "workspace:*", + "@deepseek-ai/dsh-session-title": "workspace:*" } } diff --git a/packages/boot/plugin-manager/src/build-approval.ts b/packages/boot/plugin-manager/src/build-approval.ts index 5ea1569292..75600f8c22 100644 --- a/packages/boot/plugin-manager/src/build-approval.ts +++ b/packages/boot/plugin-manager/src/build-approval.ts @@ -5,6 +5,12 @@ import { isAlias, isMap, isNode, isScalar, parseDocument, visit } from 'yaml' import { ManagementFailure } from './failure.ts' import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write' +/** Value pnpm leaves for a build script that still needs a decision. */ +const UNDECIDED_BUILD = 'set this to true or false' + +/** Where a non-interactive run names the build scripts it skipped. */ +const IGNORED_BUILDS = /\bIgnored build scripts: (.+)/ + async function readPolicy(dir: string) { let text: string try { text = await readFile(join(dir, 'pnpm-workspace.yaml'), 'utf8') } @@ -22,18 +28,43 @@ async function readPolicy(dir: string) { throw new Error('allowBuilds must not contain YAML anchors or aliases') } }) + const keys = isMap(builds) ? builds.items.flatMap(({ key }) => + isScalar(key) && typeof key.value === 'string' ? [key.value] : []) : [] const pending = isMap(builds) ? builds.items.flatMap(({ key, value }) => isScalar(key) && typeof key.value === 'string' && !/[*?]/.test(key.value) - && isScalar(value) && value.value === 'set this to true or false' ? [key.value] : []) : [] - return { document, pending } + && isScalar(value) && value.value === UNDECIDED_BUILD ? [key.value] : []) : [] + return { document, pending, keys } } -/** Read package names left undecided by pnpm 11, including after installation cleanup. +/** + * Record the build scripts a failed run reported as ignored, then list every name awaiting a decision. + * + * pnpm writes those policy entries itself only when it can prompt, and the + * manager always runs it non-interactively, so the run's own report is the + * record that survives. * @param dir Current profile directory. + * @param output Captured output of the failed run. * @returns Exact package names awaiting a build decision; wildcard rules are excluded. */ -export async function readPendingBuilds(dir: string): Promise { - return (await readPolicy(dir)).pending +export async function recordPendingBuilds(dir: string, output: string): Promise { + const { document, pending, keys } = await readPolicy(dir) + const known = new Set(keys) + const added = ignoredBuildNames(output).filter(name => !known.has(name)) + if (added.length === 0) return pending + for (const name of added) document.setIn(['allowBuilds', name], UNDECIDED_BUILD) + await writeFileAtomic(join(dir, 'pnpm-workspace.yaml'), String(document), { mode: 0o600 }) + return [...pending, ...added] +} + +/** + * Read the package names a pnpm run reported as ignored build scripts. + * @param output Captured output of the run. + * @returns Exact names in report order; wildcard patterns are excluded. + */ +export function ignoredBuildNames(output: string): string[] { + const reported = IGNORED_BUILDS.exec(output)?.[1] + if (reported === undefined) return [] + return reported.split(',').map(name => name.trim()).filter(name => name !== '' && !/[*?]/.test(name)) } /** Persist approval without running scripts; the caller holds the profile manifest lock. diff --git a/packages/boot/plugin-manager/src/bundle-rows.ts b/packages/boot/plugin-manager/src/bundle-rows.ts new file mode 100644 index 0000000000..9a150f3bd7 --- /dev/null +++ b/packages/boot/plugin-manager/src/bundle-rows.ts @@ -0,0 +1,54 @@ +/** Raw bundle declarations remain inspectable while their prerequisite layers are disabled. */ +import { existsSync } from 'node:fs' +import { dirname, isAbsolute, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' +import { realModuleFile, profilePatchPreset, type ProfilePatch, type PluginPackages } from '@deepseek-ai/dsh-app-boot' + +/** A declared child row and the outer preset operation that contributes it. */ +export interface BundleDeclaration { + readonly row: EntryOptions + readonly preset?: string +} + +/** Read inserted rows without applying a bundle against an incomplete profile. + * @param patches Parsed bundle patches, whose module paths are already anchored. + * @returns Declarations in patch order, including nested groups. + */ +export function bundleDeclarations(patches: readonly ProfilePatch[]): BundleDeclaration[] { + const result: BundleDeclaration[] = [] + const visit = (rows: EntryOptions[], preset?: string): void => { + for (const row of rows) { + result.push({ row, ...preset === undefined ? {} : { preset } }) + if (row.group && Array.isArray(row.config)) visit(row.config as EntryOptions[], preset) + } + } + for (const patch of patches) { + if (patch.insert !== undefined) visit(patch.insert, profilePatchPreset(patch)) + } + return result +} + +/** Locate the package owning a bare import or filesystem module without importing its code. + * @param specifier Configured module name or anchored file URL. + * @param base Declaring Loader tree's resolution URL. + * @param packages Optional profile package lookup for bare specifiers. + * @returns Canonical package manifest path, or undefined for builtins, missing modules, and unowned files. + */ +export function bundleModuleOwner( + specifier: string, base: string, packages?: Pick, +): string | undefined { + const known = packages?.packageOf(specifier, base) + if (known !== undefined) return realModuleFile(known.manifestPath) + if (!specifier.startsWith('file:') && !specifier.startsWith('.') && !isAbsolute(specifier)) return undefined + const file = isAbsolute(specifier) ? specifier : fileURLToPath(new URL(specifier, base)) + if (!existsSync(file)) return undefined + let dir = dirname(realModuleFile(file)) + while (true) { + const manifest = join(dir, 'package.json') + if (existsSync(manifest)) return realModuleFile(manifest) + const parent = dirname(dir) + if (parent === dir) return undefined + dir = parent + } +} diff --git a/packages/boot/plugin-manager/src/index.ts b/packages/boot/plugin-manager/src/index.ts index 10ca06c160..5c77c0a97c 100644 --- a/packages/boot/plugin-manager/src/index.ts +++ b/packages/boot/plugin-manager/src/index.ts @@ -7,25 +7,29 @@ import { pathToFileURL } from 'node:url' import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write' import { Context } from '@deepseek-ai/cordis' import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' -import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include' +import type { PluginInventorySnapshot } from '@deepseek-ai/dsh-host-plugin-inventory/types' +import type { AgentPreset } from '@deepseek-ai/dsh-agent-preset-registry' import z from '@deepseek-ai/schemastery' import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol' import { pluginEntryId, readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory' import { - readPluginMeta, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, + readPluginMeta, resolvePluginResource, realModuleFile, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, reconcileProfilePatches, readProfilePatches, OPTIONAL_BUNDLES, bundlePatchPaths, evaluatePluginCompatibility, readProfileCompatibility, readProfileVersionExemptions, - setProfileVersionExemption, PROFILE_COMPATIBILITY_FILENAME, + setProfileVersionExemption, PROFILE_COMPATIBILITY_FILENAME, profilePatchPreset, type ProfilePatch, + OFFICIAL_ON_DEMAND_CATALOG, getDshRuntimeVersion, } from '@deepseek-ai/dsh-app-boot' import type {} from '@deepseek-ai/dsh-hmr' import type { ProfileContext, ProfileManifest } from '@deepseek-ai/dsh-app-boot' import { bundleManifest, readProfileRegistry, registryArguments, runProfilePnpm, saveManifest, viewProfilePackage } from './operations.ts' import { classifyInstallFailure } from './install-failure.ts' +import { officialBundleInstallTarget } from './official-install-target.ts' import { dependencySpec, InvalidInstallSpecError, parseInstallSpec, type ParsedInstallSpec } from './install-spec.ts' import { attributeFailure, normalizeRegistry, NPMMIRROR_REGISTRY, registryPlan } from './registry.ts' import { writePluginEnabled } from './patch.ts' import { incompatiblePlugin, ManagementFailure } from './failure.ts' -import { approveBuilds, readPendingBuilds } from './build-approval.ts' +import { approveBuilds, recordPendingBuilds } from './build-approval.ts' +import { bundleDeclarations, bundleModuleOwner, type BundleDeclaration } from './bundle-rows.ts' import { checkGithubConnection } from './github-connection.ts' import type { BundleInfo, BundleRowInfo, ChangeResult, InspectOptions, InstallBundleOptions, ManagementError, PackageResult, PluginChange, @@ -271,57 +275,84 @@ export class PluginManager extends TypertRemoteService { }) } - /** Read the profile's installed bundles, the bundles this dsh installation supplies, and the selected names that are not bundles. + /** Read installed, installation-provided, and offline Official catalog bundles, plus selected non-bundle names. * A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected. * @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional * display metadata, activation selections, whether the installation offers the bundle, and removal availability. */ @Remote - listBundles(): Promise { + async listBundles(): Promise { + const inventory = await readPluginInventory(this.ctx) + const definitions = await this.ctx.get('agentPresets')?.list() ?? [] const manifest = readProfileManifest('dsh', this.profile.dir) const exemptions = readProfileVersionExemptions(this.profile.dir) const selected = manifest.dsh?.profile?.bundles ?? [] const recorded = manifest.dependencies ?? {} const dependencies = Object.keys(recorded) const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8')) as InstallationManifest - const names = [...new Set([...selected, ...dependencies, ...Object.keys(installation.dependencies ?? {})])] + const catalog = new Map(OFFICIAL_ON_DEMAND_CATALOG.map(entry => [entry.packageName, entry])) + const names = [...new Set([...selected, ...dependencies, ...Object.keys(installation.dependencies ?? {}), ...catalog.keys()])] const bundles: BundleInfo[] = [] + const runtimeVersion = getDshRuntimeVersion() for (const name of names) { const installed = dependencies.includes(name) const optional = OPTIONAL_BUNDLES.includes(name) - const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name) - // Bundle resolution reads the installation first, so a profile dependency the installation manifest also names, - // like one it forbids removing, is not the loaded copy. - const sourceOf = (packageName?: string): { source?: string } => - removable ? { source: dependencySpec(name, recorded[name] as string, this.profile.dir, packageName) } : {} const enabled = selected.includes(name) + const shipped = Object.hasOwn(installation.dependencies ?? {}, name) + const offered = catalog.get(name) + const official = optional || offered !== undefined + let target: Pick = {} + let targetError: ManagementError | undefined + if (offered !== undefined) { + try { target = { installTarget: officialBundleInstallTarget(name, this.profile.installAnchor, runtimeVersion) } } + catch (error) { targetError = managementError(error) } + } + const catalogMeta = offered === undefined ? {} : { meta: offered.meta } + if (offered !== undefined && !installed && !shipped) { + bundles.push({ name, official, availability: 'missing', ...catalogMeta, ...target, enabled, installed, optional, + removable: enabled, rows: [], overrides: [], + ...targetError !== undefined ? { error: targetError } : enabled ? { error: { code: 'not-bundle' } } : {} }) + continue + } + // Installation-owned copies remain protected even if a profile also declares their names. + const owned = installed && !shipped + const removable = owned || (enabled && !installed && !shipped) + const sourceOf = (packageName?: string): { source?: string } => + owned ? { source: dependencySpec(name, recorded[name] as string, this.profile.dir, packageName) } : {} const readOnlyReason = this.protectsManager(name) ? 'management-required' as const : undefined + let availability: BundleInfo['availability'] = 'missing' + let version: string | undefined try { const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor) + availability = shipped ? 'installation' : 'profile' if (info === undefined) { - if (enabled) bundles.push({ name, ...sourceOf(), enabled, installed, optional, - removable: removable && readOnlyReason === undefined, - ...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: { code: 'not-bundle' }, rows: [], overrides: [] }) + if (enabled || offered !== undefined) bundles.push({ name, official, availability, ...catalogMeta, ...target, + ...sourceOf(), enabled, installed, optional, removable: removable && readOnlyReason === undefined, + ...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: targetError ?? { code: 'not-bundle' }, rows: [], overrides: [] }) continue } + version = info.version const compatibility = evaluatePluginCompatibility(info, exemptions) if (compatibility !== undefined && !compatibility.exempted) throw new ManagementFailure('incompatible-version', [incompatiblePlugin(compatibility)]) const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir) - const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, 'package.json')).href) - bundles.push({ name, ...(info.version === undefined ? {} : { version: info.version }), + const meta = offered?.meta ?? readPluginMeta(info.name ?? name, pathToFileURL(join(dir, 'package.json')).href) + bundles.push({ name, official, availability, ...target, ...(version === undefined ? {} : { version }), ...(info.description === undefined || info.description === '' ? {} : { description: info.description }), ...meta === undefined ? {} : { meta }, ...sourceOf(info.name), enabled, installed, optional, removable: removable && readOnlyReason === undefined, ...(readOnlyReason === undefined ? {} : { readOnlyReason }), - ...this.declaredRows(name, info) }) + ...this.declaredRows(name, info, inventory, definitions), + ...targetError === undefined ? {} : { error: targetError } }) } catch (error) { - if (enabled || installed) { - bundles.push({ name, ...sourceOf(), enabled, installed, optional, removable: removable && readOnlyReason === undefined, - ...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: managementError(error), rows: [], overrides: [] }) + if (enabled || installed || optional || offered !== undefined) { + bundles.push({ name, official, availability, ...catalogMeta, ...target, ...(version === undefined ? {} : { version }), + ...sourceOf(), enabled, installed, optional, removable: removable && readOnlyReason === undefined, + ...(readOnlyReason === undefined ? {} : { readOnlyReason }), + error: targetError ?? managementError(error), rows: [], overrides: [] }) } } } - return Promise.resolve(bundles) + return bundles } /** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names. @@ -449,7 +480,11 @@ export class PluginManager extends TypertRemoteService { setBundleEnabled(name: string, enabled: boolean): Promise { return this.change(result => this.configure(async () => { await this.selectBundle(name, enabled) - result.warnings = await this.reload(enabled ? this.bundleRows(name).map(row => row.id) : []) + if (enabled) await this.refreshPackages() + const rows = enabled ? this.bundleRows(name) : [] + result.warnings = await this.reload(rows.filter(row => row.preset === undefined).map(({ row }) => row.id), + enabled ? this.bundlePresets(name) : []) + if (!enabled && this.ownerContext.get('hmr') !== undefined) await this.refreshPackages() }), { stage: 'enable', target: name, enabled }, 'bundle') } @@ -461,7 +496,7 @@ export class PluginManager extends TypertRemoteService { * `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay. * @param spec One package spec, including local paths relative to the invocation directory. * @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names, - * the pending build scripts to allow for this profile before pnpm runs, and the registry asked first. + * the pending build scripts to allow, whether to save an exact dependency, and the registry asked first. * @returns Package-manager diagnostics, the registries asked, and the observed activation outcome. */ @Remote @@ -483,6 +518,7 @@ export class PluginManager extends TypertRemoteService { const files = await this.readRestoredFiles() const before = readProfileManifest('dsh', this.profile.dir).dependencies ?? {} let name: string + let version: string | undefined try { result.registries = [] const connection = checkGithubConnection(parsedForRegistry(spec), this.profile.dir, { @@ -509,7 +545,7 @@ export class PluginManager extends TypertRemoteService { if (stopped()) throw new InstallCancelledError() result.registries.push(registry) announce('installing', { registry, index: index + 1, total: plan.length }) - run = await this.runPnpm(['add', spec, ...registryArguments(registry)], control.abort.signal, requestId) + run = await this.runPnpm(['add', spec, ...options?.saveExact === true ? ['--save-exact'] : [], ...registryArguments(registry)], control.abort.signal, requestId) result.packageResult = run if (stopped()) throw new InstallCancelledError() // A compatibility refusal is the package's own answer, so no other registry is asked. @@ -532,8 +568,8 @@ export class PluginManager extends TypertRemoteService { const succeeded = run.exitCode === 0 && run.timedOut !== true if (succeeded) delete result.failedAt if (!succeeded) { - // pnpm-workspace.yaml is not restored, so the names pnpm left undecided there can be offered for approval. - try { result.pendingBuilds = await readPendingBuilds(this.profile.dir) } + // pnpm-workspace.yaml is not restored, so the names it left undecided, or reported as ignored, are offered for approval. + try { result.pendingBuilds = await recordPendingBuilds(this.profile.dir, run.output) } catch (error) { this.ownerContext.logger.warn('Could not read pending build approvals after pnpm failed', error) } @@ -542,7 +578,11 @@ export class PluginManager extends TypertRemoteService { const after = readProfileManifest('dsh', this.profile.dir).dependencies ?? {} const installed = Object.keys(after).filter(name => before[name] !== after[name]) // Registry retries can retain the saved range after a partial installation. - if (installed.length === 0) installed.push(...Object.keys(after).filter(name => spec === name || spec.startsWith(`${name}@`))) + if (installed.length === 0) { + const parsed = parsedForRegistry(spec) + const localName = parsed.kind === 'path' ? readProfileManifest('dsh', parsed.path).name : undefined + installed.push(...Object.keys(after).filter(name => name === localName || spec === name || spec.startsWith(`${name}@`))) + } const target = installed[0] if (installed.length !== 1 || target === undefined) throw new ManagementFailure('ambiguous-install') name = target @@ -552,6 +592,7 @@ export class PluginManager extends TypertRemoteService { const compatibility = evaluatePluginCompatibility(manifest, readProfileVersionExemptions(this.profile.dir)) if (compatibility !== undefined && !compatibility.exempted) throw new ManagementFailure('incompatible-version', [incompatiblePlugin(compatibility)]) for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches('dsh', file) + version = manifest.version } catch (error) { // pnpm has exited by now, so the files it rewrote go back as they were. await this.restoreFiles(files) @@ -560,12 +601,14 @@ export class PluginManager extends TypertRemoteService { control.phase = 'applying' announce('applying') result.bundle = name + if (version !== undefined) result.version = version result.target = name result.stage = 'enable' return this.configure(async () => { if (options?.enabled !== false) await this.selectBundle(name, true) if (Object.hasOwn(before, name)) return 'restart-required' - if (options?.enabled !== false) result.warnings = await this.reload() + await this.refreshPackages() + if (options?.enabled !== false) result.warnings = await this.reload([], this.bundlePresets(name)) }) }, { stage: 'install', target: spec, enabled: options?.enabled !== false }, 'install') control.result = result @@ -599,70 +642,104 @@ export class PluginManager extends TypertRemoteService { return { status: 'cancelled' } } - /** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path. - * @param name Installed dependency name. + /** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path; a selected name no + * dependency holds is only deselected. + * @param name Installed dependency or selected bundle name. * @returns Removal diagnostics and the remaining profile state. */ @Remote removeBundle(name: string): Promise { return this.change(async (result) => { - await this.configure(async () => { + const installed = await this.configure(async () => { const bundle = (await this.listBundles()).find(item => item.name === name) if (bundle === undefined || !bundle.removable) throw new ManagementFailure('not-removable') + const contributions = bundle.error === undefined ? this.bundleRows(name) : [] if (this.ownerContext.get('hmr') === undefined && (this.profile.startedBundles.includes(name) - || (bundle.error === undefined && this.bundleRows(name).some(row => [...this.ctx.loader.entries()] - .some(entry => entry.options.id === row.id && entry.fiber !== undefined))))) { + || contributions.some(({ row, preset }) => preset === undefined && [...this.ctx.loader.entries()] + .some(entry => entry.options.id === row.id && entry.fiber !== undefined)) + || this.retainedBundleModules(name, contributions))) { throw new ManagementFailure('stop-profile') } - const contributions = bundle.error === undefined ? this.bundleRows(name) : [] if (bundle.enabled) { await this.selectBundle(name, false) result.warnings = await this.reload() } if ([...this.ctx.loader.entries()].some(entry => entry.fiber?.uid != null - && contributions.some(row => row.id === entry.options.id && row.name === entry.options.name))) { + && contributions.some(({ row, preset }) => preset === undefined + && row.id === entry.options.id && row.name === entry.options.name)) + || this.retainedBundleModules(name, contributions)) { throw new ManagementFailure('bundle-in-use') } + return bundle.installed }) + if (!installed) return result.packageResult = await this.runPnpm(['remove', name]) if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) { throw new Error(result.packageResult.output) } + await this.configure(() => this.refreshPackages()) }, { stage: 'remove', target: name }, 'remove') } - /** The rows a bundle's patch inserts and the existing rows it changes; an unreadable patch throws. */ - private declaredRows(name: string, info: ProfileManifest): Pick { - const bundle = info.dsh?.bundle - /* v8 ignore next -- bundleManifest answers only manifests that declare a patch */ - if (bundle === undefined) return { rows: [], overrides: [] } + /** The rows a bundle declares, joined by outer preset and child id where applicable. */ + private declaredRows(name: string, info: ProfileManifest, inventory: PluginInventorySnapshot, definitions: readonly AgentPreset[]): Pick { + const patches = this.bundlePatches(name, info) const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir) - const patches: PatchOptions[] = bundlePatchPaths(dir, bundle).flatMap(file => loadOverlayPatches('dsh', file)) - // One entry per row id: the Loader keeps a single entry for an id, whichever layer declared it last. - const live = new Map() - for (const entry of this.ctx.loader.entries()) { - /* v8 ignore next -- the Loader gives every entry an id before it is listed */ - if (typeof entry.options.id === 'string') live.set(entry.options.id, { - entryId: pluginEntryId(entry.id), baseUrl: entry.parent.tree.ctx.baseUrl, - }) - } + const entries = [...this.ctx.loader.entries()] const rows: BundleRowInfo[] = [] const packages = this.ctx.get('pluginPackages') - for (const row of flatten(composeEntries([patches.filter(item => item.insert !== undefined)]))) { + for (const { row, preset } of bundleDeclarations(patches)) { if (typeof row.id !== 'string' || typeof row.name !== 'string') continue - const active = live.get(row.id) - const entryId = active?.entryId - const base = active?.baseUrl ?? pathToFileURL(join(dir, 'package.json')).href + const matches = preset === undefined ? entries.filter(entry => entry.options.id === row.id && entry.options.name === row.name) : [] + const active = matches.length === 1 ? matches[0] : undefined + const base = active?.parent.tree.ctx.baseUrl ?? pathToFileURL(join(dir, 'package.json')).href const meta = packages?.metaOf(row.name, base) + const presetId = preset === undefined ? undefined : definitions.find(item => item.definitionEntryId === preset)?.id + const composition = inventory.agentPresets?.find(item => item.id === presetId)?.rows + .find(item => item.entryId === row.id && item.moduleName === row.name) rows.push({ rowId: row.id, moduleName: row.name, - ...entryId === undefined ? {} : { entryId }, ...meta === undefined ? {} : { meta } }) + ...preset === undefined ? {} : { preset, readOnlyReason: 'preset-managed' as const }, + ...composition === undefined ? {} : { composition: { enabled: composition.enabled, fiberPhase: composition.fiberPhase } }, + ...active === undefined ? {} : { entryId: pluginEntryId(active.id) }, ...meta === undefined ? {} : { meta } }) } - const declared = new Set(rows.map(row => row.rowId)) - const overrides = [...new Set(patches.flatMap(item => - item.insert === undefined && typeof item.id === 'string' && !declared.has(item.id) ? [item.id] : []))] + const key = (preset: string | undefined, id: string): string => JSON.stringify([preset, id]) + const declared = new Set(rows.map(row => key(row.preset, row.rowId))) + const overrides = [...new Set(patches.flatMap((item) => { + const preset = profilePatchPreset(item) + return item.insert === undefined && typeof item.id === 'string' && !declared.has(key(preset, item.id)) + ? [preset === undefined ? item.id : `${preset}/${item.id}`] : [] + }))] return { rows, overrides } } + /** Retained revisions still hold module code even after a bundle has been deselected. */ + private retainedBundleModules(name: string, declarations: readonly BundleDeclaration[]): boolean { + const presets = this.ctx.get('agentPresets') + if (presets === undefined || declarations.length === 0) return false + const manifest = join(resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir), 'package.json') + const base = pathToFileURL(manifest).href + const owner = realModuleFile(manifest) + const moduleKey = (specifier: string, parentURL: string): string | undefined => { + if (specifier.startsWith('cordis:')) return specifier + try { return realModuleFile(resolvePluginResource(specifier, parentURL)) } + catch (error) { + // An externally removed module cannot match another resolved file; identical declarations remain protected below. + if (['ENOENT', 'ENOTDIR', 'ERR_MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(String((error as NodeJS.ErrnoException).code))) return undefined + throw error + } + } + return presets.inspectCompositions().some(composition => composition.modules.some((module) => { + const moduleBase = module.baseUrl ?? base + if (bundleModuleOwner(module.moduleName, moduleBase, this.ctx.get('pluginPackages')) === owner) return true + return module.useHostBase && declarations.some(({ row, preset }) => { + if (preset === undefined || preset !== composition.definitionEntryId || row.id !== module.entryId) return false + const declared = moduleKey(row.name, base) + const retained = moduleKey(module.moduleName, moduleBase) + return declared === undefined || retained === undefined ? row.name === module.moduleName : declared === retained + }) + })) + } + /** Run one pnpm command in the profile, streaming its output as install-log chunks. */ private async runPnpm( args: readonly string[], signal?: AbortSignal, requestId?: PluginInstallRequestId, @@ -740,21 +817,31 @@ export class PluginManager extends TypertRemoteService { if (enabled) this.protectsManager(name) } - private bundleRows(name: string): EntryOptions[] { - const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor) + private bundlePatches(name: string, known?: ProfileManifest): ProfilePatch[] { + const info = known ?? bundleManifest(name, this.profile.dir, this.profile.installAnchor) if (info?.dsh?.bundle === undefined) return [] const dir = resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir) - return flatten(composeEntries([bundlePatchPaths(dir, info.dsh.bundle).flatMap(file => loadOverlayPatches('dsh', file))])) + return bundlePatchPaths(dir, info.dsh.bundle).flatMap(file => loadOverlayPatches('dsh', file)) + } + + private bundleRows(name: string): BundleDeclaration[] { return bundleDeclarations(this.bundlePatches(name)) } + + private bundlePresets(name: string): string[] { + return [...new Set(this.bundlePatches(name).flatMap((patch) => { + const preset = profilePatchPreset(patch) + return preset === undefined ? [] : [preset] + }))] } private protectsManager(name: string): boolean { if (this.managementBundles.has(name)) return true - let rows: EntryOptions[] + let rows: BundleDeclaration[] try { rows = this.bundleRows(name) } catch (_error) { // Unreadable bundles contribute no new rows; listBundles reports their diagnostics. return false } - const protectedBundle = rows.some(row => protectedModules.has(row.name) || `include:${row.id}` === this.ownerEntryId) + const protectedBundle = rows.some(({ row, preset }) => preset === undefined + && (protectedModules.has(row.name) || `include:${row.id}` === this.ownerEntryId)) if (protectedBundle) this.managementBundles.add(name) return protectedBundle } @@ -765,9 +852,42 @@ export class PluginManager extends TypertRemoteService { return hmr === undefined ? apply() : hmr.runExclusive(apply) } - private async reload(requiredIds: readonly string[] = []): Promise { - if (this.ownerContext.get('hmr') === undefined) return [] - return reconcileProfilePatches(this.ownerContext.root, readProfilePatches('dsh', this.profile), 'dsh', requiredIds) + private async refreshPackages(): Promise { + if (this.ownerContext.get('hmr') === undefined) { + const selected = readProfileManifest('dsh', this.profile.dir).dsh?.profile?.bundles ?? [] + // Deselected startup bundles still run without HMR and need the existing package table. + if (this.profile.startedBundles.some(name => !selected.includes(name))) return + } + await this.ownerContext.get('pluginPackages')?.refresh() + } + + /** Configuration identity distinguishes a changed failed preset from an unrelated pre-existing warning. */ + private presetConfiguration(preset: AgentPreset): string { + return JSON.stringify([...this.ctx.loader.entries()].flatMap((entry) => { + const config = entry.options.config as { id?: unknown; plugins?: unknown } | undefined + return entry.options.id === preset.definitionEntryId && config?.id === preset.id && Array.isArray(config.plugins) ? [config] : [] + })) + } + + private async reload(requiredIds: readonly string[] = [], requiredPresets: readonly string[] = []): Promise { + if (this.ownerContext.get('hmr') === undefined) { + composeEntries([readProfilePatches('dsh', this.profile)]) + return [] + } + const presets = this.ctx.get('agentPresets') + const before = new Map((await presets?.list() ?? []).map(row => + [row.id, { broken: row.broken, config: this.presetConfiguration(row) }])) + const warnings = await reconcileProfilePatches( + this.ownerContext.root, readProfilePatches('dsh', this.profile), 'dsh', [...requiredIds, ...requiredPresets], + ) + const after = await this.ctx.get('agentPresets')?.list() ?? [] + const required = new Set(after.filter(row => row.definitionEntryId !== undefined && requiredPresets.includes(row.definitionEntryId)) + .map(row => row.id)) + const broken = after.filter(row => row.broken !== undefined) + const failed = broken.filter(row => required.has(row.id) || before.get(row.id)?.broken !== row.broken + || before.get(row.id)?.config !== this.presetConfiguration(row)) + if (failed.length > 0) throw new Error(failed.map(row => `agent preset ${row.id}: ${row.broken}`).join('\n')) + return [...warnings, ...broken.map(row => `agent preset ${row.id}: ${row.broken}`)] } private async change( diff --git a/packages/boot/plugin-manager/src/official-install-target.ts b/packages/boot/plugin-manager/src/official-install-target.ts new file mode 100644 index 0000000000..8b5aa5f1e8 --- /dev/null +++ b/packages/boot/plugin-manager/src/official-install-target.ts @@ -0,0 +1,58 @@ +/** Select Official bundle packages from the running installation without importing provider code. */ +import { existsSync, globSync, readFileSync, realpathSync, statSync } from 'node:fs' +import { dirname, join, resolve } from 'node:path' +import { bundlePatchPaths, type ProfileManifest } from '@deepseek-ai/dsh-app-boot' + +interface WorkspaceManifest extends ProfileManifest { + main?: string +} + +function manifestAt(file: string): WorkspaceManifest { + return JSON.parse(readFileSync(file, 'utf8')) as WorkspaceManifest +} + +/** + * Offer a source checkout link or an exact published package version. Source installations are + * identified by the real CLI manifest and its unpacked workspace dependencies, including Desktop's + * development symlink. An incomplete checkout is an error, never a registry fallback. + * @param name - Official catalog package name. + * @param installAnchor - Running application's DSH package.json, possibly reached through a symlink. + * @param version - Running DSH version used by its release family. + * @returns An ordinary package-manager spec and its package version. + * @throws when a source installation lacks its workspace package, bundle patch, or built runtime. + */ +export function officialBundleInstallTarget(name: string, installAnchor: string, version: string): { spec: string; version: string } { + const anchor = realpathSync(installAnchor) + const installation = manifestAt(anchor) + const source = installation.name === '@deepseek-ai/dsh' + && Object.values(installation.dependencies ?? {}).some(value => value.startsWith('workspace:')) + if (!source) return { spec: `${name}@${version}`, version } + const root = resolve(dirname(anchor), '../..') + if (anchor !== join(root, 'apps', 'cli', 'package.json') + || !existsSync(join(root, 'package.json')) + || manifestAt(join(root, 'package.json')).name !== '@deepseek-ai/dsh-root') { + throw new Error(`Official bundle ${name}: source DSH installation ${anchor} requires its complete workspace checkout`) + } + const matches = globSync('packages/*/*/package.json', { cwd: root }) + .map(file => ({ file: join(root, file), manifest: manifestAt(join(root, file)) })) + .filter(entry => entry.manifest.name === name) + const selected = matches[0] + if (matches.length !== 1 || selected === undefined) { + throw new Error(`Official bundle ${name}: expected one workspace package in ${root}, found ${matches.length}; restore the checkout and run pnpm install`) + } + const { file, manifest } = selected + const directory = realpathSync(dirname(file)) + if (manifest.version !== version || manifest.private === true || manifest.dsh?.bundle === undefined) { + throw new Error(`Official bundle ${name}: ${file} must declare a public bundle at DSH version ${version}`) + } + const main = typeof manifest.main === 'string' ? resolve(directory, manifest.main) : undefined + if (main === undefined || !existsSync(main) || !statSync(main).isFile()) { + throw new Error(`Official bundle ${name}: built runtime is missing in ${directory}; run pnpm install and pnpm run build in ${root}`) + } + for (const patch of bundlePatchPaths(directory, manifest.dsh.bundle)) { + if (!existsSync(patch) || !statSync(patch).isFile()) { + throw new Error(`Official bundle ${name}: bundle patch ${patch} is missing; restore the checkout`) + } + } + return { spec: `link:${directory}`, version } +} diff --git a/packages/boot/plugin-manager/src/operations.ts b/packages/boot/plugin-manager/src/operations.ts index a6fe676544..5cc612f95b 100644 --- a/packages/boot/plugin-manager/src/operations.ts +++ b/packages/boot/plugin-manager/src/operations.ts @@ -4,17 +4,17 @@ import { existsSync, readFileSync } from 'node:fs' import { mkdir, mkdtemp, open, rm } from 'node:fs/promises' import { join, resolve } from 'node:path' import { execa } from 'execa' -import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader' import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write' import { DEFAULT_PROFILE_BUNDLES, bundlePatchPaths, initProfile, PROFILE_TEMPLATES, readProfileManifest, - resolveBundleDir, resolveProfileDir, loadOverlayPatches, composeEntries, readProfileVersionExemptions, + resolveBundleDir, resolveProfileDir, loadOverlayPatches, readProfileVersionExemptions, evaluatePluginCompatibility, pluginCompatibilityWarning, type ProfileManifest, } from '@deepseek-ai/dsh-app-boot' import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess' import { parseInstallSpec } from './install-spec.ts' import { awaitTreeGone, leadsOwnGroup, treeAlive, type RunTree } from './run-tree.ts' import { incompatiblePlugin } from './failure.ts' +import { bundleDeclarations } from './bundle-rows.ts' import type { IncompatiblePlugin, PackageResult, Registry } from './types.ts' export { setProfileVersionExemption, readProfileVersionExemptions } from '@deepseek-ai/dsh-app-boot' @@ -251,15 +251,11 @@ function bundleComponentManifests(manifest: ProfileManifest, dir: string, anchor if (bundle === undefined) return [] const patches = bundlePatchPaths(dir, bundle).flatMap(file => loadOverlayPatches('dsh', file)) const names = new Set() - const visit = (rows: EntryOptions[]) => { - for (const row of rows) { - if (row.group && Array.isArray(row.config)) visit(row.config as EntryOptions[]) - if (typeof row.name !== 'string' || row.name.startsWith('.') || row.name.startsWith('/') || row.name.includes(':')) continue - const parts = row.name.split('/') - names.add(parts.slice(0, row.name.startsWith('@') ? 2 : 1).join('/')) - } + for (const { row } of bundleDeclarations(patches)) { + if (typeof row.name !== 'string' || row.name.startsWith('.') || row.name.startsWith('/') || row.name.includes(':')) continue + const parts = row.name.split('/') + names.add(parts.slice(0, row.name.startsWith('@') ? 2 : 1).join('/')) } - visit(composeEntries([patches.filter(patch => patch.insert !== undefined)])) return [...names].flatMap((name) => { let packageDir: string try { packageDir = resolveBundleDir('dsh', name, anchor, dir) } catch (error) { diff --git a/packages/boot/plugin-manager/src/patch.ts b/packages/boot/plugin-manager/src/patch.ts index 5776b586fa..3a4aa69f09 100644 --- a/packages/boot/plugin-manager/src/patch.ts +++ b/packages/boot/plugin-manager/src/patch.ts @@ -4,9 +4,9 @@ import { isMap, isSeq, parseDocument } from 'yaml' import { loadOptionalPatches } from '@deepseek-ai/dsh-app-boot' import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write' -/** Replace the last matching override or append one after existing insertions. +/** Replace the last matching Host override or append one, preserving preset-scoped operations. * @param filename Current profile patch file. - * @param id Unique composition entry id. + * @param id Unique Host composition entry id. * @param name Module name used to match name-qualified overrides. * @param enabled Desired entry enablement. * @returns Whether the file changed. @@ -28,7 +28,7 @@ export async function writePluginEnabled(filename: string, id: string, name: str loadOptionalPatches('dsh', filename) const items = document.contents.items const target = items.findLast((item, index) => { - if (!isMap(item) || document.getIn([index, 'id']) !== id || item.has('insert')) return false + if (!isMap(item) || document.getIn([index, 'id']) !== id || item.has('insert') || item.has('preset')) return false const expectedName = document.getIn([index, 'name']) return !expectedName || expectedName === name }) diff --git a/packages/boot/plugin-manager/src/tools.ts b/packages/boot/plugin-manager/src/tools.ts index df9b0a200e..29c58e898d 100644 --- a/packages/boot/plugin-manager/src/tools.ts +++ b/packages/boot/plugin-manager/src/tools.ts @@ -21,7 +21,7 @@ export function apply(ctx: Context): void { description: 'List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Every action requires danger-full-access permission or approval for this call. Approval does not change the session permission mode. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. Incompatible DSH peer dependencies block installation and activation. Version exemptions risk crashes and data loss: warn the user and obtain explicit permission for the exact plugin and runtime versions before granting one.', parameters: { action: { type: 'string', required: true, enum: ['list_plugins', 'list_bundles', 'set_plugin', 'set_bundle', 'install_bundle', 'remove_bundle', 'list_version_exemptions', 'set_version_exemption'], description: 'Management operation.' }, - target: { type: 'string', description: 'Plugin entry id, bundle package name, or installation spec, according to action.' }, + target: { type: 'string', description: 'Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle.' }, enabled: { type: 'boolean', description: 'Required for set operations; defaults to true for installation. For set_version_exemption, true grants and false revokes.' }, runtimeVersion: { type: 'string', description: 'For set_version_exemption: exact DSH version from list_version_exemptions. Target must be the manifest package-name@version, not an alias or version range.' }, acceptRisk: { type: 'boolean', description: 'For granting an exemption: true only after warning the user about possible crashes and data loss and receiving explicit permission for this exact plugin/runtime pair. General installation permission is not enough.' }, diff --git a/packages/boot/plugin-manager/src/types.ts b/packages/boot/plugin-manager/src/types.ts index dbc20d9598..725d146c3f 100644 --- a/packages/boot/plugin-manager/src/types.ts +++ b/packages/boot/plugin-manager/src/types.ts @@ -1,12 +1,12 @@ /** Public plugin management records shared with clients. */ import type { Branded } from '@deepseek-ai/dsh-brand' import type { PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest' -import type { PluginInventoryEntry } from '@deepseek-ai/dsh-host-plugin-inventory/types' +import type { PluginInventoryEntry, AgentPresetPluginRow } from '@deepseek-ai/dsh-host-plugin-inventory/types' export type { PluginEntryId } from '@deepseek-ai/dsh-host-plugin-inventory/types' import type { PluginEntryId } from '@deepseek-ai/dsh-host-plugin-inventory/types' /** Reasons a profile control cannot modify its target. */ -export type ReadOnlyReason = 'management-required' | 'unaddressable' +export type ReadOnlyReason = 'management-required' | 'unaddressable' | 'preset-managed' /** A package whose declared DSH peers reject the running DSH version, without an exemption for the exact pair. */ export interface IncompatiblePlugin { @@ -33,8 +33,14 @@ export type PluginInfo = PluginInventoryEntry & ( /** One row a bundle's patch declares, with its live entry while the bundle contributes it. */ export interface BundleRowInfo { - /** The row id as the patch declares it. */ + /** Declared row id, or an internal identity for an anonymous scoped insertion. */ rowId: string + /** Outer preset row id; together with rowId identifies a scoped declaration. */ + preset?: string + /** Why direct row controls are unavailable; scoped rows are configured through their preset or bundle. */ + readOnlyReason?: ReadOnlyReason + /** Current preset composition state; scoped rows have no editable root Loader entry. */ + composition?: Pick /** The module the row names. */ moduleName: string /** Local package display metadata, including rows whose bundle is disabled. */ @@ -43,9 +49,15 @@ export interface BundleRowInfo { entryId?: PluginEntryId } -/** One installed or installation-provided bundle. */ +/** One shipped, profile-installed, or discoverable on-demand bundle. */ export interface BundleInfo { name: string + /** Project-maintained shipped optional bundle or on-demand catalog entry, independent of installation and selection. */ + official: boolean + /** Readable package location; on-demand catalog entries exclude undeclared transitive copies, and declared files may be missing. */ + availability: 'installation' | 'profile' | 'missing' + /** Exact released Host version or same-checkout development link offered for an on-demand catalog entry. */ + installTarget?: { spec: string; version: string } version?: string /** Local display text with available translations or literal fallbacks, or a metadata diagnostic. */ meta?: PluginLocalizedMeta @@ -53,7 +65,7 @@ export interface BundleInfo { description?: string /** Selected in the profile manifest; a load error means its layer was skipped. */ enabled: boolean - /** Whether the profile's own dependencies hold the package; false for a bundle the dsh installation supplies. */ + /** Whether the profile declares this dependency; availability separately reports whether its files can be read. */ installed: boolean /** * Present for a profile dependency the installation does not also supply: the spec `pnpm add` accepts, with local @@ -127,6 +139,8 @@ export interface ChangeResult { packageResult?: PackageResult /** The bundle an installation added, once pnpm and the bundle check accepted it. */ bundle?: string + /** The installed bundle's manifest version, when declared; pnpm's `minimumReleaseAge` can make it older than the newest release. */ + version?: string /** Exact package names awaiting explicit script approval in the profile's pnpm settings, read after a failed run. */ pendingBuilds?: string[] /** Package script permissions saved before this installation attempt. */ @@ -146,6 +160,8 @@ export type PluginInstallRequestId = Branded<'PluginInstallRequestId'> /** Bundle installation defaults to activation; callers that offer cancellation supply their request id. */ export interface InstallBundleOptions { enabled?: boolean + /** Save the resolved dependency version without a range; applies to any bundle installation. */ + saveExact?: boolean requestId?: PluginInstallRequestId /** Explicitly allow these pending packages' scripts for this profile, then install; a name no longer pending refuses the call. */ approvedBuilds?: string[] diff --git a/packages/boot/plugin-manager/tests/build-approval.spec.ts b/packages/boot/plugin-manager/tests/build-approval.spec.ts index 2c46e4da97..f7620aee82 100644 --- a/packages/boot/plugin-manager/tests/build-approval.spec.ts +++ b/packages/boot/plugin-manager/tests/build-approval.spec.ts @@ -4,7 +4,7 @@ import { join } from 'node:path' import { tmpdir } from 'node:os' import { expect, it, onTestFinished } from 'vitest' import { parse } from 'yaml' -import { approveBuilds, readPendingBuilds } from '../src/build-approval.ts' +import { approveBuilds, recordPendingBuilds } from '../src/build-approval.ts' function fixture(text?: string) { const dir = mkdtempSync(join(tmpdir(), 'build-approval-')) @@ -16,12 +16,12 @@ function fixture(text?: string) { it('approves only named pending packages and preserves comments, decisions and settings', async () => { const { dir, filename } = fixture('# profile settings\nother: &unrelated value\ncopy: *unrelated\nnodeLinker: hoisted\nallowBuilds:\n native: set this to true or false\n "@scope/other": set this to true or false\n trusted: true\n denied: false\n "@scope/*": set this to true or false\n') - expect(await readPendingBuilds(dir)).toEqual(['native', '@scope/other']) + expect(await recordPendingBuilds(dir, '')).toEqual(['native', '@scope/other']) await approveBuilds(dir, ['native']) const text = readFileSync(filename, 'utf8') expect(text).toContain('# profile settings') expect(parse(text)).toMatchObject({ nodeLinker: 'hoisted', allowBuilds: { native: true, trusted: true, denied: false } }) - expect(await readPendingBuilds(dir)).toEqual(['@scope/other']) + expect(await recordPendingBuilds(dir, '')).toEqual(['@scope/other']) }) it.each(['missing', 'denied', '*', '--all'])('rejects an unlisted approval atomically: %s', async (name) => { @@ -34,20 +34,20 @@ it.each(['missing', 'denied', '*', '--all'])('rejects an unlisted approval atomi it('preserves pnpm file dependency selectors verbatim', async () => { const name = '@scope/addon@file:../local addon' const { dir, filename } = fixture(`allowBuilds:\n '${name}': set this to true or false\n`) - expect(await readPendingBuilds(dir)).toEqual([name]) + expect(await recordPendingBuilds(dir, '')).toEqual([name]) await approveBuilds(dir, [name]) expect(parse(readFileSync(filename, 'utf8'))).toEqual({ allowBuilds: { [name]: true } }) }) it.each([undefined, '{}\n', 'nodeLinker: hoisted\n', 'allowBuilds: {}\n'])('has no pending approval without pnpm placeholders: %s', async (text) => { const { dir } = fixture(text) - expect(await readPendingBuilds(dir)).toEqual([]) + expect(await recordPendingBuilds(dir, '')).toEqual([]) await approveBuilds(dir, []) }) it.each(['[', '[]\n', 'allowBuilds: false\n'])('rejects malformed workspace settings without rewriting them: %s', async (text) => { const { dir, filename } = fixture(text) - await expect(readPendingBuilds(dir)).rejects.toThrow() + await expect(recordPendingBuilds(dir, '')).rejects.toThrow() await expect(approveBuilds(dir, ['native'])).rejects.toThrow() expect(readFileSync(filename, 'utf8')).toBe(text) }) @@ -55,7 +55,7 @@ it.each(['[', '[]\n', 'allowBuilds: false\n'])('rejects malformed workspace sett it('reports unreadable workspace settings', async () => { const { dir, filename } = fixture() mkdirSync(filename) - await expect(readPendingBuilds(dir)).rejects.toThrow() + await expect(recordPendingBuilds(dir, '')).rejects.toThrow() }) it.each([ @@ -67,3 +67,44 @@ it.each([ await expect(approveBuilds(dir, ['native'])).rejects.toThrow() expect(readFileSync(filename, 'utf8')).toBe(original) }) + +it('records the ignored build scripts a non-interactive run reported', async () => { + const { dir, filename } = fixture('# profile settings\nnodeLinker: hoisted\n') + const output = [ + 'Progress: resolved 2, reused 2, downloaded 0, added 2, done', + '[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: native@1.0.0, @scope/other@file:../other, @scope/*', + '', + 'Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.', + ].join('\n') + + expect(await recordPendingBuilds(dir, output)).toEqual(['native@1.0.0', '@scope/other@file:../other']) + const text = readFileSync(filename, 'utf8') + expect(text).toContain('# profile settings') + expect(parse(text)).toMatchObject({ + nodeLinker: 'hoisted', + allowBuilds: { 'native@1.0.0': 'set this to true or false', '@scope/other@file:../other': 'set this to true or false' }, + }) + expect(await recordPendingBuilds(dir, output)).toEqual(['native@1.0.0', '@scope/other@file:../other']) + await approveBuilds(dir, ['native@1.0.0']) + expect(parse(readFileSync(filename, 'utf8'))).toMatchObject({ allowBuilds: { 'native@1.0.0': true } }) +}) + +it.each([ + ['no report', 'Progress: resolved 1, reused 1, downloaded 0, added 1, done\n'], + ['an empty report', '[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: \n'], + ['only wildcards', '[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @scope/*, native@?\n'], +])('leaves the profile settings untouched for %s', async (_label, output) => { + const original = 'allowBuilds:\n decided: true\n' + const { dir, filename } = fixture(original) + expect(await recordPendingBuilds(dir, output)).toEqual([]) + expect(readFileSync(filename, 'utf8')).toBe(original) +}) + +it('preserves approval keys that are not package names', async () => { + const { dir, filename } = fixture('allowBuilds:\n 1: true\n') + expect(await recordPendingBuilds(dir, '[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: native@1.0.0\n')) + .toEqual(['native@1.0.0']) + expect(parse(readFileSync(filename, 'utf8'))).toMatchObject({ + allowBuilds: { 1: true, 'native@1.0.0': 'set this to true or false' }, + }) +}) diff --git a/packages/boot/plugin-manager/tests/bundle-rows.spec.ts b/packages/boot/plugin-manager/tests/bundle-rows.spec.ts new file mode 100644 index 0000000000..e65959f8d5 --- /dev/null +++ b/packages/boot/plugin-manager/tests/bundle-rows.spec.ts @@ -0,0 +1,60 @@ +/** Declaration identity and module ownership before activating a bundle. */ +import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { expect, it, onTestFinished } from 'vitest' +import { Context } from '@deepseek-ai/cordis' +import { PluginPackages } from '@deepseek-ai/dsh-app-boot' +import { bundleDeclarations, bundleModuleOwner } from '../src/bundle-rows.ts' + +it('reads nested declarations without requiring their target profile or preset', () => { + const row = { id: 'child', name: './child.mjs' } + expect(bundleDeclarations([ + { id: 'absent-group', insert: [row] }, + { preset: 'absent-preset', insert: [{ id: 'group', name: 'cordis:group', group: true, config: [row] }] }, + { preset: 'absent-preset', id: 'other', disabled: true }, + ])).toEqual([ + { row }, + { row: { id: 'group', name: 'cordis:group', group: true, config: [row] }, preset: 'absent-preset' }, + { row, preset: 'absent-preset' }, + ]) +}) + +it('finds canonical owners for bare, relative, absolute, and file URL modules without evaluating them', async () => { + const dir = mkdtempSync(join(tmpdir(), 'bundle-owner-')) + const ctx = new Context() + onTestFinished(async () => { await ctx.fiber.dispose(); rmSync(dir, { recursive: true, force: true }) }) + const packageDir = join(dir, 'node_modules', 'fixture') + mkdirSync(join(packageDir, 'lib'), { recursive: true }) + const manifest = join(packageDir, 'package.json') + writeFileSync(manifest, '{"name":"fixture","version":"1.0.0","exports":"./lib/index.mjs"}\n') + const module = join(packageDir, 'lib', 'index.mjs') + writeFileSync(module, 'throw new Error("must not import ownership probes")\n') + const base = pathToFileURL(join(dir, 'profile.json')).href + await ctx.plugin(PluginPackages) + const expected = realpathSync(manifest) + expect(bundleModuleOwner('fixture', base, ctx.pluginPackages)).toBe(expected) + expect(bundleModuleOwner('fixture/lib/index.mjs', base, ctx.pluginPackages)).toBe(expected) + expect(bundleModuleOwner('./node_modules/fixture/lib/index.mjs', base)).toBe(expected) + expect(bundleModuleOwner(module, base)).toBe(expected) + expect(bundleModuleOwner(pathToFileURL(module).href, base)).toBe(expected) + expect(bundleModuleOwner('cordis:group', base)).toBeUndefined() + expect(bundleModuleOwner('uninstalled', base)).toBeUndefined() + expect(bundleModuleOwner('./absent.mjs', base)).toBeUndefined() + const loose = join(dir, 'loose.mjs') + writeFileSync(loose, '') + expect(bundleModuleOwner(loose, base)).toBeUndefined() + const linked = join(dir, 'linked') + symlinkSync(packageDir, linked, process.platform === 'win32' ? 'junction' : 'dir') + expect(bundleModuleOwner(pathToFileURL(join(linked, 'lib/index.mjs')).href, base)).toBe(expected) + const otherPackage = join(dir, 'other-package') + mkdirSync(otherPackage) + writeFileSync(join(otherPackage, 'package.json'), '{"name":"other-package"}\n') + const otherModule = join(otherPackage, 'foreign.mjs') + writeFileSync(otherModule, 'export function apply() {}\n') + const fileLink = join(packageDir, 'borrowed.mjs') + symlinkSync(otherModule, fileLink, 'file') + expect(bundleModuleOwner(fileLink, base)).toBe(realpathSync(join(otherPackage, 'package.json'))) + +}) diff --git a/packages/boot/plugin-manager/tests/manager.spec.ts b/packages/boot/plugin-manager/tests/manager.spec.ts index fa8b926bae..52cd122dce 100644 --- a/packages/boot/plugin-manager/tests/manager.spec.ts +++ b/packages/boot/plugin-manager/tests/manager.spec.ts @@ -12,7 +12,7 @@ import type { Context } from '@deepseek-ai/cordis' import { afterAll, beforeAll, expect, it, onTestFinished, vi } from 'vitest' import { boot, composeEntries, initProfile, loadProfileDirectory, readProfilePatches, readProfileManifest, - reconcileProfilePatches, OPTIONAL_BUNDLES, PluginPackages, readPluginMeta, getDshRuntimeVersion, + reconcileProfilePatches, OPTIONAL_BUNDLES, OFFICIAL_ON_DEMAND_CATALOG, PluginPackages, readPluginMeta, getDshRuntimeVersion, type ProfileContext, type RuntimeResolution, } from '@deepseek-ai/dsh-app-boot' import PluginManager, { type Config, type PluginChange, type PluginInstallLogChunk, type PluginInstallProgress, type PluginInstallRequestId } from '../src/index.ts' @@ -30,18 +30,24 @@ let restoreGitCommandLineConfig: () => void beforeAll(() => { restoreGitCommandLineConfig = isolateGitCommandLineConfig() }) afterAll(() => { restoreGitCommandLineConfig() }) -async function fixture(reload: 'live' | 'startup' = 'live', overlay = false, prepare?: (ctx: Context) => void, config: Config = {}, packageManager?: ProfileContext['packageManager'], prepareFiles?: (dir: string) => void) { +async function fixture(reload: 'live' | 'startup' = 'live', overlay = false, prepare?: (ctx: Context) => void, config: Config = {}, packageManager?: ProfileContext['packageManager'], prepareFiles?: (dir: string) => void, sourceInstallation = false) { const temporaryHome = mkdtempSync(join(tmpdir(), 'plugin-manager-')) let owner: Context | undefined onTestFinished(async () => { await owner?.fiber.dispose(); rmSync(temporaryHome, { recursive: true, force: true }) }) // pnpm resolves workspace roots through native realpath, including Windows 8.3 aliases. const home = await realpath(temporaryHome) const dir = join(home, 'profiles', 'test') - const anchor = join(home, 'package.json') - writeFileSync(anchor, '{"name":"installation","dependencies":{}}\n') + const anchor = sourceInstallation ? join(home, 'apps', 'cli', 'package.json') : join(home, 'package.json') + if (sourceInstallation) { + mkdirSync(join(home, 'apps', 'cli'), { recursive: true }) + writeFileSync(join(home, 'package.json'), '{"name":"@deepseek-ai/dsh-root"}') + } + writeFileSync(anchor, JSON.stringify(sourceInstallation + ? { name: '@deepseek-ai/dsh', dependencies: { '@deepseek-ai/dsh-app-boot': 'workspace:*' } } + : { name: 'installation', dependencies: {} })) initProfile(dir, ['core', 'extra']) - const bundle = (name: string, rows: unknown[]) => { - const path = join(dir, 'node_modules', name) + const bundle = (name: string, rows: unknown[], packageRoot = dir) => { + const path = join(packageRoot, 'node_modules', name) mkdirSync(path, { recursive: true }) writeFileSync(join(path, 'package.json'), JSON.stringify({ name, version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } })) writeFileSync(join(path, 'cordis.patch.yml'), JSON.stringify([{ insert: rows }])) @@ -294,14 +300,14 @@ it('lists bundle versions and current-profile plugin targets', async () => { const plugins = await manager.listPlugins() expect(plugins.find(row => row.entryId === 'include:managed')).toMatchObject({ patchId: 'managed', enabled: true }) expect(plugins.find(row => row.entryId === 'include:manager')?.readOnlyReason).toBe('management-required') - expect(await manager.listBundles()).toEqual([ + expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([ { - name: 'core', version: '1.0.0', enabled: true, installed: false, optional: false, removable: false, readOnlyReason: 'management-required', + name: 'core', version: '1.0.0', enabled: true, installed: false, official: false, availability: 'profile', optional: false, removable: false, readOnlyReason: 'management-required', meta: { title: 'core' }, rows: [{ rowId: 'manager', moduleName: 'cordis:manager', entryId: 'include:manager' }], overrides: [], }, { - name: 'extra', version: '1.0.0', source: 'extra@1.0.0', enabled: true, installed: true, optional: false, removable: true, + name: 'extra', version: '1.0.0', source: 'extra@1.0.0', enabled: true, installed: true, official: false, availability: 'profile', optional: false, removable: true, meta: { title: 'extra' }, rows: [{ rowId: 'managed', moduleName: pathToFileURL(join(dir, 'node_modules', 'extra', 'plugin.mjs')).href, entryId: 'include:managed' }], overrides: [], }, @@ -323,7 +329,7 @@ it('describes a bundle by its manifest and patch: one-liner, rows without a live writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) const moduleName = pathToFileURL(join(dir, 'node_modules', 'described', 'plugin.mjs')).href expect((await manager.listBundles()).find(row => row.name === 'described')).toEqual({ - name: 'described', version: '2.0.0', description: 'Describes itself.', source: 'described@2.0.0', enabled: false, installed: true, optional: false, removable: true, + name: 'described', version: '2.0.0', description: 'Describes itself.', source: 'described@2.0.0', enabled: false, installed: true, official: false, availability: 'profile', optional: false, removable: true, meta: { title: 'described', description: 'Describes itself.' }, rows: [{ rowId: 'described-row', moduleName }], overrides: ['managed'], }) @@ -355,7 +361,8 @@ it('names where each installed bundle comes from as a spec pnpm installs', async writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) // The installation's own copy of a bundle is the one that loads, so the profile's dependency on it names nothing. writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { shadowed: '1.0.0' } })) - expect(Object.fromEntries((await manager.listBundles()).map(row => [row.name, row.source]))).toEqual({ + expect(Object.fromEntries((await manager.listBundles()) + .filter(row => row.installTarget === undefined).map(row => [row.name, row.source]))).toEqual({ core: undefined, extra: 'extra@^1.0.0', tagged: 'tagged@latest', aliased: 'aliased@npm:@acme/aliased@2', jsr: 'jsr@jsr:@acme/jsr@^1', github: 'github:someone/dsh-plugin#v1', ssh: 'git@github.com:someone/dsh-plugin.git', deploy: 'deploy@git.corp:team/dsh-plugin.git', sshUrl: 'git+ssh://git@github.com/someone/dsh-plugin.git', email: 'git+http://git.example.com/repo.git', @@ -446,7 +453,7 @@ it.each([ writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) expect((await manager.listBundles()).find(row => row.name === 'unnamed')).toEqual({ - name: 'unnamed', version: '1.0.0', source: 'unnamed@1.0.0', enabled: false, installed: true, optional: false, removable: true, + name: 'unnamed', version: '1.0.0', source: 'unnamed@1.0.0', enabled: false, installed: true, official: false, availability: 'profile', optional: false, removable: true, rows: [], overrides: [], ...expected, }) }) @@ -506,6 +513,9 @@ it('installs only valid bundle declarations and honors installation without acti const install = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args) => { const name = String(args[1]) bundle(name, [{ id: name, name: './plugin.mjs', config: { service: name } }]) + if (name === 'another-bundle') { + writeFileSync(join(dir, 'node_modules', name, 'package.json'), JSON.stringify({ name, dsh: { bundle: { patch: './cordis.patch.yml' } } })) + } const manifest = readProfileManifest('test', dir) manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' } writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) @@ -513,12 +523,14 @@ it('installs only valid bundle declarations and honors installation without acti }) onTestFinished(() => { install.mockRestore() }) expect(await manager.installBundle('new-bundle', { enabled: false })).toMatchObject({ - changed: true, application: 'applied', stage: 'enable', target: 'new-bundle', bundle: 'new-bundle', packageResult: { exitCode: 0 }, + changed: true, application: 'applied', stage: 'enable', target: 'new-bundle', bundle: 'new-bundle', version: '1.0.0', packageResult: { exitCode: 0 }, }) expect((await manager.listBundles()).find(row => row.name === 'new-bundle')?.enabled).toBe(false) expect(await manager.setBundleEnabled('new-bundle', true)).toMatchObject({ application: 'applied' }) expect((await manager.listPlugins()).find(row => row.patchId === 'new-bundle')?.fiberPhase).toBe('active') - expect(await manager.installBundle('another-bundle')).toMatchObject({ application: 'applied' }) + const another = await manager.installBundle('another-bundle') + expect(another).toMatchObject({ application: 'applied', bundle: 'another-bundle' }) + expect(another).not.toHaveProperty('version') expect((await manager.listBundles()).find(row => row.name === 'another-bundle')?.enabled).toBe(true) }) @@ -703,10 +715,10 @@ it('reports a selected plain dependency as a problem, omits an unselected one, a writeFileSync(join(dir, 'node_modules', 'core', 'package.json'), '{"name":"core","dsh":{"bundle":{"patch":"./cordis.patch.yml"}}}') expect((await manager.listBundles())[0]?.version).toBeUndefined() writeFileSync(join(dir, 'package.json'), '{}') - expect(await manager.listBundles()).toEqual([]) + expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([]) expect(await manager.setBundleEnabled('unknown', false)).toMatchObject({ application: 'failed' }) writeFileSync(profile.installAnchor, '{"dependencies":{"missing-builtin":"1"}}') - expect(await manager.listBundles()).toEqual([]) + expect((await manager.listBundles()).filter(bundle => bundle.installTarget === undefined)).toEqual([]) }) it('refuses management bundle disablement and permits repeated bundle selections', async () => { @@ -1064,11 +1076,10 @@ it('refuses removal of a hot-installed bundle after HMR is disabled', async () = expect(await manager.removeBundle('later')).toMatchObject({ changed: false, application: 'failed' }) }) -it('offers the launcher\'s optional bundles switched off and never removable', async () => { +it.each(OPTIONAL_BUNDLES)('offers %s switched off and never removable', async (offered) => { const { manager, profile } = await fixture() // The launcher names the bundles the installation ships; the fixture supplies one of them from the // installation's own node_modules, which the resolver consults before the profile's and before the repository's. - const offered = OPTIONAL_BUNDLES[0]! const supplied = join(profile.home, 'node_modules', offered) mkdirSync(supplied, { recursive: true }) writeFileSync(join(supplied, 'package.json'), JSON.stringify({ @@ -1080,7 +1091,7 @@ it('offers the launcher\'s optional bundles switched off and never removable', a expect((await manager.listBundles()).find(row => row.name === offered)).toEqual({ name: offered, version: '3.0.0', description: 'Package one-liner.', meta: { title: offered, description: 'Package one-liner.' }, - enabled: false, installed: false, optional: true, removable: false, + enabled: false, installed: false, official: true, availability: 'installation', optional: true, removable: false, rows: [{ rowId: 'offered-row', moduleName: pathToFileURL(join(supplied, 'plugin.mjs')).href }], overrides: [], }) expect(await manager.setBundleEnabled(offered, true)).toMatchObject({ application: 'applied' }) @@ -1088,6 +1099,23 @@ it('offers the launcher\'s optional bundles switched off and never removable', a expect(await manager.removeBundle(offered)).toMatchObject({ changed: false, application: 'failed' }) }) +it('removes a selected bundle no dependency holds by deselecting it without pnpm', async () => { + const { manager, dir } = await fixture() + const manifest = readProfileManifest('test', dir) + writeFileSync(join(dir, 'package.json'), JSON.stringify({ + ...manifest, dsh: { profile: { bundles: [...manifest.dsh?.profile?.bundles ?? [], 'retired'] } }, + })) + expect((await manager.listBundles()).find(row => row.name === 'retired')).toMatchObject({ + enabled: true, installed: false, official: false, availability: 'missing', optional: false, removable: true, error: { code: 'operation-error' }, + }) + const pnpm = vi.spyOn(operations, 'runProfilePnpm') + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.removeBundle('retired')).toMatchObject({ changed: true, application: 'applied' }) + expect(pnpm).not.toHaveBeenCalled() + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).not.toContain('retired') + expect((await manager.listBundles()).some(row => row.name === 'retired')).toBe(false) +}) + it('omits installation-owned plain packages from the bundle inventory', async () => { const { manager, dir, profile, bundle } = await fixture() bundle('installation-plain', []) @@ -1430,3 +1458,206 @@ it.each([false, true])('rechecks installed bundle peers before accepting a disab } expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'extra']) }) + + +it('lists absent Official entries offline without resolving their packages or consulting registries', async () => { + const { manager } = await fixture() + const resolve = vi.spyOn(operations, 'bundleManifest') + const registry = vi.spyOn(operations, 'viewProfilePackage') + onTestFinished(() => { resolve.mockRestore(); registry.mockRestore() }) + const bundles = await manager.listBundles() + for (const entry of OFFICIAL_ON_DEMAND_CATALOG) { + expect(bundles.filter(bundle => bundle.name === entry.packageName)).toEqual([{ + name: entry.packageName, official: true, availability: 'missing', enabled: false, installed: false, optional: false, + removable: false, rows: [], overrides: [], meta: entry.meta, + installTarget: { spec: `${entry.packageName}@${getDshRuntimeVersion()}`, version: getDshRuntimeVersion() }, + }]) + expect(resolve.mock.calls.some(([name]) => name === entry.packageName)).toBe(false) + } + expect(registry).not.toHaveBeenCalled() +}) + +it('distinguishes an unreadable profile dependency from an absent catalog selection', async () => { + const { manager, dir, profile } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const broken = join(profile.home, 'node_modules', name) + mkdirSync(broken, { recursive: true }) + writeFileSync(join(broken, 'package.json'), '{') + expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({ + official: true, installed: true, availability: 'missing', enabled: false, removable: true, + source: `${name}@1.0.0`, error: { code: 'operation-error' }, + }) +}) + +it('uses the generic exact installer and keeps an Official entry through on, off, and removal', async () => { + const { manager, dir, bundle, profile } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + const version = getDshRuntimeVersion() + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_dir, args) => { + const manifest = readProfileManifest('test', dir) + if (args[0] === 'add') { + bundle(name, [{ id: 'official-probe', name: './plugin.mjs', config: { service: 'officialProbe' } }], profile.home) + const packagePath = join(profile.home, 'node_modules', name, 'package.json') + const packaged = JSON.parse(readFileSync(packagePath, 'utf8')) as Record + writeFileSync(packagePath, JSON.stringify({ ...packaged, version })) + manifest.dependencies = { ...manifest.dependencies, [name]: version } + } else { + delete manifest.dependencies?.[name] + rmSync(join(profile.home, 'node_modules', name), { recursive: true, force: true }) + } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' } + }) + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.installBundle(`${name}@${version}`, { saveExact: true })).toMatchObject({ application: 'applied', enabled: true }) + expect(pnpm.mock.calls[0]?.[1]).toContain('--save-exact') + expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ official: true, availability: 'profile', enabled: true, version }) + expect(await manager.setBundleEnabled(name, false)).toMatchObject({ application: 'applied' }) + expect(readProfileManifest('test', dir).dependencies?.[name]).toBe(version) + expect(await manager.removeBundle(name)).toMatchObject({ application: 'applied' }) + expect((await manager.listBundles()).filter(item => item.name === name)).toHaveLength(1) + expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ official: true, availability: 'missing', enabled: false, installed: false }) +}) + +it.each([true, false])('updates an Official dependency while preserving selection=%s and requiring restart', async (enabled) => { + const { manager, dir, bundle, profile } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + bundle(name, [], profile.home) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' } + if (enabled) manifest.dsh!.profile!.bundles!.push(name) + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const listed = (await manager.listBundles()).find(item => item.name === name) + expect(listed).toMatchObject({ version: '1.0.0', official: true, availability: 'profile', + installTarget: { spec: `${name}@${getDshRuntimeVersion()}`, version: getDshRuntimeVersion() } }) + expect(listed?.error).toBeUndefined() + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + const current = readProfileManifest('test', dir) + current.dependencies = { ...current.dependencies, [name]: getDshRuntimeVersion() } + writeFileSync(join(dir, 'package.json'), JSON.stringify(current)) + return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' } + }) + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.installBundle(`${name}@${getDshRuntimeVersion()}`, { enabled, saveExact: true })) + .toMatchObject({ application: 'restart-required', enabled }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles?.includes(name)).toBe(enabled) +}) + +it('restores dependency and selection after an exact version is unavailable', async () => { + const { manager, dir, bundle, profile } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + bundle(name, [], profile.home) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' } + manifest.dsh!.profile!.bundles!.push(name) + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const before = readFileSync(join(dir, 'package.json'), 'utf8') + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + writeFileSync(join(dir, 'package.json'), '{}') + return { exitCode: 1, output: 'No matching version', truncated: false, logPath: '/install.log', kind: 'no-matching-version' } + }) + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.installBundle(`${name}@${getDshRuntimeVersion()}`, { saveExact: true })).toMatchObject({ application: 'failed', stage: 'install' }) + expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before) +}) + +it('retains a successful installation and saved selection when Official activation fails', async () => { + const { manager, dir, bundle, profile } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + bundle(name, [{ id: 'official-probe', name: './plugin.mjs', config: { fail: true } }], profile.home) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: getDshRuntimeVersion() } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + return { exitCode: 0, output: '', truncated: false, logPath: '/install.log' } + }) + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.installBundle(`${name}@${getDshRuntimeVersion()}`, { saveExact: true })).toMatchObject({ application: 'failed', stage: 'enable' }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain(name) + expect(readProfileManifest('test', dir).dependencies?.[name]).toBe(getDshRuntimeVersion()) +}) + +it('reports and removes a saved Official selection whose dependency is absent', async () => { + const { manager, dir } = await fixture() + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + const manifest = readProfileManifest('test', dir) + manifest.dsh!.profile!.bundles!.push(name) + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({ + official: true, installed: false, availability: 'missing', enabled: true, removable: true, error: { code: 'not-bundle' }, + }) + const pnpm = vi.spyOn(operations, 'runProfilePnpm') + onTestFinished(() => { pnpm.mockRestore() }) + expect(await manager.removeBundle(name)).toMatchObject({ application: 'applied' }) + expect(pnpm).not.toHaveBeenCalled() + expect((await manager.listBundles()).find(bundle => bundle.name === name)).toMatchObject({ availability: 'missing', enabled: false }) +}) + + +it('keeps a broken installation-carried catalog entry visible with offline metadata', async () => { + const { manager, profile } = await fixture() + const entry = OFFICIAL_ON_DEMAND_CATALOG[0]! + writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { [entry.packageName]: '1.0.0' } })) + const broken = join(profile.home, 'node_modules', entry.packageName) + mkdirSync(broken, { recursive: true }) + writeFileSync(join(broken, 'package.json'), '{') + expect((await manager.listBundles()).find(bundle => bundle.name === entry.packageName)).toMatchObject({ + official: true, availability: 'missing', enabled: false, installed: false, optional: false, removable: false, + meta: entry.meta, installTarget: { version: getDshRuntimeVersion() }, error: { code: 'operation-error' }, + }) +}) + + +it('offers checkout links beside stale registry and other-checkout dependencies at the same version', async () => { + const { manager, dir, bundle, profile } = await fixture('live', false, undefined, {}, undefined, undefined, true) + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + const version = getDshRuntimeVersion() + const source = join(profile.home, 'packages', 'native', 'provider') + mkdirSync(join(source, 'lib'), { recursive: true }) + writeFileSync(join(source, 'package.json'), JSON.stringify({ name, version, main: './lib/index.js', dsh: { bundle: { patch: './cordis.patch.yml' } } })) + writeFileSync(join(source, 'lib', 'index.js'), 'export function apply() {}\n') + writeFileSync(join(source, 'cordis.patch.yml'), '[]\n') + bundle(name, [], profile.home) + const installedManifest = join(profile.home, 'node_modules', name, 'package.json') + writeFileSync(installedManifest, JSON.stringify({ ...JSON.parse(readFileSync(installedManifest, 'utf8')), version })) + const target = { spec: `link:${source}`, version } + expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ availability: 'missing', installTarget: target }) + for (const spec of [version, 'link:../other-checkout/native', `link:${source}`]) { + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: spec } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const listed = (await manager.listBundles()).find(item => item.name === name) + expect(listed).toMatchObject({ version, official: true, availability: 'profile', installTarget: target }) + expect(listed?.source === listed?.installTarget?.spec).toBe(spec === `link:${source}`) + } +}) + +it.each(['absent', 'bundle', 'not-bundle', 'unreadable'])('keeps a missing source bundle visible when its installed copy is %s', async (state) => { + const { manager, dir, bundle, profile } = await fixture('live', false, undefined, {}, undefined, undefined, true) + const name = OFFICIAL_ON_DEMAND_CATALOG[0]!.packageName + if (state !== 'absent') { + bundle(name, [], profile.home) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, [name]: '1.0.0' } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const file = join(profile.home, 'node_modules', name, 'package.json') + if (state === 'not-bundle') writeFileSync(file, JSON.stringify({ name, version: '1.0.0' })) + if (state === 'unreadable') writeFileSync(file, '{') + } + expect((await manager.listBundles()).find(item => item.name === name)).toMatchObject({ + official: true, error: { code: 'operation-error' }, + }) + expect((await manager.listBundles()).find(item => item.name === name)?.error?.diagnostic).toContain('restore the checkout') + expect((await manager.listBundles()).find(item => item.name === name)?.installTarget).toBeUndefined() +}) + +it('recognizes a repeated local link install without a dependency-spec change', async () => { + const { manager, dir } = await fixture() + const install = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ exitCode: 0, output: '', truncated: false, logPath: join(dir, 'pnpm.log') }) + onTestFinished(() => { install.mockRestore() }) + expect(await manager.installBundle(`link:${join(dir, 'node_modules', 'extra')}`, { enabled: false })) + .toMatchObject({ changed: false, application: 'restart-required', bundle: 'extra', enabled: false }) +}) diff --git a/packages/boot/plugin-manager/tests/official-install-target.spec.ts b/packages/boot/plugin-manager/tests/official-install-target.spec.ts new file mode 100644 index 0000000000..96101fa59f --- /dev/null +++ b/packages/boot/plugin-manager/tests/official-install-target.spec.ts @@ -0,0 +1,100 @@ +/** Official installation targets follow the actual CLI installation and its workspace packages. */ +import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { expect, it, onTestFinished } from 'vitest' +import { officialBundleInstallTarget } from '../src/official-install-target.ts' + +const name = '@deepseek-ai/dsh-subagent-codex' +const version = '0.2.1-alpha.1' + +function fixture() { + const temporary = mkdtempSync(join(tmpdir(), 'official-target-')) + onTestFinished(() => { rmSync(temporary, { recursive: true, force: true }) }) + const root = realpathSync(temporary) + const anchor = join(root, 'apps', 'cli', 'package.json') + mkdirSync(dirname(anchor), { recursive: true }) + writeFileSync(join(root, 'package.json'), JSON.stringify({ name: '@deepseek-ai/dsh-root' })) + writeFileSync(anchor, JSON.stringify({ name: '@deepseek-ai/dsh', version, dependencies: { '@deepseek-ai/dsh-app-boot': 'workspace:*' } })) + const directory = join(root, 'packages', 'relocated', 'native-provider') + mkdirSync(join(directory, 'lib'), { recursive: true }) + writeFileSync(join(directory, 'package.json'), JSON.stringify({ name, version, main: 'lib/index.js', dsh: { bundle: { patch: './cordis.patch.yml' } } })) + writeFileSync(join(directory, 'lib', 'index.js'), 'export function apply() {}\n') + writeFileSync(join(directory, 'cordis.patch.yml'), '[]\n') + return { root, anchor, directory } +} + +it('offers an exact prerelease registry target for a packed installation inside a checkout', () => { + const { anchor } = fixture() + writeFileSync(anchor, JSON.stringify({ name: '@deepseek-ai/dsh', version, dependencies: { '@deepseek-ai/dsh-app-boot': version } })) + expect(officialBundleInstallTarget(name, anchor, version)).toEqual({ spec: `${name}@${version}`, version }) +}) + +it('keeps an installation without workspace dependencies on the registry path', () => { + const { anchor } = fixture() + writeFileSync(anchor, JSON.stringify({ name: '@deepseek-ai/dsh', version })) + expect(officialBundleInstallTarget(name, anchor, version)).toEqual({ spec: `${name}@${version}`, version }) +}) + +it('finds the package by manifest name rather than assuming its workspace directory', () => { + const { anchor, directory } = fixture() + expect(officialBundleInstallTarget(name, anchor, version)).toEqual({ spec: `link:${directory}`, version }) +}) + +it('follows the CLI directory link used by an unpackaged Desktop application', () => { + const { root, anchor, directory } = fixture() + const link = join(root, 'desktop-runtime', 'node_modules', '@deepseek-ai', 'dsh') + mkdirSync(dirname(link), { recursive: true }) + symlinkSync(dirname(anchor), link, process.platform === 'win32' ? 'junction' : 'dir') + expect(officialBundleInstallTarget(name, join(link, 'package.json'), version)).toEqual({ spec: `link:${directory}`, version }) +}) + +it.each(['missing', 'wrong-name', 'wrong-location'])('refuses a source CLI with a %s workspace root', (kind) => { + const { root, anchor } = fixture() + if (kind === 'missing') rmSync(join(root, 'package.json')) + if (kind === 'wrong-name') writeFileSync(join(root, 'package.json'), '{"name":"unrelated"}') + const selected = kind === 'wrong-location' ? join(root, 'package.json') : anchor + if (kind === 'wrong-location') writeFileSync(selected, readFileSync(anchor)) + expect(() => officialBundleInstallTarget(name, selected, version)).toThrow('requires its complete workspace checkout') +}) + +it.each(['missing', 'duplicate'])('refuses a %s workspace package without offering a registry fallback', (kind) => { + const { root, anchor, directory } = fixture() + if (kind === 'missing') rmSync(directory, { recursive: true }) + else { + const duplicate = join(root, 'packages', 'elsewhere', 'duplicate') + mkdirSync(duplicate, { recursive: true }) + writeFileSync(join(duplicate, 'package.json'), readFileSync(join(directory, 'package.json'))) + } + expect(() => officialBundleInstallTarget(name, anchor, version)).toThrow('expected one workspace package') +}) + +it.each([ + { version: '0.1.0' }, { private: true }, { dsh: {} }, +])('refuses a workspace package with invalid release or bundle metadata: %j', (changes) => { + const { anchor, directory } = fixture() + const file = join(directory, 'package.json') + writeFileSync(file, JSON.stringify({ ...JSON.parse(readFileSync(file, 'utf8')), ...changes })) + expect(() => officialBundleInstallTarget(name, anchor, version)).toThrow('must declare a public bundle at DSH version') +}) + +it.each(['missing-main', 'missing-output', 'directory-output'])('refuses an unbuilt workspace package: %s', (kind) => { + const { anchor, directory } = fixture() + if (kind === 'missing-main') { + const file = join(directory, 'package.json') + writeFileSync(file, JSON.stringify({ ...JSON.parse(readFileSync(file, 'utf8')), main: undefined })) + } else { + const output = join(directory, 'lib', 'index.js') + rmSync(output) + if (kind === 'directory-output') mkdirSync(output) + } + expect(() => officialBundleInstallTarget(name, anchor, version)).toThrow('run pnpm install and pnpm run build') +}) + +it.each(['missing', 'directory'])('refuses an unavailable bundle patch: %s', (kind) => { + const { anchor, directory } = fixture() + const patch = join(directory, 'cordis.patch.yml') + rmSync(patch) + if (kind === 'directory') mkdirSync(patch) + expect(() => officialBundleInstallTarget(name, anchor, version)).toThrow('bundle patch') +}) diff --git a/packages/boot/plugin-manager/tests/operations.spec.ts b/packages/boot/plugin-manager/tests/operations.spec.ts index 9a062e8af9..f9aa34314c 100644 --- a/packages/boot/plugin-manager/tests/operations.spec.ts +++ b/packages/boot/plugin-manager/tests/operations.spec.ts @@ -471,7 +471,7 @@ it.each(['devDependencies', 'optionalDependencies'])('checks installed direct pl expect(await runProfilePnpm(context, ['install'], { execution: 'service', outputBytes: 8192 })).toMatchObject({ exitCode: 1 }) }) -it('rejects an incompatible component declared by a newly installed bundle', async () => { +it.each([undefined, 'preset-standard'])('rejects an incompatible component contributed to %s by a newly installed bundle', async (preset) => { const { dir, context, pnpm } = fixture() pnpm.mutate = (target) => { install(target, 'bundle') @@ -481,7 +481,16 @@ it('rejects an incompatible component declared by a newly installed bundle', asy writeFileSync(join(component, 'package.json'), JSON.stringify({ name: 'component', version: '1.0.0', peerDependencies: { '@deepseek-ai/dsh': '>=999.0.0' }, })) - writeFileSync(join(packageDir, 'cordis.patch.yml'), '- insert:\n - id: component\n name: component/subpath\n') + const unrelated = join(target, 'node_modules', 'component') + mkdirSync(unrelated, { recursive: true }) + writeFileSync(join(unrelated, 'package.json'), JSON.stringify({ name: 'component', version: '2.0.0' })) + mkdirSync(join(packageDir, 'nested')) + writeFileSync(join(packageDir, 'nested', 'group.mjs'), 'export function apply() {}\n') + writeFileSync(join(packageDir, 'cordis.patch.yml'), JSON.stringify([{ + ...preset === undefined ? {} : { preset }, insert: [{ id: 'relative-group', name: './nested/group.mjs', group: true, + config: [{ id: 'component', name: 'component/subpath' }], + }], + }])) } const outcome = await runProfilePnpm(context, ['add', 'bundle'], { execution: 'service', outputBytes: 8192, activateNewBundles: false }) expect(outcome.exitCode).toBe(1) @@ -503,7 +512,7 @@ it('restores the manifest on malformed installed peer metadata and bounds the wa expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(before) }) -it('checks scoped components in nested groups while leaving unresolved and local rows to startup', async () => { +it('validates nested preset contributions without requiring their target preset to be selected', async () => { const { context, pnpm } = fixture() pnpm.mutate = (target) => { install(target, 'bundle') @@ -511,7 +520,7 @@ it('checks scoped components in nested groups while leaving unresolved and local const component = join(packageDir, 'node_modules', '@example', 'component') mkdirSync(component, { recursive: true }) writeFileSync(join(component, 'package.json'), JSON.stringify({ name: '@example/component', version: '1.0.0' })) - writeFileSync(join(packageDir, 'cordis.patch.yml'), JSON.stringify([{ insert: [ + writeFileSync(join(packageDir, 'cordis.patch.yml'), JSON.stringify([{ preset: 'preset-standard', insert: [ { id: 'group', group: true, config: [{ id: 'component', name: '@example/component/subpath' }] }, { id: 'empty-group', group: true, config: {} }, { id: 'relative', name: './plugin.mjs' }, diff --git a/packages/boot/plugin-manager/tests/package-reload.spec.ts b/packages/boot/plugin-manager/tests/package-reload.spec.ts new file mode 100644 index 0000000000..d83aab3be3 --- /dev/null +++ b/packages/boot/plugin-manager/tests/package-reload.spec.ts @@ -0,0 +1,457 @@ +/** GUI package operations publish profile package resolution around activation and disposal through the real Loader. */ +import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs' +import { createRequire } from 'node:module' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { FiberState, type Context } from '@deepseek-ai/cordis' +import Timer from '@deepseek-ai/cordis-plugin-timer' +import { + boot, createRuntimeResolution, initProfile, loadProfileDirectory, PluginPackages, + readProfileManifest, readProfilePatches, type ProfileContext, +} from '@deepseek-ai/dsh-app-boot' +import Hmr from '@deepseek-ai/dsh-hmr' +import type { ChokidarOptions } from 'chokidar' +import { expect, it, onTestFinished, vi } from 'vitest' +import PluginManager, { type PluginInstallRequestId } from '../src/index.ts' +import * as operations from '../src/operations.ts' + +// Changes in these cases come only from GUI operations, never from file notifications. +vi.mock('chokidar', async (original) => { + const actual = await original() + return { ...actual, watch: (_paths: string | string[], options: ChokidarOptions = {}) => { + const watcher = new actual.FSWatcher(options) + queueMicrotask(() => watcher.emit('ready')) + return watcher + } } +}) + +/** Write the `addon` bundle, whose plugin is its private bare dependency `addon-plugin`. */ +function writeBundle(dir: string, version: number): void { + const bundleDir = join(dir, 'node_modules', 'addon') + const pluginDir = join(bundleDir, 'node_modules', 'addon-plugin') + rmSync(bundleDir, { recursive: true, force: true }) + mkdirSync(pluginDir, { recursive: true }) + writeFileSync(join(bundleDir, 'package.json'), JSON.stringify({ + name: 'addon', version: `${version}.0.0`, type: 'module', + dependencies: { 'addon-plugin': `${version}.0.0` }, dsh: { bundle: { patch: './cordis.patch.yml' } }, + })) + writeFileSync(join(bundleDir, 'cordis.patch.yml'), JSON.stringify([{ insert: [{ id: 'addon', name: 'addon-plugin' }] }])) + writeFileSync(join(pluginDir, 'package.json'), JSON.stringify({ + name: 'addon-plugin', version: `${version}.0.0`, type: 'module', exports: './plugin.mjs', + })) + writeFileSync(join(pluginDir, 'plugin.mjs'), ` +import { appendFileSync } from 'node:fs'; +appendFileSync(${JSON.stringify(join(dir, 'addon-evaluations.log'))}, ${JSON.stringify(`addon:${version}\n`)}); +export function apply(ctx) { + ctx.get('packageReloadTrace').push('start:addon:${version}'); + ctx.provide('addonVersion', ${version}); + ctx.effect(() => () => { ctx.get('packageReloadTrace').push('stop:addon:${version}'); }); +} +`) +} + +/** Write `addon` and save it as a profile dependency, as `pnpm add` does. */ +function installFiles(dir: string, version: number): void { + writeBundle(dir, version) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, addon: `${version}.0.0` } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) +} + +/** Delete `addon` and drop it from the profile dependencies, as `pnpm remove` does. */ +function removeFiles(dir: string): void { + const manifest = readProfileManifest('test', dir) + delete manifest.dependencies?.addon + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + rmSync(join(dir, 'node_modules', 'addon'), { recursive: true }) +} + +function installOtherBundle(dir: string, shared = false): void { + const bundleDir = join(dir, 'node_modules', 'other') + mkdirSync(bundleDir, { recursive: true }) + writeFileSync(join(bundleDir, 'package.json'), JSON.stringify({ + name: 'other', version: '1.0.0', dependencies: shared ? { 'addon-plugin': '1.0.0' } : {}, + dsh: { bundle: { patch: './cordis.patch.yml' } }, + })) + writeFileSync(join(bundleDir, 'cordis.patch.yml'), '[]\n') + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, other: '1.0.0' } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) +} + +async function fixture(options: { live?: boolean; installed?: boolean; enabled?: boolean; shared?: boolean } = {}) { + const home = realpathSync.native(mkdtempSync(join(tmpdir(), 'manager-package-reload-'))) + let owner: Context | undefined + onTestFinished(async () => { + await owner?.fiber.dispose() + rmSync(home, { recursive: true, force: true }) + }) + const dir = join(home, 'profiles', 'test') + const installAnchor = join(home, 'package.json') + writeFileSync(installAnchor, '{"name":"installation","dependencies":{}}\n') + initProfile(dir, options.installed === true && options.enabled !== false ? ['core', 'addon'] : ['core']) + writeFileSync(join(dir, 'cordis.yml'), '[]\n') + const core = join(dir, 'node_modules', 'core') + mkdirSync(core, { recursive: true }) + writeFileSync(join(core, 'package.json'), JSON.stringify({ + name: 'core', version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } }, + })) + writeFileSync(join(core, 'cordis.patch.yml'), JSON.stringify([{ insert: [ + { id: 'manager', name: 'cordis:manager' }, + { id: 'sibling', name: './sibling.mjs' }, + ] }])) + writeFileSync(join(core, 'sibling.mjs'), ` +export function apply(ctx) { + ctx.get('packageReloadTrace').push('start:sibling'); + ctx.provide('packageReloadSibling', {}); + ctx.effect(() => () => { ctx.get('packageReloadTrace').push('stop:sibling'); }); +} +`) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = {} + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + if (options.installed === true) installFiles(dir, 1) + if (options.shared === true) { + renameSync(join(dir, 'node_modules', 'addon', 'node_modules', 'addon-plugin'), join(dir, 'node_modules', 'addon-plugin')) + installOtherBundle(dir, true) + const shared = readProfileManifest('test', dir) + shared.dsh = { profile: { bundles: ['core', 'addon', 'other'] } } + writeFileSync(join(dir, 'package.json'), JSON.stringify(shared)) + } + const loaded = loadProfileDirectory('test', dir, installAnchor) + const profile: ProfileContext = { + name: 'test', dir, patchPath: join(dir, 'cordis.patch.yml'), installAnchor, cwd: home, home, + startedBundles: loaded.layers.map(layer => layer.packageName), overlays: [], telemetryDisabledEnv: undefined, + } + const resolution = await createRuntimeResolution({ installAnchor, profile: loaded, home }) + const trace: string[] = [] + const ctx = await boot('test', join(dir, 'cordis.yml'), readProfilePatches('test', profile), async (host) => { + owner = host + host.provide('profileContext', profile) + host.provide('appReady', { onReady(listener) { listener(); return () => {} } }) + host.provide('packageReloadTrace', trace) + host.loader.builtins.manager = PluginManager + await host.plugin(PluginPackages, { resolution }) + }) + if (options.live !== false) { + await ctx.plugin(Timer) + await ctx.plugin(Hmr, { root: [], ignored: [], debounce: 0 }) + await ctx.hmr.runExclusive(async () => {}) + } + const registry = vi.spyOn(operations, 'readProfileRegistry').mockResolvedValue('https://registry.npmjs.org/') + onTestFinished(() => { registry.mockRestore() }) + // Publications share the plugins' trace, so their order relative to plugin starts and stops is observable. + const replace = ctx.pluginPackages.replace.bind(ctx.pluginPackages) + const published = vi.spyOn(ctx.pluginPackages, 'replace').mockImplementation((successor) => { + trace.push('publish') + replace(successor) + }) + onTestFinished(() => { published.mockRestore() }) + const bundleDir = join(dir, 'node_modules', 'addon') + return { + ctx, dir, trace, published, manager: ctx.pluginManager, + parentURL: pathToFileURL(join(dir, 'caller.cjs')).href, + bundleURL: pathToFileURL(join(bundleDir, 'package.json')).href, + bundleDir, + evaluationPath: join(dir, 'addon-evaluations.log'), + entry: () => [...ctx.loader.entries()].find(row => row.id === 'include:addon'), + } +} + +/** Answer each pnpm run by recording it in the trace and applying `run` to the profile files. */ +function mockPnpm(dir: string, trace: string[], run: (args: readonly string[]) => void, exitCode = 0) { + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, args) => { + trace.push(`pnpm:${String(args[0])}`) + run(args) + return { + exitCode, output: exitCode === 0 ? 'completed' : 'fixture install failed', truncated: false, logPath: join(dir, 'pnpm.log'), + } + }) + onTestFinished(() => { pnpm.mockRestore() }) + return pnpm +} + +it('publishes a newly installed enabled bundle before its private bare plugin starts', async () => { + const { ctx, dir, trace, published, manager, parentURL, evaluationPath, entry } = await fixture() + expect(ctx.pluginPackages.packageOf('addon', parentURL)).toBeUndefined() + mockPnpm(dir, trace, (args) => { + expect(args.slice(0, 2)).toEqual(['add', 'addon']) + installFiles(dir, 1) + }) + + expect(await manager.installBundle('addon', { enabled: true })).toMatchObject({ application: 'applied', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'pnpm:add', 'publish', 'start:addon:1']) + expect(entry()?.fiber?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.version).toBe('1.0.0') + expect(readFileSync(evaluationPath, 'utf8')).toBe('addon:1\n') +}) + +it('publishes a newly installed disabled bundle without evaluating its plugin', async () => { + const { ctx, dir, trace, published, manager, parentURL, bundleURL, evaluationPath, entry } = await fixture() + mockPnpm(dir, trace, () => { installFiles(dir, 1) }) + + expect(await manager.installBundle('addon', { enabled: false })).toMatchObject({ application: 'applied', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'pnpm:add', 'publish']) + expect(entry()).toBeUndefined() + expect(existsSync(evaluationPath)).toBe(false) + expect(ctx.pluginPackages.packageOf('addon', parentURL)?.version).toBe('1.0.0') + expect(ctx.pluginPackages.packageOf('addon-plugin', bundleURL)?.version).toBe('1.0.0') +}) + +it('publishes an enabled bundle before its plugin starts', async () => { + const { ctx, dir, trace, published, manager, parentURL, evaluationPath, entry } = await fixture() + mockPnpm(dir, trace, () => { installFiles(dir, 1) }) + expect(await manager.installBundle('addon', { enabled: false })).toMatchObject({ application: 'applied' }) + published.mockClear() + trace.length = 0 + + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['publish', 'start:addon:1']) + expect(entry()?.fiber?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.version).toBe('1.0.0') + expect(readFileSync(evaluationPath, 'utf8')).toBe('addon:1\n') +}) + +it('publishes a disabled bundle only after its plugin stops', async () => { + const { ctx, trace, published, manager, parentURL, entry } = await fixture({ installed: true }) + expect(entry()?.fiber?.state).toBe(FiberState.ACTIVE) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.version).toBe('1.0.0') + const sibling: unknown = ctx.get('packageReloadSibling') + + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'start:addon:1', 'stop:addon:1', 'publish']) + expect(entry()).toBeUndefined() + expect(ctx.get('addonVersion')).toBeUndefined() + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBeUndefined() + expect(ctx.pluginPackages.packageOf('addon', parentURL)?.version).toBe('1.0.0') + expect(ctx.get('packageReloadSibling')).toBe(sibling) +}) + +it('keeps the running plugin and its private package lookup when a bundle is disabled without HMR', async () => { + const { ctx, trace, published, manager, parentURL, entry } = await fixture({ live: false, installed: true }) + const previous = entry()?.fiber + expect(previous?.state).toBe(FiberState.ACTIVE) + + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'restart-required', changed: true }) + expect(published).not.toHaveBeenCalled() + expect(entry()?.fiber).toBe(previous) + expect(previous?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.version).toBe('1.0.0') + expect(trace).toEqual(['start:sibling', 'start:addon:1']) +}) + +it('keeps the running bundle resolution through later package operations without HMR', async () => { + const { ctx, dir, trace, published, manager, parentURL, entry } = await fixture({ live: false, installed: true }) + const previous = entry()?.fiber + const mapping = ctx.pluginPackages.packageOf('addon-plugin', parentURL) + expect(previous?.state).toBe(FiberState.ACTIVE) + expect(mapping?.version).toBe('1.0.0') + mockPnpm(dir, trace, (args) => { + if (args[0] === 'add') { + installOtherBundle(dir) + } else { + expect(args).toEqual(['remove', 'other']) + const manifest = readProfileManifest('test', dir) + delete manifest.dependencies?.other + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + rmSync(join(dir, 'node_modules', 'other'), { recursive: true }) + } + }) + const expectRunningBundle = () => { + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBe(mapping) + expect(entry()?.fiber).toBe(previous) + expect(previous?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + expect(published).not.toHaveBeenCalled() + } + + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'restart-required', changed: true }) + expectRunningBundle() + expect(await manager.installBundle('other', { enabled: false })).toMatchObject({ application: 'restart-required', changed: true }) + expectRunningBundle() + expect(await manager.setBundleEnabled('other', true)).toMatchObject({ application: 'restart-required', changed: true }) + expectRunningBundle() + expect(await manager.removeBundle('other')).toMatchObject({ application: 'restart-required', changed: true }) + expectRunningBundle() + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core']) + expect(readProfileManifest('test', dir).dependencies).toEqual({ addon: '1.0.0' }) + expect(existsSync(join(dir, 'node_modules', 'other'))).toBe(false) + expect(trace).toEqual(['start:sibling', 'start:addon:1', 'pnpm:add', 'pnpm:remove']) +}) + +it.each(['disable', 'remove'] as const)('retains a shared dependency after %s of its first declaring bundle', async (operation) => { + const { ctx, dir, trace, published, manager, parentURL, entry } = await fixture({ installed: true, shared: true }) + const sharedDir = join(dir, 'node_modules', 'addon-plugin') + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.dir).toBe(sharedDir) + mockPnpm(dir, trace, () => { removeFiles(dir) }) + + const result = operation === 'disable' ? await manager.setBundleEnabled('addon', false) : await manager.removeBundle('addon') + + expect(result).toMatchObject({ application: 'applied', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(published.mock.calls[0]?.[0].entries.find(item => item.name === 'addon-plugin')).toMatchObject({ + packageDir: sharedDir, version: '1.0.0', scope: 'profile', declarer: join(dir, 'node_modules', 'other', 'package.json'), + }) + expect(entry()).toBeUndefined() + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core', 'other']) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)?.dir).toBe(sharedDir) + expect(createRequire(join(dir, 'node_modules', 'other', 'package.json')).resolve('addon-plugin')).toBe(join(sharedDir, 'plugin.mjs')) +}) + +it('removes a running bundle after its plugin stops and then publishes', async () => { + const { ctx, dir, trace, published, manager, parentURL, bundleURL, bundleDir, entry } = await fixture({ installed: true }) + const previous = entry()?.fiber + expect(previous?.state).toBe(FiberState.ACTIVE) + const sibling: unknown = ctx.get('packageReloadSibling') + const pnpm = mockPnpm(dir, trace, (args) => { + expect(args).toEqual(['remove', 'addon']) + expect(entry()).toBeUndefined() + removeFiles(dir) + }) + + expect(await manager.removeBundle('addon')).toMatchObject({ + application: 'applied', changed: true, packageResult: { exitCode: 0 }, + }) + expect(pnpm).toHaveBeenCalledOnce() + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'start:addon:1', 'stop:addon:1', 'pnpm:remove', 'publish']) + expect(previous?.uid).toBeNull() + expect(existsSync(bundleDir)).toBe(false) + expect(ctx.pluginPackages.packageOf('addon', parentURL)).toBeUndefined() + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBeUndefined() + expect(ctx.pluginPackages.packageOf('addon-plugin', bundleURL)).toBeUndefined() + expect(ctx.get('packageReloadSibling')).toBe(sibling) + expect((await manager.listBundles()).some(bundle => bundle.name === 'addon')).toBe(false) +}) + +it('removes and publishes a bundle that is not running without HMR', async () => { + const { ctx, dir, trace, published, manager, parentURL, bundleDir, evaluationPath } = await fixture({ + live: false, installed: true, enabled: false, + }) + expect(ctx.pluginPackages.packageOf('addon', parentURL)?.version).toBe('1.0.0') + const sibling: unknown = ctx.get('packageReloadSibling') + const pnpm = mockPnpm(dir, trace, (args) => { + expect(args).toEqual(['remove', 'addon']) + removeFiles(dir) + }) + + expect(await manager.removeBundle('addon')).toMatchObject({ + application: 'restart-required', changed: true, packageResult: { exitCode: 0 }, + }) + expect(pnpm).toHaveBeenCalledOnce() + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'pnpm:remove', 'publish']) + expect(existsSync(bundleDir)).toBe(false) + expect(readProfileManifest('test', dir).dependencies).not.toHaveProperty('addon') + expect(ctx.pluginPackages.packageOf('addon', parentURL)).toBeUndefined() + expect(existsSync(evaluationPath)).toBe(false) + expect(ctx.get('packageReloadSibling')).toBe(sibling) +}) + +it.each([{ live: true }, { live: false }])('keeps the running version and publishes nothing when an installed bundle is overwritten with HMR=$live', async ({ live }) => { + const { ctx, dir, trace, published, manager, evaluationPath, entry } = await fixture({ live, installed: true }) + const previous = entry()?.fiber + expect(previous?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + mockPnpm(dir, trace, (args) => { + expect(args.slice(0, 2)).toEqual(['add', 'addon@2']) + installFiles(dir, 2) + }) + + expect(await manager.installBundle('addon@2', { enabled: true })).toMatchObject({ application: 'restart-required', changed: true }) + expect(published).not.toHaveBeenCalled() + expect(trace).toEqual(['start:sibling', 'start:addon:1', 'pnpm:add']) + expect(entry()?.fiber).toBe(previous) + expect(previous?.state).toBe(FiberState.ACTIVE) + expect(ctx.get('addonVersion')).toBe(1) + expect(readFileSync(evaluationPath, 'utf8')).toBe('addon:1\n') +}) + +it('publishes nothing after installation fails', async () => { + const { ctx, dir, trace, published, manager, parentURL, evaluationPath, entry } = await fixture() + mockPnpm(dir, trace, () => { installFiles(dir, 1) }, 1) + + expect(await manager.installBundle('addon', { enabled: true })).toMatchObject({ + application: 'failed', changed: false, error: { diagnostic: 'fixture install failed' }, packageResult: { exitCode: 1 }, + }) + expect(published).not.toHaveBeenCalled() + expect(trace).toEqual(['start:sibling', 'pnpm:add']) + expect(readProfileManifest('test', dir).dependencies).toEqual({}) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBeUndefined() + expect(entry()).toBeUndefined() + expect(existsSync(evaluationPath)).toBe(false) +}) + +it.each(['install', 'remove'] as const)('keeps successful %s disk changes when resolution publication fails', async (operation) => { + const { ctx, dir, trace, published, manager, parentURL, entry } = await fixture({ installed: operation === 'remove' }) + const previous = ctx.pluginPackages.packageOf('addon-plugin', parentURL) + mockPnpm(dir, trace, () => { if (operation === 'install') installFiles(dir, 1); else removeFiles(dir) }) + published.mockImplementation(() => { throw new Error('publication rejected') }) + + const result = operation === 'install' ? await manager.installBundle('addon') : await manager.removeBundle('addon') + + expect(result).toMatchObject({ + application: 'failed', changed: true, error: { diagnostic: 'publication rejected' }, packageResult: { exitCode: 0 }, + }) + expect(published).toHaveBeenCalledOnce() + const manifest = readProfileManifest('test', dir) + expect(manifest.dependencies).toEqual(operation === 'install' ? { addon: '1.0.0' } : {}) + expect(manifest.dsh?.profile?.bundles).toEqual(operation === 'install' ? ['core', 'addon'] : ['core']) + expect(existsSync(join(dir, 'node_modules', 'addon'))).toBe(operation === 'install') + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBe(previous) + expect(entry()).toBeUndefined() +}) + +it('publishes nothing after installation is cancelled', async () => { + const { ctx, dir, trace, published, manager, parentURL, evaluationPath, entry } = await fixture() + const started = Promise.withResolvers() + const stopped = Promise.withResolvers() + const pnpm = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async (_context, _args, options) => { + installFiles(dir, 1) + const signal = options.signal + if (signal === undefined) throw new Error('Installation did not supply its cancellation signal') + const abort = () => { stopped.resolve(undefined) } + signal.addEventListener('abort', abort, { once: true }) + started.resolve(undefined) + try { await stopped.promise } finally { signal.removeEventListener('abort', abort) } + return { exitCode: 1, output: 'cancelled', truncated: false, logPath: join(dir, 'pnpm.log') } + }) + const requestId = '9feec888-53de-4c23-8486-965e5a0367b3' as PluginInstallRequestId + const installing = manager.installBundle('addon', { enabled: true, requestId }) + onTestFinished(async () => { + stopped.resolve(undefined) + await installing + pnpm.mockRestore() + }) + await started.promise + + expect(await manager.cancelInstall(requestId)).toEqual({ status: 'cancelled' }) + expect(await installing).toMatchObject({ application: 'cancelled', changed: false }) + expect(published).not.toHaveBeenCalled() + expect(trace).toEqual(['start:sibling']) + expect(readProfileManifest('test', dir).dependencies).toEqual({}) + expect(ctx.pluginPackages.packageOf('addon-plugin', parentURL)).toBeUndefined() + expect(entry()).toBeUndefined() + expect(existsSync(evaluationPath)).toBe(false) +}) + +it('publishes a newly installed bundle without HMR and leaves its activation to the restart', async () => { + const { ctx, dir, trace, published, manager, parentURL, evaluationPath, entry } = await fixture({ live: false }) + mockPnpm(dir, trace, () => { installFiles(dir, 1) }) + + expect(await manager.installBundle('addon', { enabled: true })).toMatchObject({ application: 'restart-required', changed: true }) + expect(published).toHaveBeenCalledOnce() + expect(trace).toEqual(['start:sibling', 'pnpm:add', 'publish']) + expect(ctx.pluginPackages.packageOf('addon', parentURL)?.version).toBe('1.0.0') + expect(entry()).toBeUndefined() + expect(ctx.get('addonVersion')).toBeUndefined() + expect(existsSync(evaluationPath)).toBe(false) +}) diff --git a/packages/boot/plugin-manager/tests/patch.spec.ts b/packages/boot/plugin-manager/tests/patch.spec.ts index 30bb947c84..a51c544d2f 100644 --- a/packages/boot/plugin-manager/tests/patch.spec.ts +++ b/packages/boot/plugin-manager/tests/patch.spec.ts @@ -41,6 +41,19 @@ it('creates a missing patch file and appends after insertions', async () => { ]) }) +it.each([false, true])('preserves same-ID preset overrides when a Host override exists: %s', async (hostOverride) => { + const preset = { preset: 'preset-standard', id: 'tool-bash', name: 'package', disabled: true } + const file = await fixture(JSON.stringify([ + ...hostOverride ? [{ id: 'tool-bash', disabled: false }] : [], preset, + ])) + expect(await writePluginEnabled(file, 'tool-bash', 'package', false)).toBe(true) + const disabled = { id: 'tool-bash', disabled: true } + expect(loadOptionalPatches('test', file)).toEqual(hostOverride ? [disabled, preset] : [preset, disabled]) + expect(await writePluginEnabled(file, 'tool-bash', 'package', true)).toBe(true) + const enabled = { id: 'tool-bash', disabled: false } + expect(loadOptionalPatches('test', file)).toEqual(hostOverride ? [enabled, preset] : [preset, enabled]) +}) + it.each(['- id: [broken', 'mapping: true\n'])('refuses malformed documents without overwriting %s', async (text) => { const file = await fixture(text) await expect(writePluginEnabled(file, 'tool', 'package', true)).rejects.toThrow() diff --git a/packages/boot/plugin-manager/tests/preset-bundles.spec.ts b/packages/boot/plugin-manager/tests/preset-bundles.spec.ts new file mode 100644 index 0000000000..08c063b180 --- /dev/null +++ b/packages/boot/plugin-manager/tests/preset-bundles.spec.ts @@ -0,0 +1,424 @@ +/** Scoped bundles use real preset generations, profile reloads, and package-removal admission. */ +import fs, { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { syncBuiltinESMExports } from 'node:module' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { pathToFileURL } from 'node:url' +import { expect, it, onTestFinished, vi } from 'vitest' +import type { Context } from '@deepseek-ai/cordis' +import Timer from '@deepseek-ai/cordis-plugin-timer' +import { boot, initProfile, PluginPackages, readProfileManifest, readProfilePatches, createRuntimeResolution, loadProfileDirectory, type ProfileContext, type ProfilePatch } from '@deepseek-ai/dsh-app-boot' +import Hmr from '@deepseek-ai/dsh-hmr' +import AgentPreset from '@deepseek-ai/dsh-agent-preset' +import AgentPresets from '@deepseek-ai/dsh-agent-preset-registry' +import { createScope } from '@deepseek-ai/dsh-scope' +import SessionStore, { SessionId, type Session } from '@deepseek-ai/dsh-session' +import SessionProjections from '@deepseek-ai/dsh-session-projection' +import SessionTitle, { SessionTitleProviderId, type SessionTitleProviderRequest, type SessionTitleProviderResult } from '@deepseek-ai/dsh-session-title' +import { createUserMessage } from '@deepseek-ai/dsh-llm' +import PluginManager from '../src/index.ts' +import * as operations from '../src/operations.ts' +import * as appBoot from '@deepseek-ai/dsh-app-boot' + +async function fixture(options: { title?: boolean; hmr?: boolean; presets?: boolean } = {}) { + const home = mkdtempSync(join(tmpdir(), 'preset-bundle-')) + let owner: Context | undefined + onTestFinished(async () => { await owner?.fiber.dispose(); rmSync(home, { recursive: true, force: true }) }) + const dir = join(home, 'profiles', 'test') + const anchor = join(home, 'package.json') + writeFileSync(anchor, '{"name":"test-installation","dependencies":{}}\n') + initProfile(dir, ['core']) + writeFileSync(join(dir, 'cordis.yml'), '[]\n') + const bundle = (name: string, patches: ProfilePatch[]): string => { + const path = join(dir, 'node_modules', name) + mkdirSync(path, { recursive: true }) + writeFileSync(join(path, 'package.json'), JSON.stringify({ name, version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } })) + writeFileSync(join(path, 'cordis.patch.yml'), JSON.stringify(patches)) + writeFileSync(join(path, 'plugin.mjs'), 'export function apply(ctx, config) { if (config?.fail) throw new Error("scoped plugin failed"); ctx.get("scopedBundleTrace").push("start"); ctx.effect(() => () => { ctx.get("scopedBundleTrace").push("stop") }) }\n') + return path + } + bundle('core', [{ insert: [ + { id: 'manager', name: 'cordis:manager' }, + { id: 'sessions', name: 'cordis:sessions' }, + { id: 'session-projections', name: 'cordis:session-projections' }, + { id: 'presets', name: 'cordis:presets', config: { default: 'standard' } }, + ...(options.presets === false ? [] : ['standard', 'cordis']).map(id => ({ id: `preset-${id}`, name: 'cordis:preset', config: { id, plugins: [] } })), + ...options.title === true ? [ + { id: 'title', name: 'cordis:title', config: { fallbackMaxWords: 5, fallbackMaxBytes: 100, maxTitleBytes: 100 } }, + { id: 'default-title', name: 'cordis:title-provider', config: { id: 'default' } }, + ] : [], + ] }]) + const addon = bundle('addon', ['standard', 'cordis'].map(preset => ({ preset: `preset-${preset}`, insert: [{ + id: 'shared-id', name: './plugin.mjs', + }] }))) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { addon: '1.0.0' } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + const profile: ProfileContext = { + name: 'test', dir, patchPath: join(dir, 'cordis.patch.yml'), installAnchor: anchor, + cwd: home, home, startedBundles: ['core'], overlays: [], telemetryDisabledEnv: undefined, + } + const resolution = await createRuntimeResolution({ installAnchor: anchor, profile: loadProfileDirectory('test', dir, anchor), home }) + const calls: { + id: string + request: SessionTitleProviderRequest + pending: ReturnType> + }[] = [] + const entered: { current: ReturnType> } = { current: Promise.withResolvers() } + const trace: string[] = [] + const ctx = await boot('test', join(dir, 'cordis.yml'), readProfilePatches('test', profile), async (host) => { + owner = host + host.provide('profileContext', profile) + host.provide('scopedBundleTrace', trace) + host.provide('appReady', { onReady(listener) { listener(); return () => {} } }) + Object.assign(host.loader.builtins, { + manager: PluginManager, sessions: SessionStore, 'session-projections': SessionProjections, + presets: AgentPresets, preset: AgentPreset, title: SessionTitle, + 'title-provider': { + inject: ['sessionTitle'], + apply(ctx: Context, config: { id: string }) { + ctx.sessionTitle.register({ + id: SessionTitleProviderId(config.id), automatic: 'all-prompts', + generate(request) { + const pending = Promise.withResolvers() + calls.push({ id: config.id, request, pending }) + entered.current.resolve(undefined) + return pending.promise + }, + }) + }, + }, + }) + await host.plugin(PluginPackages, { resolution }) + }) + await ctx.plugin(Timer) + if (options.hmr !== false) { + await ctx.plugin(Hmr, { root: [], ignored: [], debounce: 0 }) + await ctx.hmr.runExclusive(async () => {}) + } + onTestFinished(() => { + for (const call of calls) call.pending.resolve({ title: 'teardown', messageSeqs: call.request.messages.map(message => message.seq) }) + }) + return { ctx, manager: ctx.pluginManager, profile, dir, addon, bundle, trace, calls, entered } +} + +it('lists scoped declarations while Off and keeps equal inner ids distinct across presets', async () => { + const { manager, addon } = await fixture() + const patchFile = join(addon, 'cordis.patch.yml') + const patches = JSON.parse(readFileSync(patchFile, 'utf8')) as ProfilePatch[] + writeFileSync(patchFile, JSON.stringify([...patches, + { id: 'shared-id', disabled: true }, + { preset: 'preset-standard', id: 'absent', disabled: true }, + ])) + const moduleName = pathToFileURL(join(addon, 'plugin.mjs')).href + expect((await manager.listBundles()).find(row => row.name === 'addon')?.rows).toEqual([ + { rowId: 'shared-id', preset: 'preset-standard', readOnlyReason: 'preset-managed', moduleName }, + { rowId: 'shared-id', preset: 'preset-cordis', readOnlyReason: 'preset-managed', moduleName }, + ]) + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied', changed: true }) + const enabled = (await manager.listBundles()).find(row => row.name === 'addon')! + expect(enabled.rows).toEqual([ + { rowId: 'shared-id', preset: 'preset-standard', readOnlyReason: 'preset-managed', moduleName, composition: { enabled: true, fiberPhase: 'active' } }, + { rowId: 'shared-id', preset: 'preset-cordis', readOnlyReason: 'preset-managed', moduleName, composition: { enabled: true, fiberPhase: 'active' } }, + ]) + expect(enabled.rows.every(row => row.entryId === undefined)).toBe(true) + expect(enabled.overrides).toEqual(['shared-id', 'preset-standard/absent']) + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + expect((await manager.listBundles()).find(row => row.name === 'addon')?.rows.every(row => row.composition === undefined)).toBe(true) +}) + +it('loads a third-party relative child inside a real preset group and retains its package until release', async () => { + const { manager, ctx, dir, addon, bundle, trace } = await fixture() + bundle('addon', [{ preset: 'preset-standard', insert: [{ + id: 'relative-group', name: 'cordis:group', group: true, config: [{ + id: 'relative-child', name: './nested/relative.mjs', config: { value: 'nested-preset' }, + }], + }] }]) + mkdirSync(join(addon, 'nested')) + writeFileSync(join(addon, 'nested/relative.mjs'), [ + 'export function apply(ctx, config) {', + ' ctx.get("scopedBundleTrace").push(`relative:start:${config.value}`)', + ' ctx.effect(() => () => { ctx.get("scopedBundleTrace").push("relative:stop") })', + '}', + '', + ].join('\n')) + const moduleName = pathToFileURL(join(addon, 'nested/relative.mjs')).href + const declared = (await manager.listBundles()).find(row => row.name === 'addon')?.rows + .find(row => row.rowId === 'relative-child') + expect(declared).toEqual({ rowId: 'relative-child', preset: 'preset-standard', readOnlyReason: 'preset-managed', moduleName }) + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + expect(trace).toEqual(['relative:start:nested-preset']) + expect((await ctx.agentPresets.compositionInventory()).find(preset => preset.id === 'standard')?.rows) + .toMatchObject([{ entryId: 'relative-child', moduleName, enabled: true }]) + + const owner = createScope(ctx, {}) + onTestFinished(() => owner.dispose()) + await ctx.agentPresets.mount(owner.ctx, 'standard') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + expect(ctx.agentPresets.inspectCompositions(owner.ctx)[0]?.modules).toMatchObject([{ moduleName }]) + expect(trace).toEqual(['relative:start:nested-preset']) + const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + const manifest = readProfileManifest('test', dir) + delete manifest.dependencies?.addon + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + return { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'remove.log') } + }) + onTestFinished(() => { remove.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' } }) + expect(remove).not.toHaveBeenCalled() + await owner.dispose() + expect(trace).toEqual(['relative:start:nested-preset', 'relative:stop']) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'applied', changed: true }) + expect(remove).toHaveBeenCalledOnce() +}) + +it('retains a failed selection, reports affected preset failures again, and keeps unrelated failures as warnings', async () => { + const { manager, dir, bundle, ctx } = await fixture() + bundle('broken', [{ preset: 'preset-standard', insert: [{ id: 'broken', name: './plugin.mjs', config: { fail: true } }] }]) + const result = await manager.setBundleEnabled('broken', true) + expect(result).toMatchObject({ application: 'failed', changed: true, error: { diagnostic: expect.stringContaining('agent preset standard') as string } }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain('broken') + expect(await manager.setBundleEnabled('broken', true)).toMatchObject({ application: 'failed', changed: false }) + expect((await ctx.agentPresets.list()).find(row => row.id === 'standard')?.broken).toContain('scoped plugin failed') + bundle('unrelated', [{ insert: [{ id: 'unrelated', name: './plugin.mjs' }] }]) + expect(await manager.setBundleEnabled('unrelated', true)).toMatchObject({ application: 'applied', warnings: [expect.stringContaining('agent preset standard')] }) + expect(await manager.setBundleEnabled('broken', false)).toMatchObject({ application: 'applied' }) + expect((await ctx.agentPresets.list()).every(row => row.broken === undefined)).toBe(true) +}) + +it('refuses a no-op scoped enable when its Host preset row already failed before registering', async () => { + const { manager, ctx, bundle, dir } = await fixture() + bundle('invalid-definition', [{ id: 'preset-standard', config: { id: '', plugins: [] } }]) + expect(await manager.setBundleEnabled('invalid-definition', true)).toMatchObject({ application: 'failed' }) + expect((await ctx.agentPresets.list()).map(row => row.id)).not.toContain('standard') + bundle('scoped-noop', [{ preset: 'preset-standard', insert: [] }]) + expect(await manager.setBundleEnabled('scoped-noop', true)).toMatchObject({ + application: 'failed', changed: true, error: { diagnostic: expect.stringContaining('Preset id must not be empty') as string }, + }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain('scoped-noop') +}) + +it.each([{ id: 'standard' }, { id: 'standard', plugins: [] }])( + 'keeps existing preset failures as warnings when an unrelated Host config is %j', async (config) => { + const { manager, bundle, ctx } = await fixture() + expect(await ctx.agentPresets.list()).toMatchObject([{ id: 'cordis', definitionEntryId: 'preset-cordis' }, { id: 'standard', definitionEntryId: 'preset-standard' }]) + expect((await ctx.agentPresets.remoteExportList()).presets.every(row => !Object.hasOwn(row, 'definitionEntryId'))).toBe(true) + bundle('broken', [{ preset: 'preset-standard', insert: [{ id: 'broken', name: './plugin.mjs', config: { fail: true } }] }]) + expect(await manager.setBundleEnabled('broken', true)).toMatchObject({ application: 'failed' }) + bundle('ordinary', [{ insert: [{ id: 'ordinary', name: './plugin.mjs', config }] }]) + expect(await manager.setBundleEnabled('ordinary', true)).toMatchObject({ + application: 'applied', warnings: [expect.stringContaining('agent preset standard')], + }) + }, +) + +it('reports invalid composition immediately without HMR while retaining the saved selection', async () => { + const { manager, dir } = await fixture({ hmr: false, presets: false }) + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ + application: 'failed', changed: true, error: { diagnostic: expect.stringContaining(join('addon', 'cordis.patch.yml')) as string }, + }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toContain('addon') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'restart-required', changed: true }) + expect(readProfileManifest('test', dir).dsh?.profile?.bundles).not.toContain('addon') +}) + +it('refuses Remove after Off while an old Agent scope retains the bundle, then removes after release', async () => { + const { manager, ctx, dir, trace } = await fixture() + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + const agent = createScope(ctx, {}) + onTestFinished(() => agent.dispose()) + await ctx.agentPresets.mount(agent.ctx, 'standard') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + expect(ctx.agentPresets.inspectCompositions(agent.ctx)[0]?.modules).toHaveLength(1) + const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + const manifest = readProfileManifest('test', dir) + delete manifest.dependencies?.addon + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + return { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'remove.log') } + }) + onTestFinished(() => { remove.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' }, changed: false }) + expect(remove).not.toHaveBeenCalled() + await agent.dispose() + expect(trace.filter(item => item === 'stop')).toHaveLength(2) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'applied', changed: true }) + expect(remove).toHaveBeenCalledOnce() +}) + +it.each([false, true].flatMap(anonymous => [false, true].map(archive => ({ anonymous, archive })))) +('removes a released contribution while base presets keep using the same shared module (anonymous=$anonymous, archive=$archive)', async ({ anonymous, archive }) => { + // Resolver identities must be created under the same carrier that later refreshes them. + if (archive) { + const previousPkg = Object.getOwnPropertyDescriptor(process, 'pkg') + onTestFinished(() => { + if (previousPkg === undefined) Reflect.deleteProperty(process, 'pkg') + else Object.defineProperty(process, 'pkg', previousPkg) + }) + Object.defineProperty(process, 'pkg', { configurable: true, value: {} }) + } + const { manager, ctx, dir, addon } = await fixture() + const shared = join(dir, 'node_modules', 'shared-fixture') + mkdirSync(shared) + writeFileSync(join(shared, 'package.json'), JSON.stringify({ name: 'shared-fixture', version: '1.0.0', type: 'module', exports: './index.mjs' })) + writeFileSync(join(shared, 'index.mjs'), 'export function apply() {}\n') + const core = join(dir, 'node_modules', 'core') + const corePatches = JSON.parse(readFileSync(join(core, 'cordis.patch.yml'), 'utf8')) as ProfilePatch[] + for (const row of corePatches[0]!.insert!) { + if (row.id === 'preset-standard') row.config = { id: 'standard', plugins: [{ id: 'base-shared', name: 'shared-fixture' }] } + if (row.id === 'preset-cordis') row.config = { id: 'cordis', plugins: [{ id: 'addon-shared', name: 'shared-fixture' }] } + } + writeFileSync(join(core, 'cordis.patch.yml'), JSON.stringify(corePatches)) + writeFileSync(join(addon, 'cordis.patch.yml'), JSON.stringify([{ + preset: 'preset-standard', insert: [{ ...anonymous ? {} : { id: 'addon-shared' }, name: 'shared-fixture' }], + }])) + for (const packageDir of [core, addon]) { + const manifest = readProfileManifest('test', packageDir) + manifest.dependencies = { 'shared-fixture': '1.0.0' } + writeFileSync(join(packageDir, 'package.json'), JSON.stringify(manifest)) + } + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + const owner = createScope(ctx, {}) + onTestFinished(() => owner.dispose()) + await ctx.agentPresets.mount(owner.ctx, 'standard') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + expect(ctx.agentPresets.inspectCompositions(owner.ctx)).toMatchObject([{ + id: 'standard', definitionEntryId: 'preset-standard', modules: [ + { entryId: 'base-shared', moduleName: 'shared-fixture' }, + { entryId: anonymous ? expect.any(String) as string : 'addon-shared', moduleName: 'shared-fixture' }, + ], + }]) + if (archive) { + const original = fs.realpathSync + onTestFinished(() => { fs.realpathSync = original; syncBuiltinESMExports() }) + fs.realpathSync = new Proxy(original, { + apply(target, receiver: unknown, args: unknown[]): unknown { + if (['package.json', 'index.mjs'].some(file => String(args[0]).endsWith(join('shared-fixture', file)))) { + throw Object.assign(new Error('archive-backed files have no realpath entry'), { code: 'ENOENT' }) + } + return Reflect.apply(target, receiver, args) + }, + }) + syncBuiltinESMExports() + expect(() => fs.realpathSync(join(shared, 'package.json'))).toThrow('archive-backed') + expect(readFileSync(join(shared, 'index.mjs'), 'utf8')).toContain('export function apply') + } + const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => { + const manifest = readProfileManifest('test', dir) + delete manifest.dependencies?.addon + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + return { exitCode: 0, output: '', truncated: false, logPath: join(dir, 'remove.log') } + }) + onTestFinished(() => { remove.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' } }) + expect(remove).not.toHaveBeenCalled() + await owner.dispose() + expect(ctx.agentPresets.inspectCompositions().flatMap(preset => preset.modules).map(module => module.moduleName)) + .toEqual(['shared-fixture', 'shared-fixture']) + const removed = await manager.removeBundle('addon') + expect(removed, JSON.stringify(removed.error)).toMatchObject({ application: 'applied', changed: true }) + expect(remove).toHaveBeenCalledOnce() +}) + +it('removes an unrelated bundle when another retained row with the same id has lost its module file', async () => { + const { manager, ctx, dir, addon, bundle } = await fixture() + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + const owner = createScope(ctx, {}) + onTestFinished(() => owner.dispose()) + await ctx.agentPresets.mount(owner.ctx, 'standard') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + bundle('unrelated', [{ preset: 'preset-standard', insert: [{ id: 'shared-id', name: './plugin.mjs' }] }]) + const manifest = readProfileManifest('test', dir) + manifest.dependencies = { ...manifest.dependencies, unrelated: '1.0.0' } + writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest)) + rmSync(join(addon, 'plugin.mjs')) + const remove = vi.spyOn(operations, 'runProfilePnpm').mockResolvedValue({ + exitCode: 0, output: '', truncated: false, logPath: join(dir, 'remove.log'), + }) + onTestFinished(() => { remove.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' } }) + expect(remove).not.toHaveBeenCalled() + const resolution = vi.spyOn(appBoot, 'resolvePluginResource').mockImplementationOnce(() => { throw new Error('resolver unavailable') }) + onTestFinished(() => { resolution.mockRestore() }) + expect(await manager.removeBundle('unrelated')).toMatchObject({ application: 'failed', error: { diagnostic: 'resolver unavailable' } }) + expect(remove).not.toHaveBeenCalled() + resolution.mockRestore() + expect(await manager.removeBundle('unrelated')).toMatchObject({ application: 'applied' }) + expect(remove).toHaveBeenCalledOnce() + expect(ctx.agentPresets.inspectCompositions(owner.ctx)[0]?.modules).toHaveLength(1) +}) + +it('retains contributed builtin rows even when no package owns their module names', async () => { + const { manager, ctx, bundle } = await fixture() + ctx.loader.builtins['retained-fixture'] = { apply() {} } + bundle('addon', [{ preset: 'preset-standard', insert: [{ id: 'builtin', name: 'cordis:retained-fixture' }] }]) + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + const agent = createScope(ctx, {}) + onTestFinished(() => agent.dispose()) + await ctx.agentPresets.mount(agent.ctx, 'standard') + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + const inspect = ctx.agentPresets.inspectCompositions.bind(ctx.agentPresets) + const projection = vi.spyOn(ctx.agentPresets, 'inspectCompositions').mockImplementation(() => inspect().map(composition => ({ + ...composition, modules: composition.modules.map(({ baseUrl: _baseUrl, ...module }) => module), + }))) + onTestFinished(() => { projection.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' } }) +}) + +it('refuses removal when a different module from the same package remains in a preset', async () => { + const { manager, ctx, dir, addon } = await fixture() + const source = readFileSync(join(addon, 'plugin.mjs'), 'utf8') + writeFileSync(join(addon, 'other.mjs'), source) + expect(await manager.setBundleEnabled('addon', true)).toMatchObject({ application: 'applied' }) + writeFileSync(join(dir, 'cordis.patch.yml'), JSON.stringify([{ + preset: 'preset-standard', insert: [{ id: 'independent', name: pathToFileURL(join(addon, 'other.mjs')).href }], + }])) + expect(await manager.setBundleEnabled('addon', false)).toMatchObject({ application: 'applied' }) + expect(ctx.agentPresets.inspectCompositions().some(row => row.modules.some(module => module.moduleName.endsWith('/other.mjs')))).toBe(true) + const remove = vi.spyOn(operations, 'runProfilePnpm') + onTestFinished(() => { remove.mockRestore() }) + expect(await manager.removeBundle('addon')).toMatchObject({ application: 'failed', error: { code: 'bundle-in-use' } }) + expect(remove).not.toHaveBeenCalled() +}) + +async function requestTitle(session: Session): Promise { + session.append('turn/start', { turn: 1 }) + session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Title for provider switch' }], source: { kind: 'user' } }), { surfaceOp: 'append' }) + await new Promise((resolve) => { queueMicrotask(resolve) }) + session.append('request/header', { header: { config: { provider: 'test', model: 'test' } }, reason: 'initial' }) +} + +it('drains and rejects stale title completions before activating each replacement in both directions', async () => { + const { manager, ctx, bundle, calls, entered } = await fixture({ title: true }) + bundle('titles', [ + { id: 'default-title', disabled: true }, + { insert: [{ id: 'optional-title', name: 'cordis:title-provider', config: { id: 'optional' } }] }, + ]) + for (const [enabled, expected] of [[true, 'default'], [false, 'optional']] as const) { + entered.current = Promise.withResolvers() + const session = ctx.sessions.create(SessionId(`title-switch-${String(enabled)}`)) + await requestTitle(session) + await entered.current.promise + const call = calls.at(-1)! + expect(call.id).toBe(expected) + const aborted = Promise.withResolvers() + call.request.signal.addEventListener('abort', () => { aborted.resolve(undefined) }, { once: true }) + let complete = false + const switching = manager.setBundleEnabled('titles', enabled).then((result) => { complete = true; return result }) + onTestFinished(async () => { + call.pending.resolve({ title: 'teardown', messageSeqs: call.request.messages.map(message => message.seq) }) + await switching + }) + await aborted.promise + expect(complete).toBe(false) + expect(calls).toHaveLength(enabled ? 1 : 2) + call.pending.resolve({ title: 'stale result', messageSeqs: call.request.messages.map(message => message.seq) }) + expect(await switching).toMatchObject({ application: 'applied' }) + expect(ctx.sessionTitle.get(session)?.source.kind).toBe('fallback') + } + entered.current = Promise.withResolvers() + const final = ctx.sessions.create(SessionId('title-switch-final')) + await requestTitle(final) + await entered.current.promise + expect(calls.at(-1)?.id).toBe('default') + calls.at(-1)?.pending.resolve({ title: 'final title', messageSeqs: calls.at(-1)!.request.messages.map(message => message.seq) }) +}) diff --git a/packages/boot/plugin-manager/tests/tools.spec.ts b/packages/boot/plugin-manager/tests/tools.spec.ts index e18b5acce8..bc7da82dfe 100644 --- a/packages/boot/plugin-manager/tests/tools.spec.ts +++ b/packages/boot/plugin-manager/tests/tools.spec.ts @@ -10,6 +10,7 @@ import SessionProjections from '@deepseek-ai/dsh-session-projection' import ApprovalService, { type ApprovalOutcome } from '@deepseek-ai/dsh-user-approval' import { expect, it, onTestFinished, vi } from 'vitest' import type PluginManager from '../src/index.ts' +import type { BundleInfo } from '../src/types.ts' import * as tool from '../src/tools.ts' function resultText(result: Awaited>): string { @@ -80,6 +81,40 @@ it('preserves selected bundle load errors in the agent list result', async () => `) }) +it('exposes an absent Official bundle installation target without display metadata', async () => { + const { manager, call } = await fixture() + const offered: BundleInfo = { + name: '@deepseek-ai/dsh-subagent-codex', official: true, availability: 'missing', + installed: false, enabled: false, optional: false, removable: false, rows: [], overrides: [], + installTarget: { spec: '@deepseek-ai/dsh-subagent-codex@1.2.3', version: '1.2.3' }, + meta: { title: 'Codex subagent' }, + } + manager.listBundles.mockResolvedValue([offered]) + expect(JSON.parse(resultText(await call({ action: 'list_bundles' })))).toMatchInlineSnapshot(` + { + "entries": [ + { + "availability": "missing", + "enabled": false, + "installTarget": { + "spec": "@deepseek-ai/dsh-subagent-codex@1.2.3", + "version": "1.2.3", + }, + "installed": false, + "name": "@deepseek-ai/dsh-subagent-codex", + "official": true, + "optional": false, + "overrides": [], + "removable": false, + "rows": [], + }, + ], + "nextOffset": null, + "total": 1, + } + `) +}) + it('checks the calling session on each execution, including after permission is revoked', async () => { const { call, manager } = await fixture() const id = SessionId('manager-permissions') diff --git a/packages/boot/plugin-manager/tsconfig.json b/packages/boot/plugin-manager/tsconfig.json index ba181795a1..643f870407 100644 --- a/packages/boot/plugin-manager/tsconfig.json +++ b/packages/boot/plugin-manager/tsconfig.json @@ -64,6 +64,9 @@ }, { "path": "../../interaction/user-approval" + }, + { + "path": "../../preset/agent-preset-registry" } ] } diff --git a/packages/browser-use/browser-use/README.i18n.yaml b/packages/browser-use/browser-use/README.i18n.yaml index ef8440b211..0b7404b051 100644 --- a/packages/browser-use/browser-use/README.i18n.yaml +++ b/packages/browser-use/browser-use/README.i18n.yaml @@ -18,8 +18,8 @@ en: d0c5ace058c0572a zh: 2bbfa786c1f9ba53 /deepseek-ai-dsh-browser-use/understand-the-implementation: - en: e8421af75ba37792 - zh: 3dbc5e6ff5291972 + en: c4d9ec0df22a30ea + zh: 06057e2a56cebbb9 /deepseek-ai-dsh-browser-use/further-exploration: en: 5ae59b1b38304607 zh: 429171c1a3fb914c diff --git a/packages/browser-use/browser-use/README.md b/packages/browser-use/browser-use/README.md index d721f4f22e..859345994d 100644 --- a/packages/browser-use/browser-use/README.md +++ b/packages/browser-use/browser-use/README.md @@ -45,8 +45,6 @@ Providers stop admitting tool calls, close their resources, and await owned work One private name owns the slot. Cordis effects remove contributions when their plugin unloads; a repeated disposer cannot remove a later registration. The [source](src/index.ts) contains no browser object, operation interface, resource lifecycle, or provider selector. -No runtime invariant companion is published: the registry has one authoritative field and exposes no independently maintained observation that can diverge. Duplicate rejection and plugin disposal are covered by the owning tests. - ----- diff --git a/packages/browser-use/browser-use/README.zh.md b/packages/browser-use/browser-use/README.zh.md index fd1b2dc832..17d1590eb2 100644 --- a/packages/browser-use/browser-use/README.zh.md +++ b/packages/browser-use/browser-use/README.zh.md @@ -45,8 +45,6 @@ kind: "package-reference" 一个私有名称拥有该注册位。Cordis effect 在插件卸载时移除贡献;重复调用清理器不能移除后续注册。[源码](src/index.ts) 不包含浏览器对象、操作接口、资源生命周期或 提供方 选择器。 -不发布运行时不变量伴随入口:注册表只有一个权威字段,不暴露可能与之偏离的独立维护观测值。所属测试覆盖重复注册拒绝与插件清理。 - ----- diff --git a/packages/browser-use/browser-use/package.json b/packages/browser-use/browser-use/package.json index d5f5e322ec..8178f5d01d 100644 --- a/packages/browser-use/browser-use/package.json +++ b/packages/browser-use/browser-use/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-browser-use", "description": "Exclusive named browser-use provider registration", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/acp-app/README.i18n.yaml b/packages/bundle/acp-app/README.i18n.yaml index 39606fc9d9..fe732545af 100644 --- a/packages/bundle/acp-app/README.i18n.yaml +++ b/packages/bundle/acp-app/README.i18n.yaml @@ -27,8 +27,8 @@ en: a7451ee84e5e156a zh: a7451ee84e5e156a /deepseek-ai-dsh-acp-app/model-experience/acp-coding-agent-persona/what-the-model-sees: - en: bc0db4c628a1e547 - zh: 740d07803bca3ce4 + en: eab16c37bdefccf5 + zh: 93b318b3959e2c5f /deepseek-ai-dsh-acp-app/model-experience/acp-coding-agent-persona/token-effect: en: 371d28831239e0a8 zh: 0d2678654607b657 @@ -39,5 +39,5 @@ en: e5e9b2ef3b75e6f6 zh: f34d43d5dd68ab60 /deepseek-ai-dsh-acp-app/known-limitations-and-deferred-work/dev-note: - en: 8bc6c774f783517d - zh: d1643c271de867ae + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/bundle/acp-app/README.md b/packages/bundle/acp-app/README.md index c770e25bbc..d67f9421c1 100644 --- a/packages/bundle/acp-app/README.md +++ b/packages/bundle/acp-app/README.md @@ -44,7 +44,7 @@ The complete supported method matrix, MCP trust model, update mapping, and stop #### What the model sees -The profile supplies `You are a coding agent powered by the {{model}} model.` before first-party guidance and `Your working directory is {{cwd}}.` in a separate persona suffix. The ACP row's route and each `session/new` cwd resolve the placeholders. +The profile supplies `You are a coding agent powered by the {{model}} model.` before first-party guidance; the ACP route resolves `{{model}}`. Each Session receives its current directory in required user-role context from [`dsh-working-directory`](../../session/working-directory/README.md). #### Token effect @@ -72,5 +72,3 @@ Stable for a fixed profile, provider, model, and tool roster. Profile changes ta None. - -**Runtime invariant:** No companion is published. The bundle adds a process transport and startup latch; source/built stdio tests own frame purity, help exclusion, and shutdown. diff --git a/packages/bundle/acp-app/README.zh.md b/packages/bundle/acp-app/README.zh.md index 3f87f92b93..53ebac8a3a 100644 --- a/packages/bundle/acp-app/README.zh.md +++ b/packages/bundle/acp-app/README.zh.md @@ -44,7 +44,7 @@ ACP v1 SDK 客户端先初始化 `dsh --profile acp`,再用绝对 `cwd` 与可 #### 模型看到什么 -profile 在第一方指导之前提供 `You are a coding agent powered by the {{model}} model.`,并在独立的 persona 后缀中提供 `Your working directory is {{cwd}}.`。ACP 配置项的路由与每个 `session/new` 的 cwd 会解析其中的占位符。 +profile 在第一方指导之前提供 `You are a coding agent powered by the {{model}} model.`;ACP 路由解析 `{{model}}`。每个 Session 通过 [`dsh-working-directory`](../../session/working-directory/README.zh.md) 提供的必需 user-role 上下文接收当前目录。 #### Token 影响 @@ -72,5 +72,3 @@ profile 在第一方指导之前提供 `You are a coding agent powered by the {{ 无。 - -**运行时不变式:** 不发布伴生入口。该 bundle 只增加进程传输与启动 latch;帧纯度、help 排除和关闭行为由源码及构建产物的 stdio 测试负责。 diff --git a/packages/bundle/acp-app/cordis.patch.yml b/packages/bundle/acp-app/cordis.patch.yml index 313dab80ae..ca261faae8 100644 --- a/packages/bundle/acp-app/cordis.patch.yml +++ b/packages/bundle/acp-app/cordis.patch.yml @@ -2,7 +2,6 @@ - id: system-prompt config: - personaSuffix: Your working directory is {{cwd}}. personaPrefix: >- You are a coding agent powered by the {{model}} model. diff --git a/packages/bundle/acp-app/package.json b/packages/bundle/acp-app/package.json index fd1590a36c..d1d12860c2 100644 --- a/packages/bundle/acp-app/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/base/README.i18n.yaml b/packages/bundle/base/README.i18n.yaml index 4aff8b80e2..5b191312a7 100644 --- a/packages/bundle/base/README.i18n.yaml +++ b/packages/bundle/base/README.i18n.yaml @@ -21,8 +21,8 @@ en: 0f971f7ac7c09139 zh: ceb866da681c709c /deepseek-ai-dsh-base/use-this-package/what-you-get: - en: 7ee3a3def76e0ce6 - zh: b53b1fdc78cf0339 + en: 5bfb7755e987862c + zh: 1f0e9ea7a826a509 /deepseek-ai-dsh-base/use-this-package/shell-tools-per-platform: en: 2dce12f82d6ee955 zh: d4599eb899a3cd88 @@ -39,11 +39,8 @@ en: 8c2444fa7b237783 zh: 82002a8b9ddf5942 /deepseek-ai-dsh-base/understand-the-implementation/source-map: - en: 3aaf655b9c93534b - zh: 5a1b627cc6385a17 -/deepseek-ai-dsh-base/understand-the-implementation/invariant-ownership: - en: 533299d7bd555e61 - zh: 0aed404f790ea447 + en: a8efda71d444155e + zh: c8221593709fbb99 /deepseek-ai-dsh-base/further-exploration: en: 39701b25b209fb67 zh: 2b7ce98365f1ecfc diff --git a/packages/bundle/base/README.md b/packages/bundle/base/README.md index 98f4b148bf..9474a8ce63 100644 --- a/packages/bundle/base/README.md +++ b/packages/bundle/base/README.md @@ -49,7 +49,9 @@ Run `dsh --profile my-profile "your task"` and you get a working agent with mode Out of the box, every profile built on this core provides: a DeepSeek model connection (the provider and model are configurable, and you can enable extra providers from your settings), the full tool set — file editing, shell commands, web search, public HTTP(S) fetch, subagents, task and goal tracking — durable sessions that survive restarts, and the default permission policy that confines file writes to your workspace and asks before risky actions. Web fetch runs without per-call approval; its provider rejects non-public destinations. Feedback stays in the Session log. [OTel session upload](../../session/session-telemetry-otel/README.md) defaults to `FEEDBACK_ONLY` for all users, including `deepseek-official`: new text feedback, message ratings, edits, and withdrawals release the complete canonical prefix through that event, including context. Later records wait for the next explicit feedback; sending an authorized batch needs no further interaction or model call. `DISABLED` prevents OTel capture. The default-on [DeepSeek session-log contributor](../../session/session-log-deepseek/README.md) remains a separate request path. -Default file editing uses `read`, `write`, and `edit`. The `str_replace_editor` tool remains available as an explicit opt-in. To add it to a base-backed profile, put this entry in the profile, home, or invocation patch: +Both `subagent` and `subagent_fork` create managed children and return a child id immediately. Use `send_message` to steer active children or continue their conversations after completion. [Delegation-tool behavior](../../subagent/tool-subagent/README.md) describes result delivery and backend capabilities. + +The built-in `working_directory` tool changes the directory for subsequent operations while existing processes and write permissions remain independent. Current directory information appears in user context. Default file editing uses `read`, `write`, and `edit`. The `str_replace_editor` tool remains available as an explicit opt-in. To add it to a base-backed profile, put this entry in the profile, home, or invocation patch: ```yaml - insert: @@ -93,13 +95,8 @@ The patch gates the two shell stacks by platform on its own rows: `bash-sandbox` |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | The bundle substance: the base plugin rows, with per-row rationale as inline comments | | [`src/index.ts`](src/index.ts) | Package entry; carries no runtime API | -| — | No runtime invariant companion is published; the package is a static patch-list carrier (a YAML document of loader rows owned by other packages); it mounts no service, emits no events, and owns no mutable relation to check. Each inserted row's own package carries that row's invariants. | | [`tests/base.spec.ts`](tests/base.spec.ts) | Manifest declaration and platform-gating checks | -### Invariant ownership - -No invariant companion is published because the package is a static patch-list carrier: each inserted row's package owns that row's invariants, and the bundle owns no mutable relation to check. - ----- diff --git a/packages/bundle/base/README.zh.md b/packages/bundle/base/README.zh.md index 2ed43b780b..b4e45bcaf6 100644 --- a/packages/bundle/base/README.zh.md +++ b/packages/bundle/base/README.zh.md @@ -49,7 +49,9 @@ kind: "package-bundle" 开箱即用,基于本核心构建的每个 profile 都提供:DeepSeek 模型连接(提供方与模型可配置,你还可以在设置中启用额外提供方)、完整工具集——文件编辑、shell 命令、web 搜索、公开 HTTP(S) 抓取、subagent、任务与目标跟踪——可跨重启存活的持久会话,以及默认权限策略:把文件写入限制在工作区内,危险操作前征询许可。Web 抓取无需逐次审批,其提供方会拒绝非公开目的地址。反馈保存在会话日志中。[OTel 会话上传](../../session/session-telemetry-otel/README.zh.md)对所有用户默认使用 `FEEDBACK_ONLY`,包括 `deepseek-official`:新的文本反馈、消息评分、编辑与撤回会释放截至该事件的完整规范会话日志前缀,包含上下文。后续记录等待下一次显式反馈;发送已授权批次无需进一步交互或模型调用。`DISABLED` 阻止 OTel 捕获。默认开启的 [DeepSeek 会话日志贡献器](../../session/session-log-deepseek/README.zh.md)仍是独立的请求路径。 -默认文件编辑使用 `read`、`write` 和 `edit`。`str_replace_editor` 工具仍可显式启用。要将它加入基于 base 的 profile,请在 profile、home 或逐次调用 patch 中添加以下条目: +`subagent` 与 `subagent_fork` 都创建受管理的子任务,并立即返回 child id。使用 `send_message` 可引导活跃子任务,或在完成后继续其对话。[委派工具行为](../../subagent/tool-subagent/README.zh.md)说明了结果交付和后端能力。 + +内置 `working_directory` 工具改变后续操作使用的目录,已有进程与写权限保持独立。当前目录信息出现在用户上下文中。默认文件编辑使用 `read`、`write` 和 `edit`。`str_replace_editor` 工具仍可显式启用。要将它加入基于 base 的 profile,请在 profile、home 或逐次调用 patch 中添加以下条目: ```yaml - insert: @@ -93,13 +95,8 @@ patch 在自身上按平台门控两个 shell 栈:`bash-sandbox` 与 `tool-bas |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | 组合包的实体:基础插件行,附以行内注释说明各行依据 | | [`src/index.ts`](src/index.ts) | 包入口;不携带任何运行时 API | -| — | 不发布运行时不变式伴生入口;本包是静态 patch 列表载体(由其他包拥有的 loader 行构成的 YAML 文档);它不挂载任何服务、不发出任何事件,也没有任何可检查的可变关系。每条插入行所属的包负责该行的不变式。 | | [`tests/base.spec.ts`](tests/base.spec.ts) | manifest(元数据清单)声明与平台门控检查 | -### 不变式归属 - -不发布不变式伴生入口,因为本包是静态 patch 列表载体:每条插入行由所属的包负责其不变式,组合包自身没有任何可审计的可变关系。 - ----- diff --git a/packages/bundle/base/cordis.patch.yml b/packages/bundle/base/cordis.patch.yml index 9c35ebf5c2..5219c2444f 100644 --- a/packages/bundle/base/cordis.patch.yml +++ b/packages/bundle/base/cordis.patch.yml @@ -65,7 +65,10 @@ targetWords: 5 targetCjkCharacters: 10 maxInputBytes: 4096 - maxOutputTokens: 64 + # A ceiling against runaway output, not a length target: the prompt + # keeps titles short, while reasoning a model cannot switch off also + # counts as output. Stays within common model output limits. + maxOutputTokens: 4096 timeoutMs: 60000 - id: user-questions @@ -155,6 +158,12 @@ # Shared projection registry: subagent catalog identity (mode/label) folds # through its registered units, so the `list_agents` surface below fails # loud without it; web layers reuse this same mount for list rows. + - id: working-directory + name: '@deepseek-ai/dsh-working-directory' + + - id: tool-working-directory + name: '@deepseek-ai/dsh-tool-working-directory' + - id: session-projection name: '@deepseek-ai/dsh-session-projection' @@ -297,10 +306,6 @@ - id: skill-filesystem name: '@deepseek-ai/dsh-skill-filesystem' - - id: skill-badge - name: '@deepseek-ai/dsh-skill-badge' - disabled: true - - id: tool-skill name: '@deepseek-ai/dsh-tool-skill' @@ -372,12 +377,10 @@ config: provider: spawn toolName: subagent - backgroundMode: continuable # Fork omits model selection so provider/model stay equal to the parent and - # the inherited history remains eligible for KV Cache reuse. This base row - # stays one-shot; preset layers may select continuable mode without adding a - # child-only system-prompt section or tool schema ahead of that history. + # the inherited history remains eligible for KV Cache reuse. Parent and child + # share the messaging tools; return guidance follows the inherited history. # See .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md # and .agents/notes/implemented/architecture/2026-08-10-fork-children-stay-one-shot.md. - id: tool-subagent-fork @@ -385,7 +388,6 @@ config: provider: fork toolName: subagent_fork - backgroundMode: one-shot - id: ptc-runtime name: '@deepseek-ai/dsh-ptc-runtime-node' @@ -437,20 +439,6 @@ - id: tool-goal name: '@deepseek-ai/dsh-tool-goal' - # Fresh-agent Ralph iteration over a build-time-fixed script. Off by - # default: the tool description restricts `ralph` to runs the human - # explicitly asked for, and completion is a worker self-report, not an - # independent evaluation. An overlay row restores it for a base-backed - # profile (`$DSH_HOME/cordis.patch.yml`, or a `--patch `): - # - id: tool-ralph - # disabled: false - - id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' - disabled: true - config: - subagentProvider: spawn - maxRounds: 64 - # Consecutive-repeat reminders on the tool chain. - id: repeat-tool-reminder name: '@deepseek-ai/dsh-repeat-tool-reminder' diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index be14c75ad5..be152900d3 100644 --- a/packages/bundle/base/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -81,7 +81,6 @@ "@deepseek-ai/dsh-session-title": "workspace:*", "@deepseek-ai/dsh-session-title-first-prompt-llm": "workspace:*", "@deepseek-ai/dsh-skill": "workspace:*", - "@deepseek-ai/dsh-skill-badge": "workspace:*", "@deepseek-ai/dsh-skill-filesystem": "workspace:*", "@deepseek-ai/dsh-spill-local": "workspace:*", "@deepseek-ai/dsh-spill-policy": "workspace:*", @@ -102,7 +101,6 @@ "@deepseek-ai/dsh-tool-fs-search": "workspace:*", "@deepseek-ai/dsh-tool-goal": "workspace:*", "@deepseek-ai/dsh-tool-pwsh": "workspace:*", - "@deepseek-ai/dsh-tool-ralph": "workspace:*", "@deepseek-ai/dsh-tool-skill": "workspace:*", "@deepseek-ai/dsh-tool-subagent": "workspace:*", "@deepseek-ai/dsh-tool-subagent-control": "workspace:*", @@ -126,7 +124,9 @@ "@deepseek-ai/dsh-deepseek-account-platform": "workspace:*", "@deepseek-ai/dsh-config-editor": "workspace:*", "@deepseek-ai/dsh-settings": "workspace:*", - "@deepseek-ai/dsh-otel": "workspace:*" + "@deepseek-ai/dsh-otel": "workspace:*", + "@deepseek-ai/dsh-working-directory": "workspace:*", + "@deepseek-ai/dsh-tool-working-directory": "workspace:*" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/bundle/headless/README.i18n.yaml b/packages/bundle/headless/README.i18n.yaml index 5e6cce5acb..c3715b490f 100644 --- a/packages/bundle/headless/README.i18n.yaml +++ b/packages/bundle/headless/README.i18n.yaml @@ -36,20 +36,17 @@ en: be1b73a6d0ee3902 zh: bdba099685d68b27 /deepseek-ai-dsh-headless/understand-the-implementation/run-flow: - en: 2f2adf0c346c6c1b - zh: f1e988c7e597a852 + en: 3729fbdc598637da + zh: 254c6961b65b9e2b /deepseek-ai-dsh-headless/understand-the-implementation/patch-surface-over-base: en: a22752c052878579 zh: 1e42c946d8673eeb /deepseek-ai-dsh-headless/understand-the-implementation/exit-mapping: - en: cc6c485a42d40d28 - zh: c57775d9319d8dfd + en: c2466d3d8a26177a + zh: b75743bfa2e7723a /deepseek-ai-dsh-headless/understand-the-implementation/source-map: - en: 20a37f62e58b3ddb - zh: ede1fb8cfbae7a0e -/deepseek-ai-dsh-headless/understand-the-implementation/invariant-ownership: - en: 329f170dec4b2279 - zh: bb40f8beda5b3a1c + en: d82278600e0efdbd + zh: 0f546bdba164ef25 /deepseek-ai-dsh-headless/further-exploration: en: 65aebf0f8ab3047c zh: 85616d3859073c4b diff --git a/packages/bundle/headless/README.md b/packages/bundle/headless/README.md index d3eb8054e5..e7776f0591 100644 --- a/packages/bundle/headless/README.md +++ b/packages/bundle/headless/README.md @@ -77,7 +77,7 @@ The runner is a direct driver over the core API carrier: it resolves the Agent i ### Run flow -The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for quiescence, then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout (or the `final` event) and requests exit. +The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for child work and rechecks that the parent remains idle with an unchanged Session sequence before finishing, so settlement notices can drive a final parent answer. Idle children with parked inbox messages do not delay completion; process shutdown discards their unclaimed messages. It then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout (or the `final` event) and requests exit. ### Patch surface over base @@ -85,7 +85,7 @@ The patch rides over `dsh-base`: it inherits the projection cache and shared PTC ### Exit mapping -A completed final `turn/end` exits 0; any other outcome — aborted, error, or no turn in the owned interval — exits 1. An `error` reason also writes `dsh: : ` to stderr. A direct driver failure (for example, Agent creation or an unusable `--session-id`) writes `dsh: ` to stderr and exits 1, and in `--json` mode also emits an `error` event. +A completed final `turn/end` exits 0; any other outcome — aborted, error, or no turn in the owned interval — exits 1. An `error` reason also writes `dsh: : ` to stderr. A live Agent failure without a later committed `turn/end` is a direct driver failure, including a summary whose start or end cannot be persisted; an earlier completed turn cannot make that run succeed. A direct driver failure (for example, Agent creation or an unusable `--session-id`) writes `dsh: ` to stderr and exits 1, and in `--json` mode also emits an `error` event without a `final` event. ### Source map @@ -95,15 +95,10 @@ A completed final `turn/end` exits 0; any other outcome — aborted, error, or n | [`src/startup.ts`](src/startup.ts) | The `headless-startup` provider: task positional, `--session-id`, `--json`, and `--help` | | [`src/json-stream.ts`](src/json-stream.ts) | The `--json` projection: event vocabulary, commit-point emission, string bounding | | [`cordis.patch.yml`](cordis.patch.yml) | The one-shot patch over `dsh-base` | -| — | No runtime invariant companion is published; the runner's observable contract (provider reasoning on stderr, final text on stdout, exit code by turn-end reason) is process-level and owned by the launcher e2e; it registers nothing and holds no mutable relation to audit inside the tree. | | [`tests/headless.spec.ts`](tests/headless.spec.ts) | Run flow, aggregation, flush, session adoption, and exit mapping | | [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | Projection ordering, commit-point emission, bounding, and disposal | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree | -### Invariant ownership - -No invariant companion is published because the runner's observable contract (final text on stdout, exit code by turn-end reason) is process-level and owned by the launcher e2e; the plugin registers nothing and holds no mutable relation to audit inside the tree. - ----- diff --git a/packages/bundle/headless/README.zh.md b/packages/bundle/headless/README.zh.md index b3f4d44513..a7d35fdbe9 100644 --- a/packages/bundle/headless/README.zh.md +++ b/packages/bundle/headless/README.zh.md @@ -77,7 +77,7 @@ runner 是核心 API 载体之上的直接驱动器:它确定 Agent 标识— ### 运行流程 -runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待完全停稳,然后对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout(或 `final` 事件)并请求退出。 +runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待子级工作,并在结束前复核父级仍空闲且 Session 序号未变,让结算通知可以驱动父级生成最终答案。收件箱消息处于停放状态的空闲子级不会延迟完成;进程关闭时丢弃这些未领取消息。随后它对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout(或 `final` 事件)并请求退出。 ### 基于 base 的 patch 内容 @@ -85,7 +85,7 @@ patch 叠加在 `dsh-base` 之上:继承投影缓存与共享 PTC 运行时, ### 退出映射 -最终 `turn/end` 完成时退出码为 0;任何其他结果——aborted、error,或所属区间内没有轮次——退出码为 1。结束原因为 `error` 时还会向 stderr 写入 `dsh: : `。直接驱动器失败(例如 Agent 创建失败或不可用的 `--session-id`)向 stderr 写入 `dsh: ` 并退出 1,且在 `--json` 模式下额外发出一个 `error` 事件。 +最终 `turn/end` 完成时退出码为 0;任何其他结果——aborted、error,或所属区间内没有轮次——退出码为 1。结束原因为 `error` 时还会向 stderr 写入 `dsh: : `。若实时 Agent 失败后没有提交更晚的 `turn/end`,则视为直接驱动器失败,包括汇总轮次的开始或结束无法持久化;更早的已完成轮次不能使该次运行成功。直接驱动器失败(例如 Agent 创建失败或不可用的 `--session-id`)向 stderr 写入 `dsh: ` 并退出 1,且在 `--json` 模式下额外发出一个 `error` 事件,不发出 `final` 事件。 ### 源码地图 @@ -95,15 +95,10 @@ patch 叠加在 `dsh-base` 之上:继承投影缓存与共享 PTC 运行时, | [`src/startup.ts`](src/startup.ts) | `headless-startup` 提供方:任务位置参数、`--session-id`、`--json` 与 `--help` | | [`src/json-stream.ts`](src/json-stream.ts) | `--json` 投影:事件词汇、提交点发射、字符串限长 | | [`cordis.patch.yml`](cordis.patch.yml) | 叠加在 `dsh-base` 之上的一次性 patch | -| — | 不发布运行时不变式伴生入口;runner 的可观察约定(stderr 中的提供方推理、stdout 中的最终文本、按轮次结束原因决定的退出码)属于进程级,并由启动器 e2e 负责;runner 不注册任何内容,树内也没有任何可变关系可审计。 | | [`tests/headless.spec.ts`](tests/headless.spec.ts) | 运行流程、汇总、flush、Session 沿用与退出映射 | | [`tests/json-stream.spec.ts`](tests/json-stream.spec.ts) | 投影顺序、提交点发射、限长与释放 | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 | -### 不变式归属 - -不发布不变式伴生入口,因为 runner 的可观察约定(stdout 的最终文本、按轮次结束原因决定的退出码)是进程级的、由启动器 e2e 负责;插件不注册任何内容,树内也没有任何可变关系可审计。 - ----- diff --git a/packages/bundle/headless/cordis.patch.yml b/packages/bundle/headless/cordis.patch.yml index bb5eb2aa09..20f13b54e6 100644 --- a/packages/bundle/headless/cordis.patch.yml +++ b/packages/bundle/headless/cordis.patch.yml @@ -8,7 +8,6 @@ - id: system-prompt config: - personaSuffix: Your working directory is {{cwd}}. personaPrefix: >- You are a coding agent powered by the {{model}} model. diff --git a/packages/bundle/headless/package.json b/packages/bundle/headless/package.json index e5261aa45f..cf2dc9f38f 100644 --- a/packages/bundle/headless/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -54,7 +54,8 @@ "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-query": "workspace:*", - "@deepseek-ai/dsh-fs": "workspace:*" + "@deepseek-ai/dsh-fs": "workspace:*", + "@deepseek-ai/dsh-subagent": "workspace:*" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", @@ -66,6 +67,12 @@ "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@deepseek-ai/dsh-session-query": "workspace:*", - "@deepseek-ai/dsh-fs": "workspace:*" + "@deepseek-ai/dsh-fs": "workspace:*", + "@deepseek-ai/dsh-subagent": "workspace:*" + }, + "peerDependenciesMeta": { + "@deepseek-ai/dsh-subagent": { + "optional": true + } } } diff --git a/packages/bundle/headless/src/index.ts b/packages/bundle/headless/src/index.ts index 5e62b222eb..93006eb810 100644 --- a/packages/bundle/headless/src/index.ts +++ b/packages/bundle/headless/src/index.ts @@ -29,6 +29,7 @@ import { SessionQueryError } from '@deepseek-ai/dsh-session-query' import type {} from '@deepseek-ai/cordis-plugin-loader' import type {} from '@deepseek-ai/dsh-cmdline' import type {} from '@deepseek-ai/dsh-session-query' +import type {} from '@deepseek-ai/dsh-subagent' import { internals } from './runner-internals.ts' import { projectJsonRun, boundJsonLine } from './json-stream.ts' @@ -302,6 +303,7 @@ function fail(io: HeadlessIo, error: unknown, json: boolean): void { /** * Run one task through one Agent and request process exit. + * Live Agent failures require a later durable terminal before a recovered run can succeed. * @param ctx - plugin context carrying the Agent, default model, Session, and launcher IO services. * @param config - task, optional exact Session identity, and output mode. * @param io - process-facing effects. @@ -360,17 +362,33 @@ async function run(ctx: Context, config: Config, io: HeadlessIo): Promise const firstSeq = agent.session.seq const projection = config.json === true ? projectJsonRun(ctx, agent, io.stdout, { cwd }) : undefined const stopReasoning = projection === undefined ? streamReasoning(ctx, agent, io.stderr) : undefined + let failure: { error: unknown; atOffset: SessionLogOffset } | undefined + const stopErrors = ctx.on('agent/error', ({ agent: subject, error }) => { + if (subject === agent) failure = { error, atOffset: agent.session.seq } + }) + const stopTerminals = ctx.on('session/event', (session, event) => { + if (session === agent.session && event.type === 'turn/end' + && failure !== undefined && event.seq >= failure.atOffset) failure = undefined + }) try { try { agent.followup(createUserMessage({ content: [{ type: 'text', text: task }], source: { kind: 'user' }, })) - await agent.whenIdle() + const subagents = ctx.get('subagents') + while (true) { + await agent.whenIdle() + const idleSeq = agent.session.seq + // Child settlement can wake the parent before the child wait observes it. + if (!await subagents?.waitForChildren(agent) + && agent.status === 'idle' && agent.session.seq === idleSeq) break + } } finally { stopReasoning?.() } await sessions.flush(agent.session) + if (failure !== undefined) throw failure.error const outcome = summarize(agent.session, firstSeq) if (projection === undefined) io.stdout.write(outcome.text + '\n') else projection.finish(outcome.text) @@ -379,6 +397,8 @@ async function run(ctx: Context, config: Config, io: HeadlessIo): Promise } io.exit(outcome.reason?.kind === 'completed' ? 0 : 1) } finally { + stopErrors() + stopTerminals() projection?.dispose() } } diff --git a/packages/bundle/headless/tests/headless.spec.ts b/packages/bundle/headless/tests/headless.spec.ts index 5c8bd6b0f9..cc73507d5f 100644 --- a/packages/bundle/headless/tests/headless.spec.ts +++ b/packages/bundle/headless/tests/headless.spec.ts @@ -13,13 +13,14 @@ import type { ResumeAgentOptions, } from '@deepseek-ai/dsh-agent' import AgentDefaultModelConfig from '@deepseek-ai/dsh-agent-default-model' -import { LlmAttemptId, ToolCallId, createAssistantMessage, createToolResultMessage, type MessageId, type StreamChunk } from '@deepseek-ai/dsh-llm' +import { LlmAttemptId, ToolCallId, createAssistantMessage, createToolResultMessage, createUserMessage, type MessageId, type StreamChunk } from '@deepseek-ai/dsh-llm' import SessionStore from '@deepseek-ai/dsh-session' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import type { Session, SessionId, UserMessage } from '@deepseek-ai/dsh-session' import { SessionQueryError } from '@deepseek-ai/dsh-session-query' -import { createInboxStub } from '@deepseek-ai/dsh-agent-loop-testkit' +import { createInboxStub, provideWorkingDirectoryFixture } from '@deepseek-ai/dsh-agent-loop-testkit' import { apply, Config } from '../src/index.ts' +import SubagentRuntime from '@deepseek-ai/dsh-subagent' import { internals } from '../src/runner-internals.ts' const originalInternals = { ...internals } @@ -57,6 +58,8 @@ interface BenchOptions { preliveMeta?: { cwd?: string; origin?: 'subagent'; agentPreset?: string } /** Run when the runner awaits idle, e.g. to append to the attached log. */ onWhenIdle?: (agent: Agent) => void + /** Release child cleanup after the observed parent activity ends. */ + afterIdle?: () => void } const frameStates = new WeakMap; revision: number; index: number }>() @@ -125,6 +128,7 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{ run(): Promise<{ code: number; out: string; err: string; order: string[] }> }> { const ctx = new Context() + provideWorkingDirectoryFixture(ctx) if (options.filesystemCwd !== undefined) { const cwd = options.filesystemCwd ctx.provide('fs', { @@ -143,25 +147,29 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{ ): Promise => { const inbox = createInboxStub() let idle = Promise.resolve() + let running = false const agent: Agent = { id: session.id, options: createOptions.agentOptions ?? {}, session, inbox, - status: 'idle', + get status() { return running ? 'running' : 'idle' }, ctx: ownerCtx, cancel: () => {}, runMaintenance: () => Promise.reject(new Error('not used')), send: () => {}, followup: (message: UserMessage) => { agent.inbox.append('next-turn', message) - idle = Promise.resolve().then(() => script.afterPrompt(session, message, agent)) + running = true + idle = Promise.resolve().then(() => script.afterPrompt(session, message, agent)).finally(() => { running = false }) }, steer: () => {}, inject: () => {}, - whenIdle: () => { + whenIdle: async () => { options.onWhenIdle?.(agent) - return idle + let activity: Promise + do { await (activity = idle) } while (activity !== idle) + options.afterIdle?.() }, } await createOptions.setup?.(ownerCtx, agent) @@ -224,6 +232,120 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{ } describe('headless runner', () => { + it('waits for parent awakened between idle and child observation', async () => { + const cleanup = Promise.withResolvers() + const cleaning = Promise.withResolvers() + const finalTurn = Promise.withResolvers() + const parentWait = Promise.withResolvers() + let turns = 0 + const test = await bench({ + async afterPrompt(session, message, parent) { + turns += 1 + if (turns === 1) { + await test.ctx.subagents.startActivation({ + provider: 'external', label: 'work', delivery: 'parent', signal: new AbortController().signal, + request: { parent, prompt: [] }, + }) + await cleaning.promise + appendTurn(session, 1, message, 'waiting for child', true) + } else { + await finalTurn.promise + appendTurn(session, 2, message, 'child result incorporated', true) + } + }, + }, { + afterIdle: () => { if (turns === 1) cleanup.resolve(undefined) }, + onWhenIdle: () => { if (turns === 2) parentWait.resolve(undefined) }, + }) + await test.ctx.plugin(SubagentRuntime) + test.ctx.subagents.registerProvider({ + name: 'external', inheritsParentContext: false, + capabilities: { agentOptions: false, outputSchema: false, depthLimit: false, toolFilter: false, persona: false }, + start: async () => ({ + id: brandString('settlement-child'), + result: Promise.resolve({ output: [{ type: 'text' as const, text: 'child answer' }], stopReason: 'completed' as const }), + dispose: () => { cleaning.resolve(undefined); return cleanup.promise }, + }), + }) + try { + const run = test.run() + expect(await Promise.race([run.then(() => 'exited'), parentWait.promise.then(() => 'waiting')])).toBe('waiting') + expect(test.output().out).toBe('') + finalTurn.resolve(undefined) + await expect(run).resolves.toMatchObject({ code: 0, out: 'child result incorporated\n' }) + } finally { + cleanup.resolve(undefined) + finalTurn.resolve(undefined) + await test.ctx.fiber.dispose() + } + }) + + it('waits for later child batches started after settlement wakes the parent', async () => { + const waiting = [Promise.withResolvers(), Promise.withResolvers()] + const released = [Promise.withResolvers(), Promise.withResolvers()] + let turns = 0 + let checks = 0 + const test = await bench({ + afterPrompt(session, message) { + turns += 1 + appendTurn(session, turns, message, turns === 3 ? 'all children complete' : 'waiting for children', true) + }, + }) + test.ctx.provide('subagents', { + async waitForChildren(parent: Agent) { + expect(test.ctx.agents.get(parent.id)).toBe(parent) + const batch = checks++ + if (batch === 2) return false + waiting[batch]!.resolve(undefined) + await released[batch]!.promise + parent.followup(createUserMessage({ content: [{ type: 'text', text: `child batch ${batch + 1} complete` }], source: { kind: 'user' } })) + return true + }, + } as never) + const run = test.run() + try { + await waiting[0]!.promise + expect(test.output().out).toBe('') + released[0]!.resolve(undefined) + await waiting[1]!.promise + expect(turns).toBe(2) + expect(test.output().order).not.toContain('exit') + released[1]!.resolve(undefined) + await expect(run).resolves.toMatchObject({ code: 0, out: 'all children complete\n' }) + expect(checks).toBe(3) + } finally { + for (const release of released) release.resolve(undefined) + await run + await test.ctx.fiber.dispose() + } + }) + + it('rechecks children after a parent turn completes during the child wait', async () => { + let turns = 0 + let checks = 0 + const test = await bench({ + afterPrompt(session, message) { + turns += 1 + appendTurn(session, turns, message, turns === 3 ? 'all batches complete' : 'waiting for batch', true) + }, + }) + test.ctx.provide('subagents', { + async waitForChildren(parent: Agent) { + const check = checks++ + if (check === 2) return false + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'batch complete' }], source: { kind: 'user' } })) + await parent.whenIdle() + // The first observation missed a released child; its parent started another batch. + return check !== 0 + }, + } as never) + try { + await expect(test.run()).resolves.toMatchObject({ code: 0, out: 'all batches complete\n' }) + } finally { + await test.ctx.fiber.dispose() + } + }) + it('records a fresh Session in the filesystem provider working directory', async () => { const cwd = '/remote/workspace' const test = await bench({ diff --git a/packages/bundle/headless/tests/terminal-failures.spec.ts b/packages/bundle/headless/tests/terminal-failures.spec.ts new file mode 100644 index 0000000000..5c20881ea9 --- /dev/null +++ b/packages/bundle/headless/tests/terminal-failures.spec.ts @@ -0,0 +1,154 @@ +/** Headless exit status after parent summaries fail outside a durable terminal. */ + +import { afterEach, describe, expect, it, vi } from 'vitest' +import { Context } from '@deepseek-ai/cordis' +import type { Agent } from '@deepseek-ai/dsh-agent' +import AgentDefaultModelConfig from '@deepseek-ai/dsh-agent-default-model' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit' +import { createUserMessage } from '@deepseek-ai/dsh-llm' +import { SessionId } from '@deepseek-ai/dsh-session' +import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' +import { apply } from '../src/index.ts' +import { internals } from '../src/runner-internals.ts' + +const originalInternals = { ...internals } +let ctx: Context | undefined + +afterEach(async () => { + await ctx?.fiber.dispose() + ctx = undefined + Object.assign(internals, originalInternals) + vi.restoreAllMocks() +}) + +async function harness() { + const context = new Context() + ctx = context + await mountAgentLoopTestDependencies(context) + await context.plugin(AgentLoop, { agents: [] }) + await context.plugin(AgentDefaultModelConfig, { provider: 'mock', model: 'mock' }) + context.llm.registerAdapter(['mock'], new MockAdapter([ + textResponse('waiting'), textResponse('summary'), textResponse('recovered'), + ])) + let out = '' + let err = '' + internals.stdout = { write: (chunk: string) => { out += chunk; return true } } + internals.stderr = { write: (chunk: string) => { err += chunk; return true } } + const exited = Promise.withResolvers() + context.provide('appExit', exited.resolve) + return { + ctx: context, + async run(json = false) { + apply(context, { task: 'delegate', json }) + return { code: await exited.promise, out, err } + }, + } +} + +function followup(parent: Agent): void { + parent.followup(createUserMessage({ content: [{ type: 'text', text: 'child result' }], source: { kind: 'user' } })) +} + +describe('headless terminal failures', () => { + it.each([ + { failedEvent: 'turn/start', json: false }, + { failedEvent: 'turn/end', json: false }, + { failedEvent: 'turn/start', json: true }, + { failedEvent: 'turn/end', json: true }, + ] as const)('fails after a later $failedEvent cannot commit (json=$json)', async ({ failedEvent, json }) => { + const test = await harness() + const failures: unknown[] = [] + test.ctx.on('agent/error', ({ error }) => { failures.push(error) }) + const waitForChildren = vi.fn().mockImplementationOnce((parent: Agent) => { + const append = parent.session.append.bind(parent.session) + const failAppend: typeof parent.session.append = (type, data, ...intent) => { + if (type === failedEvent) throw new Error(`summary ${failedEvent} unavailable`) + return append(type, data, ...intent) + } + vi.spyOn(parent.session, 'append').mockImplementation(failAppend) + followup(parent) + return Promise.resolve(true) + }).mockResolvedValue(false) + test.ctx.provide('subagents', { waitForChildren } as never) + + const result = await test.run(json) + expect(failures).toHaveLength(1) + expect(result.code).toBe(1) + expect(result.err).toBe(`dsh: summary ${failedEvent} unavailable\n`) + if (json) { + const events = result.out.trim().split('\n').map(line => JSON.parse(line) as { type: string; message?: string }) + expect(events.at(-1)).toEqual({ type: 'error', message: `summary ${failedEvent} unavailable` }) + expect(events.some(event => event.type === 'final')).toBe(false) + } else { + expect(result.out).toBe('') + } + }) + + it.each([false, true])('uses the durable outcome after a summary hook fails (recover=%s)', async (recover) => { + const test = await harness() + const waitForChildren = vi.fn().mockImplementationOnce((parent: Agent) => { + const stop = test.ctx.on('agent/pre-step', () => { + stop() + throw new Error('summary hook failed') + }) + followup(parent) + return Promise.resolve(true) + }).mockImplementationOnce((parent: Agent) => { + if (recover) followup(parent) + return Promise.resolve(recover) + }).mockResolvedValue(false) + test.ctx.provide('subagents', { waitForChildren } as never) + + await expect(test.run()).resolves.toEqual(recover + ? { code: 0, out: 'summary\n', err: '' } + : { code: 1, out: 'waiting\n', err: 'dsh: UNKNOWN: summary hook failed\n' }) + }) + + it.each(['turn/start', 'turn/end'] as const)('accepts a recovered summary after a missing %s', async (failedEvent) => { + const test = await harness() + const waitForChildren = vi.fn().mockImplementationOnce((parent: Agent) => { + const append = parent.session.append.bind(parent.session) + let failed = false + const failAppend: typeof parent.session.append = (type, data, ...intent) => { + if (!failed && type === failedEvent) { + failed = true + throw new Error(`summary ${failedEvent} unavailable`) + } + return append(type, data, ...intent) + } + vi.spyOn(parent.session, 'append').mockImplementation(failAppend) + followup(parent) + return Promise.resolve(true) + }).mockImplementationOnce((parent: Agent) => { + followup(parent) + return Promise.resolve(true) + }).mockResolvedValue(false) + test.ctx.provide('subagents', { waitForChildren } as never) + + await expect(test.run()).resolves.toEqual({ + code: 0, + out: 'recovered\n', + err: '', + }) + }) + + it('ignores a different Agent failure during root completion', async () => { + const test = await harness() + const { agent: other } = await test.ctx.agents.create({ + sessionId: SessionId('other'), agentOptions: { provider: 'mock', model: 'mock' }, + }) + test.ctx.on('agent/pre-step', ({ agent }, next) => { + if (agent === other) throw new Error('another Agent failed') + return next() + }) + const waitForChildren = vi.fn().mockImplementationOnce(async () => { + followup(other) + await other.whenIdle() + return false + }).mockResolvedValue(false) + test.ctx.provide('subagents', { waitForChildren } as never) + + await expect(test.run()).resolves.toEqual({ code: 0, out: 'waiting\n', err: '' }) + }) +}) diff --git a/packages/bundle/headless/tsconfig.json b/packages/bundle/headless/tsconfig.json index e1d1128fea..640ee3b2ad 100644 --- a/packages/bundle/headless/tsconfig.json +++ b/packages/bundle/headless/tsconfig.json @@ -37,6 +37,9 @@ }, { "path": "../../fs/fs" + }, + { + "path": "../../subagent/subagent" } ] } diff --git a/packages/bundle/sdk-app/README.i18n.yaml b/packages/bundle/sdk-app/README.i18n.yaml index 14acc275d8..b8b29a0453 100644 --- a/packages/bundle/sdk-app/README.i18n.yaml +++ b/packages/bundle/sdk-app/README.i18n.yaml @@ -24,8 +24,8 @@ en: af149b1e7ed0a5fc zh: fe483822d03deddb /deepseek-ai-dsh-sdk-app/model-experience/sdk-coding-agent-persona/what-the-model-sees: - en: 895f2c7fc9357d23 - zh: d2e6455f4adcf7d3 + en: afe0af085d77833e + zh: f41ef2a2219f1248 /deepseek-ai-dsh-sdk-app/model-experience/sdk-coding-agent-persona/token-effect: en: 371d28831239e0a8 zh: 57595359ff30a259 @@ -36,5 +36,5 @@ en: 416e2793f9786480 zh: 05cc4fd289d0185d /deepseek-ai-dsh-sdk-app/known-limitations-and-deferred-work/dev-note: - en: 8bc6c774f783517d - zh: b1ae7ae9832b2444 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/bundle/sdk-app/README.md b/packages/bundle/sdk-app/README.md index 6d6106e6dd..df3d0aa353 100644 --- a/packages/bundle/sdk-app/README.md +++ b/packages/bundle/sdk-app/README.md @@ -44,7 +44,7 @@ The packaged Python runtime enables the [Office runtime query and skills](../../ #### What the model sees -The profile supplies `You are a coding agent powered by the {{model}} model.` before first-party guidance and `Your working directory is {{cwd}}.` in a separate persona suffix. The exact SDK initialization route and session cwd resolve the placeholders. Default file tool schemas include `read`, `write`, and `edit`; they omit `str_replace_editor`. +The profile supplies `You are a coding agent powered by the {{model}} model.` before first-party guidance; the SDK initialization route resolves `{{model}}`. Each Session receives its current directory in required user-role context from [`dsh-working-directory`](../../session/working-directory/README.md). Default file tool schemas include `read`, `write`, and `edit`; they omit `str_replace_editor`. #### Token effect @@ -72,5 +72,3 @@ Stable for a fixed profile, provider, model, and tool roster. Profile changes ta None. - -**Runtime invariant:** No companion is published. The bundle adds a process transport and startup latch; source/built stdio tests own frame purity, help exclusion, and shutdown. diff --git a/packages/bundle/sdk-app/README.zh.md b/packages/bundle/sdk-app/README.zh.md index 9de1fb6e17..026d442b1a 100644 --- a/packages/bundle/sdk-app/README.zh.md +++ b/packages/bundle/sdk-app/README.zh.md @@ -44,7 +44,7 @@ SDK 使用 base 默认提供的 `read`、`write` 和 `edit`。要添加 `str_rep #### 模型看到什么 -profile 在第一方指导之前提供 `You are a coding agent powered by the {{model}} model.`,并在独立的 persona 后缀中提供 `Your working directory is {{cwd}}.`。确切的 SDK 初始化路由与会话 cwd 会解析其中的占位符。默认文件工具 schema 包含 `read`、`write` 和 `edit`,不包含 `str_replace_editor`。 +profile 在第一方指导之前提供 `You are a coding agent powered by the {{model}} model.`;SDK 初始化路由解析 `{{model}}`。每个 Session 通过 [`dsh-working-directory`](../../session/working-directory/README.zh.md) 提供的必需 user-role 上下文接收当前目录。默认文件工具 schema 包含 `read`、`write` 和 `edit`,不包含 `str_replace_editor`。 #### Token 影响 @@ -72,5 +72,3 @@ profile 在第一方指导之前提供 `You are a coding agent powered by the {{ 无。 - -**运行时不变式:** 不发布伴生入口。该 bundle 增加进程传输与启动 latch;帧纯度、help 排除和关闭行为由源码及构建产物的 stdio 测试负责。 diff --git a/packages/bundle/sdk-app/cordis.patch.yml b/packages/bundle/sdk-app/cordis.patch.yml index 85aeaca996..d852a0a41d 100644 --- a/packages/bundle/sdk-app/cordis.patch.yml +++ b/packages/bundle/sdk-app/cordis.patch.yml @@ -2,7 +2,6 @@ - id: system-prompt config: - personaSuffix: Your working directory is {{cwd}}. personaPrefix: >- You are a coding agent powered by the {{model}} model. diff --git a/packages/bundle/sdk-app/package.json b/packages/bundle/sdk-app/package.json index 95308deec7..fdfc1d2ad3 100644 --- a/packages/bundle/sdk-app/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/bundle/sdk-minimal/README.i18n.yaml b/packages/bundle/sdk-minimal/README.i18n.yaml index 0aa6dc1565..1a22a57b32 100644 --- a/packages/bundle/sdk-minimal/README.i18n.yaml +++ b/packages/bundle/sdk-minimal/README.i18n.yaml @@ -3,26 +3,26 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bundle/sdk-minimal/README.md /: - en: ae5bdac5add26503 - zh: 93cd37d41c05fc9c + en: 7cbc0e332890851f + zh: 07c71026d8e1bb15 /deepseek-ai-dsh-sdk-minimal: en: 807c43eedba7d53e zh: 9f1efe5d6ac393f7 /deepseek-ai-dsh-sdk-minimal/summary: - en: 1885da605fd804b8 - zh: 742cbfdd2a4a224b + en: c0a98b4bcbb3e719 + zh: 92895dd5ce381cd1 /deepseek-ai-dsh-sdk-minimal/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-sdk-minimal/use-this-package: - en: 107f4613e077f9ad - zh: e7d89433a98b296b + en: f920307c0cf12118 + zh: edf9051846fecd5c /deepseek-ai-dsh-sdk-minimal/understand-the-implementation: - en: 281b8d6f1aa0e526 - zh: 070c2a66b1b2feee + en: 91f4a78ab7a688d3 + zh: 7bb8bf33c0d8b429 /deepseek-ai-dsh-sdk-minimal/understand-the-implementation/source-map: - en: d992e946f4928fa3 - zh: 46d6b3d1aabdc3f4 + en: 3784c554e96ab004 + zh: 0a05d4fe1e26b3b1 /deepseek-ai-dsh-sdk-minimal/further-exploration: en: 6909fc128c321153 zh: 7d2a0866efdc5a69 @@ -33,11 +33,11 @@ en: bd87e16b0c6e0506 zh: c8ee5e3b4578754e /deepseek-ai-dsh-sdk-minimal/model-experience/minimal-coding-agent-composition/what-the-model-sees: - en: fa9af6239831a097 - zh: 6951e08b165cb282 + en: 724e1f4075df9293 + zh: 35955569ded10b56 /deepseek-ai-dsh-sdk-minimal/model-experience/minimal-coding-agent-composition/token-effect: - en: 65613fb41fcc3151 - zh: fc20a732aca8282c + en: 5df298b2c9f95d27 + zh: def93cd59b5ca843 /deepseek-ai-dsh-sdk-minimal/model-experience/minimal-coding-agent-composition/kv-cache-effect: en: 904ae09b5fdb87c1 zh: de6fcaaa12bd9733 diff --git a/packages/bundle/sdk-minimal/README.md b/packages/bundle/sdk-minimal/README.md index 2632bf9bd9..7a4a0ff127 100644 --- a/packages/bundle/sdk-minimal/README.md +++ b/packages/bundle/sdk-minimal/README.md @@ -1,5 +1,5 @@ --- -description: "Standalone SDK profile with one default shell tool for users who need a minimal cross-platform coding agent without the shared base bundle." +description: "Standalone SDK profile with a persistent shell and Session working-directory tool for a minimal cross-platform coding agent without the shared base bundle." kind: "package-bundle" --- @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Use `dsh --profile sdk-minimal` when an SDK client needs a small, explicit coding-agent runtime. By default, the profile advertises only a platform-selected persistent shell, persists sessions as uncompressed JSONL, and selects the model from the SDK initialization request. It supplies a complete Cordis tree and deliberately excludes `dsh-base`, Web, settings, managed credentials, telemetry, compaction, filesystem tools, workspace instructions, skills, jobs, and subagents. Its danger-full-access policy lets the shell modify any path available to the process, so use it only with an isolated workspace. +Use `dsh --profile sdk-minimal` when an SDK client needs a small, explicit coding-agent runtime. By default, the profile advertises a platform-selected persistent shell and `working_directory`, persists sessions as uncompressed JSONL, and selects the model from the SDK initialization request. It supplies a complete Cordis tree and excludes `dsh-base`, Web, settings, managed credentials, telemetry, compaction, filesystem tools, workspace instructions, skills, jobs, and subagents. Its danger-full-access policy lets the shell modify any path available to the process, so use it only with an isolated workspace. ## Table of Contents @@ -38,7 +38,7 @@ Use `dsh plugin --profile sdk-minimal` to manage persistent external dependencie The profile mounts exactly one persistent shell stack: Bash on Linux and macOS, or PowerShell on Windows. Both stacks use a 300-second timeout and one owner-scoped terminal; the other platform's rows remain disabled. -Like every shipped profile, it mounts [MCP resources](../../mcp/mcp-resources/README.md) once. Configure only [MCP client entries](../../mcp/mcp-client/README.md) to add servers. Clients mounted by another provider also count as configured. With no server configured in the caller's scope, MCP contributes no prompt text or tools and the default remains one shell tool. +Like every shipped profile, it mounts [MCP resources](../../mcp/mcp-resources/README.md) once. Configure only [MCP client entries](../../mcp/mcp-client/README.md) to add servers. Clients mounted by another provider also count as configured. With no server configured in the caller's scope, MCP contributes no prompt text or tools and the default remains the shell and `working_directory` tools. ----- @@ -48,7 +48,7 @@ Like every shipped profile, it mounts [MCP resources](../../mcp/mcp-resources/RE
Implementation internals — click to expand -The bundle's single insert is the complete application tree: SDK stdio startup and JSON-RPC serving, one environment-configured DeepSeek adapter, the explicit agent core, conditional MCP resource tools, local subprocess execution, a platform-selected persistent shell PTY, and uncompressed JSONL persistence under `$DSH_HOME/sessions`. It does not inherit another bundle, so every extra row is an explicit profile change. +The bundle's single insert is the complete application tree: SDK stdio startup and JSON-RPC serving, one environment-configured DeepSeek adapter, the explicit agent core, conditional MCP resource tools, local filesystem and subprocess services, Session working-directory state and its tool, a platform-selected persistent shell PTY, and uncompressed JSONL persistence under `$DSH_HOME/sessions`. It does not inherit another bundle, so every extra row is an explicit profile change. ### Source map @@ -56,7 +56,6 @@ The bundle's single insert is the complete application tree: SDK stdio startup a |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | Complete standalone profile tree and its environment-backed defaults | | [`src/index.ts`](src/index.ts) | Bundle package entry | -| — | No runtime invariant companion is published; the package is a static patch-list carrier whose inserted rows own their runtime relationships and invariant companions. | | [`tests/sdk-minimal.spec.ts`](tests/sdk-minimal.spec.ts) | Exact composition, profile-name, and platform-selection checks |
@@ -79,11 +78,11 @@ The bundle's single insert is the complete application tree: SDK stdio startup a #### What the model sees -The system prompt is `DSH_SYSTEM_PROMPT` or `You are a helpful software engineer assistant.`. With no MCP server configured, the only advertised tool is owner-scoped persistent `bash` on Linux/macOS or `pwsh` on Windows; runtime context, filesystem tools, workspace instructions, skills, jobs controls, compaction, and Harness identity are absent. +The system prompt is `DSH_SYSTEM_PROMPT` or `You are a helpful software engineer assistant.`. With no MCP server configured, the advertised tools are `working_directory` and owner-scoped persistent `bash` on Linux/macOS or `pwsh` on Windows. Required user context supplies the current directory. Optional runtime context, filesystem tools, workspace instructions, skills, jobs controls, compaction, and Harness identity are absent. #### Token effect -By default, one stable persona plus one tool schema. Configured MCP servers add their tools, shared resource tools, and available server instructions. Tool results and ordinary conversation history grow with the session. +By default, one stable persona, two tool schemas, and user context for the initial directory and subsequent changes. Configured MCP servers add their tools, shared resource tools, and available server instructions. Tool results and ordinary conversation history grow with the session. #### KV Cache effect diff --git a/packages/bundle/sdk-minimal/README.zh.md b/packages/bundle/sdk-minimal/README.zh.md index 7aa26c9e89..96de535e19 100644 --- a/packages/bundle/sdk-minimal/README.zh.md +++ b/packages/bundle/sdk-minimal/README.zh.md @@ -1,5 +1,5 @@ --- -description: "供需要不含共享 base bundle 的极简跨平台 coding agent(编程智能体)的用户使用的独立 SDK profile,默认提供一个 shell 工具。" +description: "独立 SDK profile,提供持久 shell 和 Session 工作目录工具,用于不含共享 base bundle 的极简跨平台 coding agent(编程智能体)。" kind: "package-bundle" --- @@ -9,7 +9,7 @@ kind: "package-bundle" ## 概述 -当 SDK 客户端需要小型、显式的 coding agent 运行时时,请使用 `dsh --profile sdk-minimal`。该 profile 默认只公布按平台选择的持久 shell,把会话持久化为未压缩 JSONL,并从 SDK 初始化请求选择模型。它提供完整 Cordis 配置树,并刻意排除 `dsh-base`、Web、settings、托管凭据、遥测、压缩(compaction)、文件系统工具、workspace 指令、skill(技能)、jobs 与 subagent。其 danger-full-access 策略允许 shell 修改进程可访问的任何路径,因此只能配合隔离 workspace 使用。 +当 SDK 客户端需要小型、显式的 coding agent 运行时时,请使用 `dsh --profile sdk-minimal`。该 profile 默认公布按平台选择的持久 shell 和 `working_directory`,把会话持久化为未压缩 JSONL,并从 SDK 初始化请求选择模型。它提供完整 Cordis 配置树,并排除 `dsh-base`、Web、settings、托管凭据、遥测、压缩(compaction)、文件系统工具、workspace 指令、skill(技能)、jobs 与 subagent。其 danger-full-access 策略允许 shell 修改进程可访问的任何路径,因此只能配合隔离 workspace 使用。 ## 目录 @@ -38,7 +38,7 @@ dsh --profile sdk-minimal 该 profile 只挂载一套持久 shell:Linux 和 macOS 使用 Bash,Windows 使用 PowerShell。两套配置都使用 300 秒超时与一个 agent 自有终端;另一平台的配置项保持禁用。 -与其他随附 profile 一样,它统一挂载 [MCP 资源](../../mcp/mcp-resources/README.zh.md)一次。只需配置 [MCP 客户端条目](../../mcp/mcp-client/README.zh.md)即可添加服务器。其他提供方挂载的客户端也属于已配置状态。调用方作用域中未配置服务器时,MCP 不贡献提示词文本或工具,默认仍只有一个 shell 工具。 +与其他随附 profile 一样,它统一挂载 [MCP 资源](../../mcp/mcp-resources/README.zh.md)一次。只需配置 [MCP 客户端条目](../../mcp/mcp-client/README.zh.md)即可添加服务器。其他提供方挂载的客户端也属于已配置状态。调用方作用域中未配置服务器时,MCP 不贡献提示词文本或工具,默认仍为 shell 与 `working_directory` 两个工具。 ----- @@ -48,7 +48,7 @@ dsh --profile sdk-minimal
实现细节——点击展开 -该 bundle 的单个 insert 就是完整应用配置树:SDK stdio 启动与 JSON-RPC 服务、一个由环境配置的 DeepSeek 适配器、显式 agent 核心、按配置启用的 MCP 资源工具、本地子进程执行、按平台选择的持久 shell PTY,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 持久化。它不继承其他 bundle,因此每个额外配置项都是显式 profile 变更。 +该 bundle 的单个 insert 就是完整应用配置树:SDK stdio 启动与 JSON-RPC 服务、一个由环境配置的 DeepSeek 适配器、显式 agent 核心、按配置启用的 MCP 资源工具、本地文件系统与子进程服务、Session 工作目录状态及其工具、按平台选择的持久 shell PTY,以及位于 `$DSH_HOME/sessions` 的未压缩 JSONL 持久化。它不继承其他 bundle,因此每个额外配置项都是显式 profile 变更。 ### 源码地图 @@ -56,7 +56,6 @@ dsh --profile sdk-minimal |---|---| | [`cordis.patch.yml`](cordis.patch.yml) | 完整独立 profile 配置树及其环境默认值 | | [`src/index.ts`](src/index.ts) | Bundle 包入口 | -| — | 不发布运行时不变式伴生项;本包只是静态 patch 列表载体,插入的各行分别拥有自己的运行时关系和不变式伴生项。 | | [`tests/sdk-minimal.spec.ts`](tests/sdk-minimal.spec.ts) | 精确组合、profile 名称与平台选择检查 |
@@ -79,11 +78,11 @@ dsh --profile sdk-minimal #### 模型看到的内容 -系统提示词取 `DSH_SYSTEM_PROMPT`,未设置时使用 `You are a helpful software engineer assistant.`。未配置 MCP 服务器时,对外公布的唯一工具是 Linux/macOS 上 agent 所有的持久 `bash` 或 Windows 上的 `pwsh`;运行时上下文、文件系统工具、workspace 指令、skill、jobs 控制、压缩与 Harness 身份均不存在。 +系统提示词取 `DSH_SYSTEM_PROMPT`,未设置时使用 `You are a helpful software engineer assistant.`。未配置 MCP 服务器时,对外公布的工具为 `working_directory`,以及 Linux/macOS 上 agent 所有的持久 `bash` 或 Windows 上的 `pwsh`。必需的用户上下文提供当前目录。可选运行时上下文、文件系统工具、workspace 指令、skill、jobs 控制、压缩与 Harness 身份均不存在。 #### Token 影响 -默认是一个稳定 persona 加一个工具 schema。已配置 MCP 服务器会添加自己的工具、共享资源工具及可用的服务器指令。工具结果与普通对话历史随会话增长。 +默认是一个稳定 persona、两个工具 schema,以及初始目录与后续变更的用户上下文。已配置 MCP 服务器会添加自己的工具、共享资源工具及可用的服务器指令。工具结果与普通对话历史随会话增长。 #### KV Cache 影响 diff --git a/packages/bundle/sdk-minimal/cordis.patch.yml b/packages/bundle/sdk-minimal/cordis.patch.yml index ac6c7c4bca..6c8dbce78a 100644 --- a/packages/bundle/sdk-minimal/cordis.patch.yml +++ b/packages/bundle/sdk-minimal/cordis.patch.yml @@ -35,6 +35,15 @@ # Shared projection registry: sandbox-policy and terminal-bash fold # sandbox-mode state through its units and require it as a hard injection. + - id: filesystem + name: '@deepseek-ai/dsh-fs-local' + + - id: working-directory + name: '@deepseek-ai/dsh-working-directory' + + - id: tool-working-directory + name: '@deepseek-ai/dsh-tool-working-directory' + - id: session-projection name: '@deepseek-ai/dsh-session-projection' @@ -103,21 +112,6 @@ - id: jobs name: '@deepseek-ai/dsh-jobs-local' - - id: invariants - name: '@deepseek-ai/dsh-invariants' - - - id: session-invariant - name: '@deepseek-ai/dsh-session/invariant' - - - id: agent-invariant - name: '@deepseek-ai/dsh-agent/invariant' - - - id: scope-invariant - name: '@deepseek-ai/dsh-scope/invariant' - - - id: agent-loop-invariant - name: '@deepseek-ai/dsh-agent-loop/invariant' - - id: agent-loop name: '@deepseek-ai/dsh-agent-loop' config: diff --git a/packages/bundle/sdk-minimal/package.json b/packages/bundle/sdk-minimal/package.json index 27acba13d2..df59eaae4d 100644 --- a/packages/bundle/sdk-minimal/package.json +++ b/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, and JSONL sessions", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -38,7 +38,6 @@ "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-loop": "workspace:*", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", @@ -47,7 +46,6 @@ "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:*", "@deepseek-ai/dsh-sandbox-local": "workspace:*", "@deepseek-ai/dsh-sandbox-policy": "workspace:*", - "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-sdk-app": "workspace:*", "@deepseek-ai/dsh-sdk-jsonrpc-server": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", @@ -61,7 +59,10 @@ "@deepseek-ai/dsh-terminal-bash": "workspace:*", "@deepseek-ai/dsh-tool-bash-persistent": "workspace:*", "@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:*", - "@deepseek-ai/dsh-tools": "workspace:*" + "@deepseek-ai/dsh-tools": "workspace:*", + "@deepseek-ai/dsh-working-directory": "workspace:*", + "@deepseek-ai/dsh-tool-working-directory": "workspace:*", + "@deepseek-ai/dsh-fs-local": "workspace:*" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts index f23bc62084..4194bdf4b0 100644 --- a/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts +++ b/packages/bundle/sdk-minimal/tests/sdk-minimal.spec.ts @@ -33,6 +33,9 @@ describe('dsh-sdk-minimal bundle', () => { ['plugin-package-inventory-deepseek', '@deepseek-ai/dsh-plugin-package-inventory-deepseek'], ['llm-deepseek', '@deepseek-ai/dsh-llm-deepseek-api-key'], ['sandbox', '@deepseek-ai/dsh-sandbox-local'], + ['filesystem', '@deepseek-ai/dsh-fs-local'], + ['working-directory', '@deepseek-ai/dsh-working-directory'], + ['tool-working-directory', '@deepseek-ai/dsh-tool-working-directory'], ['session-projection', '@deepseek-ai/dsh-session-projection'], ['sandbox-policy', '@deepseek-ai/dsh-sandbox-policy'], ['subprocess', '@deepseek-ai/dsh-subprocess-local'], @@ -49,11 +52,6 @@ describe('dsh-sdk-minimal bundle', () => { ['agent', '@deepseek-ai/dsh-agent'], ['llm-retry', '@deepseek-ai/dsh-llm-retry'], ['jobs', '@deepseek-ai/dsh-jobs-local'], - ['invariants', '@deepseek-ai/dsh-invariants'], - ['session-invariant', '@deepseek-ai/dsh-session/invariant'], - ['agent-invariant', '@deepseek-ai/dsh-agent/invariant'], - ['scope-invariant', '@deepseek-ai/dsh-scope/invariant'], - ['agent-loop-invariant', '@deepseek-ai/dsh-agent-loop/invariant'], ['agent-loop', '@deepseek-ai/dsh-agent-loop'], ['persistent-bash', '@deepseek-ai/dsh-tool-bash-persistent'], ['persistent-pwsh', '@deepseek-ai/dsh-tool-pwsh-persistent'], diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index 4b208042ed..0c5a6c4a2e 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/README.i18n.yaml @@ -9,8 +9,8 @@ en: a85684740ecd278a zh: 7c54099b9edccf01 /deepseek-ai-dsh-web-app/summary: - en: 0ebb6c3ca8c0a84e - zh: 4d85e02c769ddae0 + en: fd97ee0c14fb5378 + zh: 0e1ccba47bc408d9 /deepseek-ai-dsh-web-app/table-of-contents: en: d152484eb41ac6b4 zh: 09388d293f9be9cb @@ -21,23 +21,26 @@ en: b7ab01cfe9fed40f zh: 23703ab7727c3ab5 /deepseek-ai-dsh-web-app/use-this-package/configuration: - en: 04ae40fa453d2cb5 - zh: 51df66e4100f5660 -/deepseek-ai-dsh-web-app/use-this-package/lan-access-and-trusted-hosts: - en: 2522c806db384ddc - zh: 7e96f11194af9f9f + en: a9ee366c5af68a6c + zh: 2711d7a3926e1101 +/deepseek-ai-dsh-web-app/use-this-package/listening-trust-and-public-deployments: + en: a79add86cb73d966 + zh: 78b58259f98ed063 /deepseek-ai-dsh-web-app/use-this-package/running-over-ssh: - en: 1fe96dde34f21b23 - zh: 5689c4f4186805c1 + en: 45db677dcc91c5c9 + zh: 75b765f7f1e15035 /deepseek-ai-dsh-web-app/use-this-package/per-session-agent-setup: en: ccd6fc264b51e44b zh: 3265c68477a89ec9 /deepseek-ai-dsh-web-app/understand-the-implementation: - en: 4eb00d95238e3b92 - zh: 0d6ca9e1a3e7db84 + en: fb819d10930089a8 + zh: f2dee0ff170a4d41 /deepseek-ai-dsh-web-app/understand-the-implementation/patch-semantics: - en: f0078127aa8dd273 - zh: c954c137599b486f + en: 23ddb4d33e79c0a1 + zh: dc9a458b9492b49b +/deepseek-ai-dsh-web-app/understand-the-implementation/advertised-application-url: + en: ca977d2b2bcf72c5 + zh: 2b81b377c133c1a8 /deepseek-ai-dsh-web-app/understand-the-implementation/readiness: en: 973128fd346869df zh: 54e6792f9f82a192 @@ -45,11 +48,8 @@ en: b5ed8ff0056ca9da zh: fdc1fa4f14cb00b3 /deepseek-ai-dsh-web-app/understand-the-implementation/source-map: - en: 0d08e95f0a5543cb - zh: 5a3e8e4102678cb7 -/deepseek-ai-dsh-web-app/understand-the-implementation/invariant-ownership: - en: 34767bb4c0471622 - zh: e6d76e7c7a0b9ce8 + en: e41444bed021a121 + zh: 44088a90df50d5b4 /deepseek-ai-dsh-web-app/further-exploration: en: f81694f7424e2988 zh: 6643f9227039e228 @@ -60,17 +60,17 @@ en: e6febc53efe0f51d zh: 9177816a8678d65d /deepseek-ai-dsh-web-app/model-experience/harness-source-and-web-surface-context/what-the-model-sees: - en: 359b949dd483477f - zh: 1476006614a82282 + en: 583592075e04687d + zh: 9defddfd77bab0f7 /deepseek-ai-dsh-web-app/model-experience/harness-source-and-web-surface-context/token-effect: en: 908fc84d6849a830 zh: 56fe5e418bab60ba /deepseek-ai-dsh-web-app/model-experience/harness-source-and-web-surface-context/kv-cache-effect: - en: da0f568d6d661c5e - zh: 606919b7d37c95bf + en: 1d7d87f54b20b563 + zh: d150a51615d278e8 /deepseek-ai-dsh-web-app/known-limitations-and-deferred-work: - en: fead2f7bcd17e1ab - zh: 80949c203c0a0732 + en: d6de1d07249ef3b0 + zh: 49d01329256e2eb3 /deepseek-ai-dsh-web-app/known-limitations-and-deferred-work/dev-note: en: 3f81b4f3beb74a0d zh: 2e377ca40822d093 diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index b438f16281..fc4584fbad 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -13,7 +13,7 @@ Desktop analytics schedules partial batches every 30 seconds, with a 15-second e ## Summary -Run `dsh --profile web` to open an interactive browser GUI with chat, model and settings management, and session history. It uses the same model access, tools, and safety defaults as other dsh surfaces. Startup prints an authenticated URL and normally opens it in the default browser; SSH sessions and `--no-open` leave the URL for manual opening. You can change the port and allow extra hosts, but cannot bind all network interfaces. Choose this package for interactive browser work; use `dsh-headless` for one-shot command-line tasks. +Run `dsh --profile web` for browser chat, model and settings management, and session history, using the same model access, tools, and safety defaults as other dsh surfaces. Startup prints a tokenized URL and normally opens the default browser; SSH sessions and `--no-open` require manual opening. You can change the port and allow extra hosts, but cannot bind all network interfaces. Remote access supports an advertised public HTTP(S) URL behind a prefix-stripping proxy. For one-shot command-line tasks, use `dsh-headless`. ## Table of Contents @@ -46,24 +46,30 @@ Saved model selections override the composition default. The settings card accep ### Configuration -Most users never set these; the command-line flags feed the four settings below — `--host`, `--port`, and `--trusted-host` come from the invocation, and `--no-open` turns the browser handoff off for that invocation: +`--host` and `--port` configure the listener; `--public-url` names the advertised public HTTP(S) root the GUI is reached at behind a prefix-stripping proxy, and `--trusted-host` adds further accepted authorities. All are described under [Listening, trust, and public deployments](#public-deployments): | Field | Default | Meaning | |---|---|---| | `openBrowser` | `true` | Open the default browser after startup; SSH launches suppress it | | `printUrl` | `true` | Print the `dsh web:` URL line at startup | | `surfaceContext` | `true` | Give the agent GUI-orientation context and expose `DSH_WEB_URL` to its shell commands | +| `publicUrl` | Unset | Advertised HTTP(S) application root; otherwise announce the listener's loopback URL | | `trustedHosts` | `[]` | Extra hosts allowed to reach the GUI from the network | -The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-app) is the exhaustive source for every accepted field and its JSDoc. The shipped composition inserts the `schedule` service row and the `ui-schedule` task page row, while the clock reading and the four reminder tools belong to the `standard`, `cordis`, and `ptc` presets. The `tool-subagent` and `tool-subagent-fork` rows in those presets deny the four tools, so a delegated child's scope never lists them. +The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-web-app) lists this runtime plugin's accepted fields and their JSDoc. The shipped composition inserts the `schedule` service row and the `ui-schedule` task page row, while the clock reading and the four reminder tools belong to the `standard`, `cordis`, and `ptc` presets. The `tool-subagent` and `tool-subagent-fork` rows in those presets deny the four tools, so a delegated child's scope never lists them. -### LAN access and trusted hosts + +### Listening, trust, and public deployments -By default the GUI accepts connections from this machine only. A deployment that binds all network interfaces also allows browsers from the LAN, and the printed URL then includes a LAN address; `--trusted-host` adds extra hosts in either case. Host and Origin checks control reachability, while the token exchange authenticates every Host API method and WebSocket stream. The LAN addresses are sampled once at startup, so a network change later is not picked up — restart the GUI to re-advertise. +By default the GUI listens on loopback and accepts connections from this machine only; repeatable `--trusted-host` adds the authorities its Host/Origin fence accepts, so a remote browser reaches it behind a prefix-stripping proxy or through a port-forwarding client that presents a trusted hostname. + +`--public-url` advertises the HTTP(S) root browsers use — the printed and opened startup URL, `DSH_WEB_URL`, and the web-surface orientation. Advertisement grants no trust: the browser-visible authority must also be named with `--trusted-host`. The flag configures no listener, routing, or cookie scope, because the proxy owns the external leg: [Publish the Web UI behind a reverse proxy](../../../docs/user/guide/public-deployments.md) lists what such a deployment must provide. + +The printed URL contains a process credential; share it only with intended users, and `printUrl: false` suppresses the line with or without `--public-url`. ### Running over SSH -When you launch `dsh --profile web` over SSH, the URL line still prints but the browser is not opened for you: the SSH client or editor owns the local forwarding address. Open the forwarded URL on your machine yourself; the printed URL names the remote host's loopback endpoint. +When you launch `dsh --profile web` over SSH, the URL line still prints but the browser is not opened for you: the SSH client or editor owns the local forwarding address. Without an advertised root the printed URL names the remote host's loopback endpoint, which you reach through your forwarding address. With `--public-url` the printed URL is the authenticated advertised root; the browser handoff stays suppressed, because opening a browser on the remote host cannot reach your screen. ### Per-session agent setup @@ -77,11 +83,15 @@ Each browser session selects a shipped preset (`standard` by default). The Agent
Implementation internals — click to expand -The bundle is one patch layer of five files plus one runtime glue plugin: `cordis.patch.yml` carries the host rows and the preset registry, and each `presets/.patch.yml` inserts one shipped preset declaration, applied in the order `dsh.bundle.patch` lists them. The storage stack and projection cache come from `dsh-base`; the web overlay's workspace and message-feedback rows consume that shared `storageDomain` service. The patch restates the surface-specific values the base deliberately omits, inserts the web-only host rows and browser roster, then moves the agent plane behind presets. The glue plugin owns dist serving, trust sampling, prompt sections, the bash variable, and the readiness announcements. The `office-to-pdf` row mounts one lazy [Office conversion provider](../../document/office-to-pdf/README.md) for Host consumers, including Desktop compositions using this bundle. The conversion service's Remote methods authorize preview reads, while Document Preview owns the Office viewer and Client cache. +The bundle is one patch layer of five files plus one runtime glue plugin: `cordis.patch.yml` carries the host rows and the preset registry, and each `presets/.patch.yml` inserts one shipped preset declaration, applied in the order `dsh.bundle.patch` lists them. The storage stack and projection cache come from `dsh-base`; the web overlay's workspace and message-feedback rows consume that shared `storageDomain` service. The patch restates the surface-specific values the base deliberately omits, inserts the web-only host rows and browser roster, then moves the agent plane behind presets. The glue plugin owns dist serving, the advertised application URL, trust sampling, prompt sections, the bash variable, and the readiness announcements. The `office-to-pdf` row mounts one lazy [Office conversion provider](../../document/office-to-pdf/README.md) for Host consumers, including Desktop compositions using this bundle. The conversion service's Remote methods authorize preview reads, while Document Preview owns the Office viewer and Client cache. ### Patch semantics -A patch replaces the targeted row's whole `config`, so each web row restates every key it owns: the persona prefix and suffix templates, the `DSH_TOOLS_MODE` PTC mode opt-in, and the `session-query-sqlite` values on the base rows, then `insert` adds the web host rows, transport, and browser roster. The per-agent tool rows the base mounts process-wide are disabled here and the preset roster takes over; the reasoning for each host-plane versus preset-plane decision is inline in the patch. +A patch replaces the targeted row's whole `config`, so each web row restates every key it owns: the persona prefix template, the `DSH_TOOLS_MODE` PTC mode opt-in, and the `session-query-sqlite` values on the base rows, then `insert` adds the web host rows, transport, and browser roster. The `webserver` and `web-runtime` rows inject the `webStartup` provider and read their invocation values directly; the `connection` row instead reads the bind-dependent `webRuntime` values the web-runtime row publishes, which are that provider's authorities plus the LAN literals of an all-interfaces bind. The per-agent tool rows the base mounts process-wide are disabled here and the preset roster takes over; the reasoning for each host-plane versus preset-plane decision is inline in the patch. + +### Advertised application URL + +Startup display and browser handoff receive the advertised root with its launch token; the web-surface prompt and `DSH_WEB_URL` receive it clean. The root itself is defined under [Listening, trust, and public deployments](#public-deployments). ### Readiness @@ -95,20 +105,17 @@ The URL line and browser handoff are readiness signals: supervisors RPC as soon | File | Role | |---|---| -| [`src/index.ts`](src/index.ts) | The `web-app` glue plugin: dist resolution, LAN trust sampling, prompt sections, bash variable, URL line, browser handoff | -| [`src/startup.ts`](src/startup.ts) | The `web-startup` provider: `--host`, `--port`, `--trusted-host`, `--no-open`, `--help` | +| [`src/index.ts`](src/index.ts) | The `web-app` glue plugin: dist resolution, advertised application URL, LAN trust sampling, prompt sections, bash variable, URL line, browser handoff | +| [`src/public-url.ts`](src/public-url.ts) | Advertised-root validation and trailing-slash normalization; a leaf module for local imports, not package API | +| [`src/startup.ts`](src/startup.ts) | The `web-startup` provider: `--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`, `--help` | | [`cordis.patch.yml`](cordis.patch.yml) | The web patch: restated base values, web host rows, browser roster, preset registry | | [`presets/`](presets) | One `@deepseek-ai/dsh-agent-preset` declaration per shipped preset (`standard`, `ptc`, `minimal`, `cordis`), each its own patch file | -| — | No runtime invariant companion is published; every contribution (frontend-static child plugin, prompt section, bashEnv registration) is registry-disposed with the fiber, and each owning registry's package carries that relation's invariant; the package holds no mutable state of its own to audit. | -| [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | Dist resolution, fallback seat, prompt sections, readiness | +| [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | Dist resolution, fallback seat, prompt sections, readiness, advertised URL publication | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | Command-line parsing over a real Loader tree | +| [`tests/public-url.spec.ts`](tests/public-url.spec.ts) | Advertised-root parsing and normalization | | [`tests/trusted-hosts.spec.ts`](tests/trusted-hosts.spec.ts) | LAN-trust sampling | | [`tests/browser-open.spec.ts`](tests/browser-open.spec.ts) | Default-browser handoff after the page is reachable | -### Invariant ownership - -No invariant companion is published because every contribution — the frontend-static child plugin, the prompt sections, and the bash variable registration — is registry-disposed with the fiber, and each owning registry package carries that relation's invariant. -
----- @@ -133,7 +140,7 @@ Read these pages when you want to go deeper into the shared core, the browser re #### What the model sees -When `surfaceContext` is true, the `harness:source` section identifies the on-disk Harness implementation without claiming it is the working directory, and the `app:web-surface` global section (first-party order 10100, after reusable instructions) orients the model to the GUI: the canonical local URL, the "this page" referent, the update contract (the reload receiver is always on; no-refresh reloads additionally need the `pnpm run dev:web` watcher), and the instruction not to start replacement servers. `DSH_WEB_URL` additionally appears in the managed bash environment with its description, resolved per invocation from the live server. When it is false, neither section nor the variable is registered. +When `surfaceContext` is true, the `harness:source` section identifies the on-disk Harness implementation without claiming it is the working directory, and the `app:web-surface` global section (first-party order 10100, after reusable instructions) orients the model to the GUI: the advertised application URL (defined under Listening, trust, and public deployments above), the "this page" referent, the update contract (the reload receiver is always on; no-refresh reloads additionally need the `pnpm run dev:web` watcher), and the instruction not to start replacement servers. `DSH_WEB_URL` additionally appears in the managed bash environment with its description, resolved per invocation from the live server. When it is false, neither section nor the variable is registered. #### Token effect @@ -141,7 +148,7 @@ One source line and one prompt paragraph per session plus two managed-environmen #### KV Cache effect -Source and Web sections follow first-party reusable instructions. Different checkout paths or local ports leave that preceding prefix unchanged when tools and configuration match; provider cache reuse is not guaranteed. +Source and Web sections follow first-party reusable instructions. Different checkout paths or application URLs leave that preceding prefix unchanged when tools and configuration match; provider cache reuse is not guaranteed. ## Known Limitations and Deferred Work @@ -151,9 +158,10 @@ Source and Web sections follow first-party reusable instructions. Different chec These limits tell you what to expect in unusual setups — a source checkout, SSH sessions, or strict networks. They are current package constraints, not a general browser comparison or a task backlog. - **The frontend must be built** — a source checkout needs `pnpm run build` first; startup stops with a build hint when the dist is missing, and there is no source-serving fallback. +- **The listener has no TLS** — protect the external leg with a TLS-terminating proxy; an HTTP advertised root sends credentials without encryption. - **LAN addresses are sampled once at startup** — interface changes after boot are not re-advertised; the printed LAN URL always matches what was sampled. - **Only the handoff start is observable** — the GUI reports that the browser was asked to open, not that it actually opened; a later browser exit is never reported, and the printed URL is your manual fallback. -- **SSH sessions keep the URL but skip the browser handoff** — the printed URL names the remote host's loopback endpoint; the SSH client or editor must expose and open the local forwarded address. +- **SSH sessions keep the URL but skip the browser handoff** — without an advertised root the printed URL names the remote host's loopback endpoint; the SSH client or editor must expose and open the local forwarded address. - **`BROWSER` overrides only come from the environment** — a discovered `.env` cannot set `BROWSER`; only an inherited value can choose the executable for the automatic handoff. - **Binding all network interfaces is not supported** — `--host 0.0.0.0` is rejected at startup for safety; use the default loopback host. diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index edae9772e2..3b7774bfd2 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -13,7 +13,7 @@ kind: "package-bundle" ## 概述 -运行 `dsh --profile web`,打开提供聊天、模型与设置管理以及会话历史的交互式浏览器 GUI。它使用与其他 dsh 表层相同的模型访问、工具与安全默认值。启动时会打印带认证信息的 URL,通常还会在默认浏览器中打开;SSH 会话和 `--no-open` 会保留该 URL,供你手动打开。你可以更改端口并允许额外主机,但不能绑定所有网络接口。需要在浏览器中交互式工作时选择本包;一次性的命令行任务应使用 `dsh-headless`。 +运行 `dsh --profile web`,获得浏览器内的聊天、模型与设置管理以及会话历史,并与其他 dsh 表层共用同一套模型访问、工具与安全默认值。启动时会打印带 token 的 URL,通常还会在默认浏览器中打开;SSH 会话和 `--no-open` 需要手动打开。你可以更改端口并允许额外主机,但不能绑定所有网络接口。跨机访问支持在剥离前缀的代理之后公告一个公开的 HTTP(S) URL。一次性的命令行任务应使用 `dsh-headless`。 ## 目录 @@ -46,24 +46,30 @@ dsh --profile web --no-open --port 8080 ### 配置 -大多数用户不需要设置这些;命令行 flag 会提供给下面四个设置——`--host`、`--port` 与 `--trusted-host` 来自本次调用,`--no-open` 仅对本次调用关闭浏览器交接: +`--host` 与 `--port` 配置监听器;`--public-url` 指定 GUI 在剥离前缀的代理之后对外公告的公开 HTTP(S) 根,`--trusted-host` 则添加更多被接受的 authority。两者都在[监听、信任与公开部署](#public-deployments)中说明: | 字段 | 默认值 | 含义 | |---|---|---| | `openBrowser` | `true` | 启动后用默认浏览器打开;SSH 启动会抑制它 | | `printUrl` | `true` | 启动时打印 `dsh web:` URL 行 | | `surfaceContext` | `true` | 给 agent 提供 GUI 定位上下文,并把 `DSH_WEB_URL` 暴露给其 shell 命令 | +| `publicUrl` | 未设置 | 对外公告的 HTTP(S) 应用根;否则公告监听器的 loopback URL | | `trustedHosts` | `[]` | 允许从网络访问 GUI 的额外主机 | -生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-app)是每个受支持字段及其 JSDoc 的穷尽式真源。 随发行版交付的组合插入 `schedule` 服务行与 `ui-schedule` 任务页面行,时钟读数与四个提醒工具则属于 `standard`、`cordis` 与 `ptc` 三个 preset。这三个 preset 的 `tool-subagent` 与 `tool-subagent-fork` 两行都 deny 这四个工具,因此被委派子 agent 的作用域不会列出它们。 +生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-web-app)列出了本运行时插件接受的字段及其 JSDoc。随发行版交付的组合插入 `schedule` 服务行与 `ui-schedule` 任务页面行,时钟读数与四个提醒工具则属于 `standard`、`cordis` 与 `ptc` 三个 preset。这三个 preset 的 `tool-subagent` 与 `tool-subagent-fork` 两行都 deny 这四个工具,因此被委派子 agent 的作用域不会列出它们。 -### LAN 访问与可信主机 + +### 监听、信任与公开部署 -默认情况下 GUI 只接受本机的连接。绑定所有网络接口的部署也会允许 LAN 内的浏览器访问,此时打印的 URL 会附带一个 LAN 地址;`--trusted-host` 在两种情况下都能添加额外主机。Host 与 Origin 检查控制可达性,token 交换则认证每个 Host API 方法与 WebSocket 流。LAN 地址只在启动时采样一次,因此之后的网络变化不会被感知——重启 GUI 以重新公告。 +默认情况下 GUI 只在 loopback 上监听,只接受本机的连接;可重复的 `--trusted-host` 会添加其 Host/Origin 栅栏接受的 authority,因此远端浏览器要么经由剥离前缀的代理访问,要么通过以可信主机名呈现的端口转发客户端访问。 + +`--public-url` 公告浏览器使用的 HTTP(S) 根——打印与打开的启动 URL、`DSH_WEB_URL` 与 web 表层定位。公告不授予任何信任:浏览器可见的 authority 还必须用 `--trusted-host` 点名。该 flag 不配置监听器、路由或 cookie 作用域,因为外部链路归代理所有:[在反向代理之后发布 Web UI](../../../docs/user/guide/public-deployments.zh.md)列出了这样的部署必须提供什么。 + +打印的 URL 包含进程凭据,只应与预期用户分享;`printUrl: false` 无论是否配置 `--public-url` 都会抑制该行。 ### 通过 SSH 运行 -通过 SSH 启动 `dsh --profile web` 时,URL 行仍会打印,但不会为你打开浏览器:本地转发地址由 SSH 客户端或编辑器持有。请在自己的机器上打开转发后的 URL;打印出的 URL 指向远端宿主机 loopback 端点。 +通过 SSH 启动 `dsh --profile web` 时,URL 行仍会打印,但不会为你打开浏览器:本地转发地址由 SSH 客户端或编辑器持有。没有公告根时,打印的 URL 指向远端宿主机 loopback 端点,你通过自己的转发地址访问它。配置了 `--public-url` 时,打印的 URL 是带认证的公告根;浏览器交接仍被抑制,因为在远端宿主机上打开浏览器无法到达你的屏幕。 ### 按会话的 agent 设置 @@ -77,11 +83,15 @@ dsh --profile web --no-open --port 8080
实现细节——点击展开 -此 bundle 由一层五个文件的补丁和一个运行时胶水插件组成:`cordis.patch.yml` 承载宿主行和 preset 注册表,每个 `presets/.patch.yml` 插入一条随发行版交付的 preset 声明,按 `dsh.bundle.patch` 列出的顺序应用。存储栈与投影缓存来自 `dsh-base`;Web 叠加层的工作区和消息反馈条目消费共享的 `storageDomain` 服务。补丁重述 base 有意省略的界面专用值,插入 Web 专用宿主条目和浏览器插件列表,再将 Agent 层移到预设后面。胶水插件负责 dist 服务、信任采样、提示词段落、bash 变量和就绪通知。`office-to-pdf` 条目为宿主消费者挂载一个延迟创建引擎的 [Office 转换提供方](../../document/office-to-pdf/README.zh.md),使用此 bundle 的 Desktop 组合也共享该提供方。 转换服务的 Remote 方法负责预览读取授权,Document Preview 负责 Office 查看器和客户端缓存。 +此 bundle 由一层五个文件的补丁和一个运行时胶水插件组成:`cordis.patch.yml` 承载宿主行和 preset 注册表,每个 `presets/.patch.yml` 插入一条随发行版交付的 preset 声明,按 `dsh.bundle.patch` 列出的顺序应用。存储栈与投影缓存来自 `dsh-base`;Web 叠加层的工作区和消息反馈条目消费共享的 `storageDomain` 服务。补丁重述 base 有意省略的界面专用值,插入 Web 专用宿主条目和浏览器插件列表,再将 Agent 层移到预设后面。胶水插件负责 dist 服务、公告应用 URL、信任采样、提示词段落、bash 变量和就绪通知。`office-to-pdf` 条目为宿主消费者挂载一个延迟创建引擎的 [Office 转换提供方](../../document/office-to-pdf/README.zh.md),使用此 bundle 的 Desktop 组合也共享该提供方。 转换服务的 Remote 方法负责预览读取授权,Document Preview 负责 Office 查看器和客户端缓存。 ### patch 语义 -patch 会替换目标行的整个 `config`,因此每个 Web 行都重述自己拥有的每个键:基础行上的 persona 前缀与后缀模板、`DSH_TOOLS_MODE` PTC mode 开关与 `session-query-sqlite` 值,随后 `insert` 添加 Web 宿主行、传输层与浏览器名录。base 以进程级挂载的按 agent 工具行在这里被禁用,由 preset 名录接管;每项宿主层与 preset 层归属决策的理由以行内注释写在 patch 里。 +patch 会替换目标行的整个 `config`,因此每个 Web 行都重述自己拥有的每个键:基础行上的 persona 前缀模板、`DSH_TOOLS_MODE` PTC mode 开关与 `session-query-sqlite` 值,随后 `insert` 添加 Web 宿主行、传输层与浏览器名录。`webserver` 与 `web-runtime` 行注入 `webStartup` 提供方并直接读取本次调用的取值;`connection` 行则改为读取 web-runtime 行发布的、与绑定相关的 `webRuntime` 值,即该提供方的 authority 加上全接口绑定的 LAN 字面量。base 以进程级挂载的按 agent 工具行在这里被禁用,由 preset 名录接管;每项宿主层与 preset 层归属决策的理由以行内注释写在 patch 里。 + +### 公告应用 URL + +启动显示与浏览器交接接收附带启动 token 的公告根;web 表层提示词与 `DSH_WEB_URL` 接收不含凭据的形式。根本身由[监听、信任与公开部署](#public-deployments)定义。 ### 就绪宣告 @@ -95,20 +105,17 @@ URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发 | 文件 | 职责 | |---|---| -| [`src/index.ts`](src/index.ts) | `web-app` 粘合插件:dist 解析、LAN 信任采样、提示词段落、bash 变量、URL 行、浏览器交接 | -| [`src/startup.ts`](src/startup.ts) | `web-startup` 提供方:`--host`、`--port`、`--trusted-host`、`--no-open`、`--help` | +| [`src/index.ts`](src/index.ts) | `web-app` 粘合插件:dist 解析、公告应用 URL、LAN 信任采样、提示词段落、bash 变量、URL 行、浏览器交接 | +| [`src/public-url.ts`](src/public-url.ts) | 公告根的校验与尾斜杠归一化;供本地导入的叶子模块,不属于包 API | +| [`src/startup.ts`](src/startup.ts) | `web-startup` 提供方:`--host`、`--port`、`--public-url`、`--trusted-host`、`--no-open`、`--help` | | [`cordis.patch.yml`](cordis.patch.yml) | Web patch:重述的基础值、Web 宿主行、浏览器名录、preset 注册表 | | [`presets/`](presets) | 每个随发行版交付的 preset(`standard`、`ptc`、`minimal`、`cordis`)各一条 `@deepseek-ai/dsh-agent-preset` 声明,各自一个补丁文件 | -| — | 不发布运行时不变式伴生入口;每项贡献(frontend-static 子插件、提示词段落、bashEnv 注册)都会随 fiber 由注册表释放,且每个所属注册表的包负责该关系的不变式;本包不持有需要审计的可变状态。 | -| [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | dist 解析、回退席位、提示词段落、就绪宣告 | +| [`tests/web-app.spec.ts`](tests/web-app.spec.ts) | dist 解析、回退席位、提示词段落、就绪宣告、公告 URL 发布 | | [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 | +| [`tests/public-url.spec.ts`](tests/public-url.spec.ts) | 公告根的解析与归一化 | | [`tests/trusted-hosts.spec.ts`](tests/trusted-hosts.spec.ts) | LAN 信任采样 | | [`tests/browser-open.spec.ts`](tests/browser-open.spec.ts) | 页面可达后的默认浏览器交接 | -### 不变式归属 - -不发布不变式伴生入口,因为每项贡献——frontend-static 子插件、提示词段落与 bash 变量注册——都会随 fiber 由注册表释放,且每个所属注册表的包负责该关系的不变式。 -
----- @@ -133,7 +140,7 @@ URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发 #### 模型看到什么 -当 `surfaceContext` 为 true 时,`harness:source` 段落标明磁盘上的 Harness 实现,但不会声称它就是工作目录;全局段落 `app:web-surface`(first-party 顺序 10100,位于可复用指令之后)则向模型说明 GUI:规范的本地 URL、「this page」指代什么、更新约定(重载接收端始终开启;无刷新重载还需要 `pnpm run dev:web` watcher),以及不要启动替代服务器的指令。`DSH_WEB_URL` 还会连同描述出现在受管 bash 环境中,每次调用时从运行中的服务器解析。当它为 false 时,这两个段落和该变量都不会注册。 +当 `surfaceContext` 为 true 时,`harness:source` 段落标明磁盘上的 Harness 实现,但不会声称它就是工作目录;全局段落 `app:web-surface`(first-party 顺序 10100,位于可复用指令之后)则向模型说明 GUI:公告应用 URL(定义见上文「监听、信任与公开部署」)、「this page」指代什么、更新约定(重载接收端始终开启;无刷新重载还需要 `pnpm run dev:web` watcher),以及不要启动替代服务器的指令。`DSH_WEB_URL` 还会连同描述出现在受管 bash 环境中,每次调用时从运行中的服务器解析。当它为 false 时,这两个段落和该变量都不会注册。 #### Token 影响 @@ -141,7 +148,7 @@ URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发 #### KV Cache 影响 -源码与 Web 段落位于第一方可复用指令之后。工具与配置一致时,不同 checkout 路径或本地端口不会改变前置前缀;不保证提供方复用缓存。 +源码与 Web 段落位于第一方可复用指令之后。工具与配置一致时,不同 checkout 路径或应用 URL 不会改变前置前缀;不保证提供方复用缓存。 ## 已知限制与延期工作 @@ -151,9 +158,10 @@ URL 行与浏览器交接都是就绪信号:监督方一观察到该行就发 这些限制告诉你在不常见的环境下会遇到什么——源码 checkout、SSH 会话或严格网络。它们是当前包约束,不是通用的浏览器对比或任务积压。 - **前端必须已构建**——源码 checkout 需要先运行 `pnpm run build`;dist 缺失时启动会以构建提示停止,且没有从源码直接服务的回退路径。 +- **监听器不提供 TLS**——请用终止 TLS 的代理保护外部链路;HTTP 公告根会以明文发送凭据。 - **LAN 地址只在启动时采样一次**——启动后的网卡变化不会重新公告;打印的 LAN URL 始终与采样结果一致。 - **只能观察到交接的启动**——GUI 只报告浏览器被请求打开,而不是它确实打开了;之后的浏览器退出永远不会上报,打印的 URL 是你的手动回退路径。 -- **SSH 会话保留 URL 但跳过浏览器交接**——打印的 URL 指向远端宿主机 loopback 端点;SSH 客户端或编辑器必须暴露并打开本地转发地址。 +- **SSH 会话保留 URL 但跳过浏览器交接**——没有公告根时,打印的 URL 指向远端宿主机 loopback 端点;SSH 客户端或编辑器必须暴露并打开本地转发地址。 - **`BROWSER` 覆盖只能来自环境**——被发现的 `.env` 不能设置 `BROWSER`;只有继承值能为自动交接选择可执行文件。 - **不支持绑定所有网络接口**——出于安全考虑,`--host 0.0.0.0` 会在启动时被拒绝;请使用默认 loopback 主机。 diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index e177446672..ee5201b9c7 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -9,13 +9,12 @@ # ordinary Cordis service. Rows configured from flags inject that service, so # Loader resolves their expressions only after it exists. The web runtime then # provides bind-dependent `webRuntime` values to the trust fence. -# `dsh --profile web --help` provides neither service, so no server binds. +# `dsh --profile web --help` provides no `webStartup`, so no server binds. # ── surface-specific values the base deliberately omits ───────────────────── - id: system-prompt config: - personaSuffix: Your working directory is {{cwd}}. personaPrefix: >- You are a coding agent powered by the {{model}} model. @@ -31,7 +30,7 @@ - id: tools config: - # TEMPORARY workaround: DSH_TOOLS_MODE (native|ptc|both) opts a whole dsh + # TEMPORARY workaround: DSH_TOOLS_MODE (native|ptc) opts a whole dsh # process into PTC mode while per-session tool-presentation selection is being # designed; unset keeps the schema default (native). Remove the env seam # once the web UI owns the choice per session. @@ -171,8 +170,10 @@ # ── layer 2: transport/service ────────────────────────────────────────────── # Plain route-registration carrier; host and port come from the app's - # webStartup provider, with these deployment fallbacks. The dist is served by - # the web-runtime row below through the fallback seat. + # webStartup provider, with these deployment fallbacks. The dist is served + # by the web-runtime row below through the fallback seat. Routing and + # browser-facing asset paths do not depend on the advertised URL, which the + # web-runtime row owns. - id: webserver name: '@deepseek-ai/dsh-host-webserver' inject: [webStartup] @@ -187,18 +188,20 @@ # assembly fact of dsh-web-app, never user config), mounts the # frontend-static fallback owner, registers the web-surface prompt # section and the bash runtime variable, prints the URL line, and opens the - # canonical local URL after the full Loader tree settles and the required- - # entry audit passes. The - # webStartup provider supplies invocation-only values; after the server binds, this row samples - # LAN trust once and provides `webRuntime`. A complete agent-preset persona - # suppresses the prompt section for that agent while retaining the host-owned - # shell variable. + # canonical URL after the full Loader tree settles and the required- + # entry audit passes. Invocation-only values come from the webStartup + # provider; the advertised application URL is this row's `publicUrl` (the + # loopback URL when unset). After the server binds, this row samples LAN + # trust once and provides `webRuntime`. A complete agent-preset persona + # suppresses the prompt section for that agent while retaining the + # host-owned shell variable. - id: web-runtime name: '@deepseek-ai/dsh-web-app' inject: [webStartup] config: openBrowser: !!js ctx.webStartup.openBrowser printUrl: true + publicUrl: !!js ctx.webStartup.publicUrl surfaceContext: true trustedHosts: !!js ctx.webStartup.trustedHosts @@ -219,7 +222,9 @@ name: '@deepseek-ai/dsh-client-modules' # Owns both ends of the web transport: node half binds the gateway to the - # webserver under /api; browser half is the fetch/SSE client. + # webserver under /api; browser half is the fetch/SSE client. Every API + # request and upgrade passes the loopback plus trustedHosts policy; an + # advertised public URL contributes nothing to it. - id: connection name: '@deepseek-ai/dsh-client-connection' inject: [webRuntime] @@ -466,8 +471,8 @@ disabled: true # The background-job REGISTRY stays on the host plane; only the model-facing -# `job_*` controls move. Its producers — `tool-bash` here, `tool-terminal` and a -# non-continuable `tool-subagent` elsewhere — are preset rows that resolve it +# `job_*` controls move. Its producers — `tool-bash` here and `tool-terminal` +# elsewhere — are preset rows that resolve it # with `ctx.get`, and an entry-local realm around the registry is invisible to # every sibling row outside that realm, so `run_in_background` answered # "background jobs unavailable" while the controls sat in the catalog. That is @@ -552,12 +557,6 @@ - id: tool-workflow disabled: true -# `base` ships this row disabled; the preset-plane gate reads declared row ids -# without regard to `disabled`, so this row must stay to keep `tool-ralph` off -# the host plane and out of collision with every preset's row. -- id: tool-ralph - disabled: true - - id: agent-instructions disabled: true diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index ab39e107c4..81c3782121 100644 --- a/packages/bundle/web-app/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -35,6 +35,7 @@ "presets/ptc.patch.yml", "presets/minimal.patch.yml", "presets/cordis.patch.yml", + "lib/public-url-*.js", "lib/types/**/*.d.ts" ], "license": "MIT", @@ -159,7 +160,6 @@ "@deepseek-ai/dsh-tool-present": "workspace:*", "@deepseek-ai/dsh-tool-pwsh": "workspace:*", "@deepseek-ai/dsh-tool-pwsh-persistent": "workspace:*", - "@deepseek-ai/dsh-tool-ralph": "workspace:*", "@deepseek-ai/dsh-tool-schedule": "workspace:*", "@deepseek-ai/dsh-tool-skill": "workspace:*", "@deepseek-ai/dsh-tool-subagent": "workspace:*", @@ -173,7 +173,7 @@ "@deepseek-ai/dsh-workspace-changes": "workspace:*", "@deepseek-ai/schemastery": "workspace:~", "commander": "^15.0.0", - "open": "^11.0.0", + "open": "^11.0.2", "@deepseek-ai/dsh-api-account-controller": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-account": "workspace:*", "@deepseek-ai/dsh-client-shortcuts": "workspace:*", @@ -200,7 +200,7 @@ "@deepseek-ai/dsh-shell-env": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", "@deepseek-ai/dsh-typert-registry": "workspace:*", - "@deepseek-ai/libreoffice-kit": "^0.1.1", + "@deepseek-ai/libreoffice-kit": "^0.1.5", "@deepseek-ai/dsh-otel": "workspace:*" } } diff --git a/packages/bundle/web-app/presets/cordis.patch.yml b/packages/bundle/web-app/presets/cordis.patch.yml index f6527f77ba..277176f1a0 100644 --- a/packages/bundle/web-app/presets/cordis.patch.yml +++ b/packages/bundle/web-app/presets/cordis.patch.yml @@ -13,7 +13,6 @@ - id: persona name: '@deepseek-ai/dsh-persona' config: - suffix: Your working directory is {{cwd}}. prefix: >- You are a coding agent powered by the {{model}} model. - id: agent-instructions @@ -96,7 +95,6 @@ provider: spawn toolName: subagent modelSelectionSettings: true - backgroundMode: continuable toolFilter: deny: - schedule_create @@ -108,41 +106,18 @@ config: provider: fork toolName: subagent_fork - backgroundMode: continuable toolFilter: deny: - schedule_create - schedule_delete - schedule_list - schedule_update - - id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: codex - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-code - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: claude-code - toolName: subagent_claude_code - backgroundMode: one-shot - maxDepth: provider-managed - id: workflow-ptc name: '@deepseek-ai/dsh-workflow-ptc' config: provider: spawn - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' - - id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' - disabled: true - config: - subagentProvider: spawn - maxRounds: 64 - id: tool-ask-user name: '@deepseek-ai/dsh-tool-ask-user' - id: tool-todo diff --git a/packages/bundle/web-app/presets/ptc.patch.yml b/packages/bundle/web-app/presets/ptc.patch.yml index de206c33be..1aed4cb9e9 100644 --- a/packages/bundle/web-app/presets/ptc.patch.yml +++ b/packages/bundle/web-app/presets/ptc.patch.yml @@ -11,7 +11,6 @@ - id: persona name: '@deepseek-ai/dsh-persona' config: - suffix: Your working directory is {{cwd}}. prefix: You are a coding agent powered by the {{model}} model. - id: agent-instructions name: '@deepseek-ai/dsh-agent-instructions' @@ -97,7 +96,6 @@ provider: spawn toolName: subagent modelSelectionSettings: true - backgroundMode: continuable toolFilter: deny: - schedule_create @@ -109,29 +107,12 @@ config: provider: fork toolName: subagent_fork - backgroundMode: continuable toolFilter: deny: - schedule_create - schedule_delete - schedule_list - schedule_update - - id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: codex - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-code - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: claude-code - toolName: subagent_claude_code - backgroundMode: one-shot - maxDepth: provider-managed - id: workflow-ptc name: '@deepseek-ai/dsh-workflow-ptc' disabled: true @@ -140,12 +121,6 @@ - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' disabled: true - - id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' - disabled: true - config: - subagentProvider: spawn - maxRounds: 64 - id: tool-ask-user name: '@deepseek-ai/dsh-tool-ask-user' - id: tool-todo diff --git a/packages/bundle/web-app/presets/standard.patch.yml b/packages/bundle/web-app/presets/standard.patch.yml index a0b474b0e3..f2a9e080fe 100644 --- a/packages/bundle/web-app/presets/standard.patch.yml +++ b/packages/bundle/web-app/presets/standard.patch.yml @@ -11,7 +11,6 @@ - id: persona name: '@deepseek-ai/dsh-persona' config: - suffix: Your working directory is {{cwd}}. prefix: You are a coding agent powered by the {{model}} model. - id: agent-instructions name: '@deepseek-ai/dsh-agent-instructions' @@ -97,7 +96,6 @@ provider: spawn toolName: subagent modelSelectionSettings: true - backgroundMode: continuable toolFilter: deny: - schedule_create @@ -109,41 +107,18 @@ config: provider: fork toolName: subagent_fork - backgroundMode: continuable toolFilter: deny: - schedule_create - schedule_delete - schedule_list - schedule_update - - id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: codex - toolName: subagent_codex - backgroundMode: one-shot - maxDepth: provider-managed - - id: tool-subagent-claude-code - name: '@deepseek-ai/dsh-tool-subagent' - disabled: true - config: - provider: claude-code - toolName: subagent_claude_code - backgroundMode: one-shot - maxDepth: provider-managed - id: workflow-ptc name: '@deepseek-ai/dsh-workflow-ptc' config: provider: spawn - id: tool-workflow name: '@deepseek-ai/dsh-tool-workflow' - - id: tool-ralph - name: '@deepseek-ai/dsh-tool-ralph' - disabled: true - config: - subagentProvider: spawn - maxRounds: 64 - id: tool-ask-user name: '@deepseek-ai/dsh-tool-ask-user' - id: tool-todo diff --git a/packages/bundle/web-app/src/index.ts b/packages/bundle/web-app/src/index.ts index 76c212e4e6..bf1aec78c1 100644 --- a/packages/bundle/web-app/src/index.ts +++ b/packages/bundle/web-app/src/index.ts @@ -5,9 +5,10 @@ * the built frontend dist (workspace knowledge of this bundle, never user * config), mounts the `frontend-static` fallback owner over it, registers the * harness-source and web-surface prompt sections, the bash-visible web runtime - * variable, the process-token URL line, and the default-browser handoff. The - * model and shell retain the clean URL. App command-line values arrive through - * the `webStartup` service expressions in the bundle patch. + * variable, the process-token URL line, and the default-browser handoff. An + * advertised `publicUrl` replaces the published root — the loopback URL + * otherwise. App command-line values arrive through the `webStartup` service + * expressions in the bundle patch. * @module @deepseek-ai/dsh-web-app */ @@ -26,6 +27,7 @@ import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess' import type {} from '@deepseek-ai/cordis-plugin-loader' import type {} from '@deepseek-ai/dsh-host-webserver' import type {} from '@deepseek-ai/dsh-shell-env' +import { parsePublicUrl } from './public-url.ts' /** Stable Cordis plugin name. */ export const name = 'web-app' @@ -53,6 +55,14 @@ export interface Config { * orientation text would be false. */ surfaceContext: boolean + /** + * Canonical HTTP(S) root to advertise in the printed and opened URL, + * `DSH_WEB_URL`, and the web-surface orientation, e.g. + * `https://app.example/ui/`, normalized to end in `/`. Advertisement only; + * see [public deployments](../README.md#public-deployments). Absent or YAML + * `null` advertises the loopback URL. + */ + publicUrl?: string /** Explicit `--trusted-host` authorities from this invocation. */ trustedHosts: string[] } @@ -61,6 +71,7 @@ export const Config: z = z.object({ openBrowser: z.boolean().default(true), printUrl: z.boolean().default(true), surfaceContext: z.boolean().default(true), + publicUrl: z.transform(z.string(), value => parsePublicUrl(value).href), trustedHosts: z.array(String).default([]), }) @@ -72,7 +83,7 @@ export interface WebRuntimeValues { trustedHosts: string[] } -/** Environment variable naming the canonical local URL of this Web GUI. */ +/** Environment variable naming the advertised URL of this Web GUI. */ const DSH_WEB_URL = 'DSH_WEB_URL' as const // Display-only mirror of the webserver schema's loopback host: the address the @@ -152,6 +163,11 @@ function localWebUrl(ctx: Context): string { return `http://${LOOPBACK_HOST}:${String(port)}` } +function appRootUrl(ctx: Context, publicUrl: string | undefined): string { + if (publicUrl !== undefined) return publicUrl + return localWebUrl(ctx) +} + /** * Dist location is workspace knowledge of this bundle: anchored on the * frontend package manifest, not configured. Existence is a request-time @@ -224,6 +240,9 @@ export const internals: { */ export function apply(ctx: Context, config: Config): void { const runtime = resolveLanTrust(ctx.webServer.host, config.trustedHosts) + // The schema validates a present string; an explicit YAML `null` bypasses + // the string transform and reaches here, meaning unset. + const publicUrl = config.publicUrl ?? undefined // The loopback URL belongs to this host. Under SSH, the operator reaches it // through a local forwarding address that this process cannot derive. const handoffBrowser = config.openBrowser && !launchedThroughSsh(launchEnvironmentOf(ctx)) @@ -236,16 +255,16 @@ export function apply(ctx: Context, config: Config): void { promptCtx.systemPrompt.section({ name: 'app:web-surface', order: promptCtx.systemPrompt.getSectionOrder('WEB_SURFACE'), - text: () => webSurfacePrompt(localWebUrl(promptCtx)), + text: () => webSurfacePrompt(appRootUrl(promptCtx, publicUrl)), }) }) ctx.inject(['shellEnv'], (runtimeCtx) => { runtimeCtx.shellEnv.register({ name: 'web-runtime', variables: { - [DSH_WEB_URL]: { description: 'Canonical local URL of the DeepSeek Harness Web GUI serving this session.' }, + [DSH_WEB_URL]: { description: 'Advertised URL of the DeepSeek Harness Web GUI serving this session.' }, }, - resolve: () => ({ [DSH_WEB_URL]: localWebUrl(runtimeCtx) }), + resolve: () => ({ [DSH_WEB_URL]: appRootUrl(runtimeCtx, publicUrl) }), }) }) } @@ -258,7 +277,7 @@ export function apply(ctx: Context, config: Config): void { // hand-built tree without a Loader is already the complete tree. const announceReady = (): void => { if (ANNOUNCED_ROOTS.has(connectionCtx.root)) return - const webUrl = localWebUrl(connectionCtx) + const webUrl = appRootUrl(connectionCtx, publicUrl) const authenticatedUrl = connectionCtx.connection.authenticatedUrl(webUrl) // Reuse the exact LAN snapshot provided to the /api trust fence. const lanCandidate = runtime.lanAddresses[0] diff --git a/packages/bundle/web-app/src/public-url.ts b/packages/bundle/web-app/src/public-url.ts new file mode 100644 index 0000000000..408013e471 --- /dev/null +++ b/packages/bundle/web-app/src/public-url.ts @@ -0,0 +1,39 @@ +/** + * Canonical public HTTP(S) root parsing shared by this bundle's `publicUrl` + * config and its CLI startup provider. + * @module @deepseek-ai/dsh-web-app/public-url + */ + +/** + * Parse the advertised public HTTP(S) root of a prefix-stripping proxy + * deployment, rejecting the hazards WHATWG URL parsing would accept on its own, + * then normalize the path to end in `/`. + * @param value - advertised public HTTP(S) root. + * @param label - field name or CLI flag included in rejection messages. + * @returns the parsed URL with a trailing-slash pathname. + * @throws when the value is not a usable advertised root, naming the reason. + */ +export function parsePublicUrl(value: string, label = 'publicUrl'): URL { + if (/[\u0000-\u0020\u007f]/u.test(value)) { + throw new Error(`${label} must not contain ASCII whitespace or control characters`) + } + if (!/^https?:\/\//iu.test(value)) { + throw new Error(`${label} must be an absolute http or https URL of the form http(s)://host[/prefix]`) + } + if (value.includes('?') || value.includes('#')) { + throw new Error(`${label} must not include a query or fragment (? or #)`) + } + // Userinfo lives in the raw authority (before the first path slash); the + // empty-authority forms (`https://`, `https:///ui`, `http:///ui`) fail here too. + const [authority = ''] = value.slice(value.indexOf('://') + 3).split(/[/\\]/, 1) + if (authority === '') { + throw new Error(`${label} must name a host after http:// or https://`) + } + if (authority.includes('@')) { + throw new Error(`${label} must not include credentials (a username or password before @, including an empty user)`) + } + const url = URL.parse(value) + if (url === null) throw new Error(`${label} must be an absolute http or https URL`) + if (!url.pathname.endsWith('/')) url.pathname += '/' + return url +} diff --git a/packages/bundle/web-app/src/startup.ts b/packages/bundle/web-app/src/startup.ts index 9faad244dd..749dd8ea5d 100644 --- a/packages/bundle/web-app/src/startup.ts +++ b/packages/bundle/web-app/src/startup.ts @@ -1,14 +1,16 @@ /** * The web app's command-line provider: it parses the `dsh --profile web` flag - * family (`--host`, `--port`, `--trusted-host`, `--no-open`) and its `--help` - * text, then provides the immutable values as {@link WEB_STARTUP_SERVICE}. - * Ordinary rows inject that service before reading it from lazy config. + * family (`--host`, `--port`, `--public-url`, `--trusted-host`, `--no-open`) + * and its `--help` text, then provides the immutable values as + * {@link WEB_STARTUP_SERVICE}. Ordinary rows inject that service before + * reading it from lazy config. * @module @deepseek-ai/dsh-web-app/startup */ import { Command } from 'commander' import type { Context } from '@deepseek-ai/cordis' import { parseCmdline } from '@deepseek-ai/dsh-cmdline' +import { parsePublicUrl } from './public-url.ts' /** Stable Cordis plugin name. */ export const name = 'web-startup' @@ -27,6 +29,11 @@ export interface WebStartupValues { host?: string /** `--port`, absent when the invocation did not name one. */ port?: number + /** + * `--public-url`, absent when not specified: the advertised HTTP(S) root. + * See [public deployments](../README.md#public-deployments). + */ + publicUrl?: string /** Explicit `--trusted-host` authorities, in argument order. */ trustedHosts: string[] } @@ -36,6 +43,7 @@ interface WebOptions { host?: string open: boolean port?: string + publicUrl?: string trustedHost?: string[] } @@ -51,20 +59,23 @@ function webCommand(): Command { .option('--host ', 'bind host') .option('--no-open', 'do not open the Web UI in the default browser') .option('--port ', 'listen port; pass 0 to let the OS pick a free one') + .option('--public-url ', 'advertise this HTTP(S) root in the printed, opened, web-surface, and DSH_WEB_URL forms; grants no trust') .option('--trusted-host ', 'extra authority the /api browser-trust fence accepts (host or host:port; repeatable)') .addHelpText('after', ` Examples: dsh --profile web serve on the composed host and port dsh --profile web --no-open serve without opening a browser dsh --profile web --port 8080 serve on another port + dsh --profile web --public-url https://app.example/ui/ --trusted-host app.example + advertise a prefix-stripping HTTPS proxy entry and admit its authority `) } /** * Parse and provide the Web invocation as an ordinary Cordis service. The - * command's action publishes the flags this invocation named; `--host 0.0.0.0` - * or a non-numeric `--port` is a usage error, so on rejection (and on `--help`) - * nothing is provided. + * command's action publishes the flags this invocation named; `--host 0.0.0.0`, + * a non-numeric `--port`, or a malformed `--public-url` is a usage error, so on + * rejection (and on `--help`) nothing is provided. * @param ctx - plugin context carrying the command line. */ export function apply(ctx: Context): void { @@ -77,10 +88,18 @@ export function apply(ctx: Context): void { if (options.port !== undefined && !/^\d+$/.test(options.port)) { program.error(`error: --port must be a number, got ${JSON.stringify(options.port)}`) } + if (options.publicUrl !== undefined) { + try { + parsePublicUrl(options.publicUrl, '--public-url') + } catch (error) { + program.error(`error: ${(error as Error).message}`) + } + } ctx.provide(WEB_STARTUP_SERVICE, { openBrowser: options.open, ...options.host !== undefined && { host: options.host }, ...options.port !== undefined && { port: Number(options.port) }, + ...options.publicUrl !== undefined && { publicUrl: options.publicUrl }, trustedHosts: options.trustedHost ?? [], } satisfies WebStartupValues) }) diff --git a/packages/bundle/web-app/tests/public-url.spec.ts b/packages/bundle/web-app/tests/public-url.spec.ts new file mode 100644 index 0000000000..0d236d7729 --- /dev/null +++ b/packages/bundle/web-app/tests/public-url.spec.ts @@ -0,0 +1,32 @@ +/** + * Contract of the Web bundle's advertised-root parser: a canonical HTTP(S) + * root with an optional mount prefix, normalized to a trailing slash. The + * rejections pin the hazards WHATWG URL parsing accepts on its own. + */ + +import { describe, expect, it } from 'vitest' +import { parsePublicUrl } from '../src/public-url.ts' + +describe('parsePublicUrl', () => { + it('accepts canonical HTTP(S) roots and normalizes a missing trailing slash', () => { + expect(parsePublicUrl('https://app.example').href).toBe('https://app.example/') + expect(parsePublicUrl('https://app.example/ui').href).toBe('https://app.example/ui/') + expect(parsePublicUrl('https://app.example:8443/ui').href).toBe('https://app.example:8443/ui/') + expect(parsePublicUrl('https://[::1]:8443/ui').href).toBe('https://[::1]:8443/ui/') + expect(parsePublicUrl('HTTPS://app.example/UI').href).toBe('https://app.example/UI/') + }) + + + + it.each([ + // One row per rejection branch; the whitespace check precedes the scheme check. + ['relative or non-HTTP(S)', '/web/ui', 'publicUrl must be an absolute http or https URL of the form'], + ['whitespace or control', 'https://app.example/ui ', 'publicUrl must not contain ASCII whitespace or control characters'], + ['query or fragment', 'https://app.example/ui?x=1', 'publicUrl must not include a query or fragment'], + ['host-less', 'https:///ui', 'publicUrl must name a host after http:// or https://'], + ['credential-bearing', 'https://user@example.com/', 'publicUrl must not include credentials'], + ['unparseable', 'https://[broken/', /^publicUrl must be an absolute http or https URL$/u], + ] as const)('rejects a %s publicUrl', (_kind, value, message) => { + expect(() => parsePublicUrl(value)).toThrow(message) + }) +}) diff --git a/packages/bundle/web-app/tests/startup.spec.ts b/packages/bundle/web-app/tests/startup.spec.ts index a10272f9d3..322e98a818 100644 --- a/packages/bundle/web-app/tests/startup.spec.ts +++ b/packages/bundle/web-app/tests/startup.spec.ts @@ -63,6 +63,7 @@ export const apply = ctx => globalThis.__webStartupApply(ctx) " host: !!js ctx.webStartup.host ?? '127.0.0.1'", ' openBrowser: !!js ctx.webStartup.openBrowser', ' port: !!js ctx.webStartup.port ?? 3080', + ' publicUrl: !!js ctx.webStartup.publicUrl', ' trustedHosts: !!js ctx.webStartup.trustedHosts', '- id: provider', ` name: ${pathToFileURL(join(dir, 'provider.mjs')).href}`, @@ -125,6 +126,7 @@ describe('web command-line provider', () => { const { values, observed } = await bootProvider(['--help']) expect(observed.out).toContain('dsh --profile web') expect(observed.out).toContain('--no-open') + expect(observed.out).toContain('--public-url') expect(observed.out).toContain('--trusted-host') expect(values).toBeUndefined() expect(observed.readerConfig).toBeUndefined() @@ -146,4 +148,34 @@ describe('web command-line provider', () => { expect(observed.readerConfig).toBeUndefined() expect(observed.exits).toEqual([1]) }) + + it('publishes --public-url as advertisement only, leaving the fence to --trusted-host', async () => { + const { values, observed } = await bootProvider([ + '--public-url', 'https://web.example/ui', + '--trusted-host', 'lab.internal', + ]) + expect(values).toEqual({ + openBrowser: true, + publicUrl: 'https://web.example/ui', + trustedHosts: ['lab.internal'], + }) + expect(observed.readerConfig).toEqual({ + host: '127.0.0.1', + openBrowser: true, + port: 3080, + publicUrl: 'https://web.example/ui', + trustedHosts: ['lab.internal'], + }) + expect(observed.exits).toEqual([]) + }) + + it('rejects a malformed --public-url before the consumer activates', async () => { + // The parser's own suite owns the exhaustive spellings; the provider only + // has to fail the invocation before any consumer activates. + const { values, observed } = await bootProvider(['--public-url', '/web/ui']) + expect(observed.out).toContain('error: --public-url must be an absolute http or https URL of the form http(s)://host[/prefix]') + expect(values).toBeUndefined() + expect(observed.readerConfig).toBeUndefined() + expect(observed.exits).toEqual([1]) + }) }) diff --git a/packages/bundle/web-app/tests/web-app.spec.ts b/packages/bundle/web-app/tests/web-app.spec.ts index c0aa838c0f..6d2b4be201 100644 --- a/packages/bundle/web-app/tests/web-app.spec.ts +++ b/packages/bundle/web-app/tests/web-app.spec.ts @@ -2,7 +2,9 @@ * Web runtime glue behavior: dist resolution through the bundle's own hook, * the frontend-static child claiming the fallback seat, the web-surface * prompt section and bash runtime variables, and readiness publication through - * the URL line and default-browser handoff. + * the URL line and default-browser handoff. The advertised URL comes from this + * plugin's `publicUrl` config (loopback when unset), never from the bound + * webserver. */ import { EventEmitter } from 'node:events' @@ -88,9 +90,10 @@ function fakeHttpServer(host: '127.0.0.1' | '0.0.0.0' = '127.0.0.1'): { server: /** Deterministic Host Connection face for URL publication and frontend injection. */ function provideConnection(ctx: Context): void { ctx.provide('connection', { + // Adds the process token to the given canonical root, preserving any + // public mount prefix the caller passed. authenticatedUrl(baseUrl: string) { const url = new URL(baseUrl) - url.pathname = '/' url.searchParams.set('token', 'test-token') return url.href }, @@ -165,6 +168,48 @@ describe('web-app runtime glue', () => { await ctx.fiber.dispose() }) + it('advertises and opens the configured public root behind a prefix-stripping proxy', async () => { + stageDist() + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + const { server } = fakeHttpServer() + ctx.provide('webServer', server) + provideConnection(ctx) + const contributions: BashContribution[] = [] + ctx.provide('shellEnv', { + register: (contribution: BashContribution) => { + contributions.push(contribution) + return () => {} + }, + } as never) + provideLoader(ctx) + const lifecycle: string[] = [] + vi.spyOn(console, 'log').mockImplementation((message) => { lifecycle.push(String(message)) }) + const openBrowser = vi.fn(async (url: string) => { lifecycle.push(`open:${url}`) }) + internals.openBrowser = openBrowser + apply(ctx, new Config({ + openBrowser: true, + printUrl: true, + surfaceContext: true, + publicUrl: 'https://proxy.example:8443/web', + trustedHosts: ['proxy.example:8443'], + })) + await ctx.plugin(SystemPrompt, { personaPrefix: '' }) + await vi.waitFor(() => { expect(openBrowser).toHaveBeenCalled() }) + + expect(lifecycle).toEqual([ + 'dsh web: https://proxy.example:8443/web/?token=test-token', + 'dsh web: opening the default browser; pass --no-open to disable', + 'open:https://proxy.example:8443/web/?token=test-token', + ]) + const assembly = await ctx.systemPrompt.assemble() + const section = assembly.sections.find(entry => entry.name === 'app:web-surface') + expect(section?.text).toContain('https://proxy.example:8443/web/') + expect(section?.text).not.toContain('127.0.0.1') + const urlContribution = contributions.find(contribution => contribution.name === 'web-runtime') + expect(urlContribution?.resolve()).toEqual({ DSH_WEB_URL: 'https://proxy.example:8443/web/' }) + }) + it('publishes no readiness side effect when printing and browser opening are disabled', async () => { stageDist() const ctx = new Context() @@ -236,6 +281,17 @@ describe('web-app runtime glue', () => { await ctx.fiber.dispose() }) + it('advertises the loopback URL when the composition sets publicUrl to null', async () => { + stageDist() + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + ctx.provide('webServer', fakeHttpServer().server) + provideConnection(ctx) + const log = vi.spyOn(console, 'log').mockImplementation(() => {}) + apply(ctx, new Config({ openBrowser: false, printUrl: true, surfaceContext: false, publicUrl: null as never, trustedHosts: [] })) + await vi.waitFor(() => { expect(log).toHaveBeenCalledWith('dsh web: http://127.0.0.1:4567/?token=test-token') }) + }) + it.each([ ['SSH_CONNECTION', '10.0.0.2 55000 10.0.0.9 22'], ['SSH_TTY', '/dev/pts/3'], diff --git a/packages/client/AGENTS.md b/packages/client/AGENTS.md index 605a9fcde3..2d39d6b3ca 100644 --- a/packages/client/AGENTS.md +++ b/packages/client/AGENTS.md @@ -24,7 +24,7 @@ How live data reaches render code, and what UI domains may share: 1. **Everything a render reads that can change outside React arrives through a framework hook** (rule 4 above). Event-handler code may read live snapshots (e.g. `keyboard.snapshot`); render code subscribes. 2. **Business components contain no subscription machinery** — no `useSyncExternalStore`, no manual subscribe wiring, no mirroring an external snapshot into local state or a second store. Give each reactive fact its owning channel instead: registrant-private → the inject `hooks` compartment; cross-entry or remount-surviving → a declared store; per-session standard → `sessions.provide`. 3. **Data-access ladder** — resolve needs in this order: framework hooks (standing seats + provide/inject-bound `use`) → a declared store (`useStore`/`actions`) → inject callbacks → anything else is a new framework extension point and needs main-thread arbitration. -4. **UI domains share JSON-compatible data and callbacks.** Owner props, injected values, store state, and provide contributions use these values. Transient file-read and document-preview content may also carry `Uint8Array`: published buffers are borrowed read-only, copied before Worker transfer, and never persisted in layout or Session JSON. The injected `hooks` compartment is the only place for bare observables, and components never receive those sources directly. Route ReactNode content through a slot; do not add ReactNode-valued owner props or injected members (the composer's existing `accessory`/`overlay`/`leftItems`/`rightItems` fields remain until they move to slots). +4. **UI domains share JSON-compatible data and callbacks.** Owner props, injected values, store state, and provide contributions use these values. Tool `args` may carry the [lazy argument reader](../../.agents/notes/implemented/architecture/2026-09-24-preparing-tool-arguments.md); its Definition republishes the block when an observed answer changes. Transient file-read and document-preview content may also carry `Uint8Array`: published buffers are borrowed read-only, copied before Worker transfer, and never persisted in layout or Session JSON. The injected `hooks` compartment is the only place for bare observables, and components never receive those sources directly. Route ReactNode content through a slot; do not add ReactNode-valued owner props or injected members (the composer's existing `accessory`/`overlay`/`leftItems`/`rightItems` fields remain until they move to slots). 5. **An observable source keeps two identities stable**: the source object itself (hook binding is cached per source), and its snapshot between changes (`getSnapshot` returns the same reference until the fact moves). 6. **Whoever rebuilds a published value republishes it through the same source in the same step**, and a registration path that can run after consumers exist notifies the live consumers as part of registering. @@ -63,7 +63,7 @@ Npm sections describe installation and development relationships; each build fac 2. **A package under `packages/client/` is always covered; `dsh.client` marks a Client/Host package outside that directory.** Explicit include/exclude entries handle exceptions. Every covered package's Host entry is scanned, while a `./client` export alone does not select dependency policy. 3. **Browser and type relationships are development-only.** Workspace and third-party Client imports, type-only imports, module augmentations, TypeScript project references, `dsh.client.inject`, and metadata-only peers belong only in `devDependencies`. Configuration-only entries absent from imports are listed in the dependency policy; Node companion value imports follow the Host rules below. 4. **Host value imports require classified exports.** A workspace value reached from the package's Host entry belongs only in `dependencies` when its exact module specifier and runtime export appear in `safeHostDependencyExports`. Exports whose identity or module state must be shared appear in `peerRequiredHostExports` and keep the whole package edge in matching `peerDependencies` and `devDependencies`. The verifier rejects unclassified exports before `--fix` writes manifests. -5. **Only Host runtime libraries require installation dependencies.** Browser-only third-party implementations belong in `devDependencies`, whether inlined into `lib/client.js` or retained as bare imports in a static browser library. Static libraries are Web-shell build inputs; the final Vite build merges their imports and styles. Distributed browser code remains subject to runtime license disclosure ([decision](../../.agents/notes/implemented/process/2026-09-08-browser-third-party-build-inputs.md)). +5. **Only Host runtime libraries require installation dependencies.** Browser-only third-party implementations belong in `devDependencies`, whether inlined into `lib/client.js` or retained as bare imports in a static browser library. Static libraries are Web-shell build inputs; the final Vite build merges their imports and styles. Distributed browser code remains subject to runtime license disclosure ([reference](../../.agents/notes/implemented/process/2026-08-26-published-dependency-faces.md)). 6. **Browser and Node build faces declare externality independently.** A dynamic browser half uses the baseline plus `dsh.client.external`; a statically linked face externalizes every bare specifier; a Node face externalizes its production dependencies ([`tsdown.client.ts`](tsdown.client.ts)). Moving a name between npm sections must not silently change bundle contents. 7. **Keep the published payload closed.** Every relative runtime import and emitted asset must be covered by `files`; the repository publint pass checks the exact publication view. @@ -118,7 +118,7 @@ Every product-visible string—including text, accessibility names, tooltips, pl ## Testing and coverage -The GUI test structure (three tiers, lane map) is settled in the [GUI testing system note](../../.agents/notes/implemented/process/2026-07-20-gui-testing-system.md); repo-wide policy in [docs/testing.md](../../docs/testing.md). +The GUI test structure (three tiers, lane map) is settled in the [GUI testing system reference](../../docs/testing.md); repo-wide policy in [docs/testing.md](../../docs/testing.md). - Client source packages are inside the per-file 100% coverage gate (`pnpm run test:coverage`). Genuinely unreachable defensive arms take a `/* v8 ignore -- */` comment with a real reason, never a bare ignore. - Component specs render with realistic props or a driven fixture runtime and assert user-visible behavior, not class names, hook internals, or render counts. @@ -139,7 +139,7 @@ If `test:gui` is red on code you did not touch, neither silently fix nor ignore Bringing up a new `packages/client/` plugin package (ui-workspace is a complete example; ui-sidebar/ui-user-questions are minimal skeletons): -1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-`, exports `.`/`./client`/`./src/*`/`./package.json`, optional `./invariant` only for an independent runtime relationship, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js'])`, plus `lib/types/invariant.js` only when published), `src/index.ts` (empty node-half apply), optional `src/invariant.ts`, `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section and the reason when no invariant is published. +1. **Package skeleton**: `package.json` (`@deepseek-ai/dsh-client-`, exports `.`/`./client`/`./src/*`/`./package.json`, `dsh.client` manifest, `files` list), `tsconfig.json` (extends `tsconfig.base.client.json`, one `references` entry per workspace dependency), `tsdown.config.ts` (`clientBundle(id, ['lib/types/index.js'])`), `src/index.ts` (empty node-half apply), `src/css-modules.d.ts` when using CSS Modules, and `README.md` with the Model Experience section. 2. **Three registration surfaces, all required** (missing any one fails at a different, later point): the `tsconfig.client.json` aggregate `references` entry; a `dsh.client` row in `packages/bundle/web-app/cordis.patch.yml`; a `packages/bundle/web-app/package.json` dependency (profile boots resolve bare row names through the computed runtime resolution, which is installed as an interception on Node's resolvers — a row whose package no manifest declares fails to import). `pnpm-workspace.yaml` already globs `packages/*/*`. 3. **dsh.client manifest semantics**: `platform: 'web'` always, and the declaration requires a `./client` export (the scan throws without one); `immediately: true` only for stage-one-prefetch infrastructure rows. `inject` lists package-name dependency edges — they are **informational only** (preflight display, HMR diffing); they do not sequence entry activation or apply order. Activation order is Cordis fiber inject waiting on *services*, nothing else. A non-baseline `external` request sequences its dynamic supplier ahead of the consumer — see [shared modules](#shared-modules-and-the-module-graph). 4. **Registering into another package's slot**: apply order is unconstrained, and a business service is not a declaration barrier. Use `ctx.slots.inject(name, () => ctx.slots.register(...))`; it waits on the actual declaration, removes the contribution when that declaration collapses, reruns after redeclaration, and leaves with the caller's plugin fiber. Return a generator yielding each registration when several contributions must install and roll back atomically. A bare `slots.register` into an undeclared slot remains an error; keep service edges only for services the contribution actually reads. diff --git a/packages/client/README.i18n.yaml b/packages/client/README.i18n.yaml index 0b76133bf7..766b7d16d0 100644 --- a/packages/client/README.i18n.yaml +++ b/packages/client/README.i18n.yaml @@ -18,8 +18,8 @@ en: 1aa93b8088fb1962 zh: 10feaed78bc6408c /client-web-gui-browser-half/related-documentation: - en: 598d07b07449128f - zh: e8c40708ac0e603e + en: d1d506e68fab7333 + zh: 1a90fe6f3f8f24a9 /client-web-gui-browser-half/dev-note: en: 5de1d6304e00c58f zh: 8d8225361cfa29e5 diff --git a/packages/client/README.md b/packages/client/README.md index bb89a761f2..04eb19d8f7 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -97,7 +97,7 @@ Start with the subsystem reference and the two notes that own the cross-package - [Client modules subsystem](../../docs/subsystems/client-modules.md) — the web plugin table: `dsh.client` declarations, the boot graph wire, and the bundle route. - [Slot system standard](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) — the definitive slot model: registration, props shares, and stores. -- [Web client architecture note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) — the loading chain, object layer, and client services. +- [Web client architecture reference](../../docs/subsystems/web-client.md) — the loading chain, object layer, and client services. - [Host group map](../host/README.md) — the host half that serves this browser half. diff --git a/packages/client/README.zh.md b/packages/client/README.zh.md index 83f0a7d0f1..2ebc91a3e1 100644 --- a/packages/client/README.zh.md +++ b/packages/client/README.zh.md @@ -97,7 +97,7 @@ kind: "package-group" - [客户端模块子系统](../../docs/subsystems/client-modules.zh.md)——web 插件表:`dsh.client` 声明、启动图协议与 bundle 路由。 - [slot 系统标准](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.zh.md)——权威 slot 模型:注册、props 份额与存储。 -- [web 客户端架构 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.zh.md)——加载链、对象层与客户端服务。 +- [web 客户端架构 reference](../../docs/subsystems/web-client.zh.md)——加载链、对象层与客户端服务。 - [宿主组地图](../host/README.zh.md)——服务于本浏览器半侧的宿主半侧。 diff --git a/packages/client/connection/README.i18n.yaml b/packages/client/connection/README.i18n.yaml index 2b327002f9..7498a796dd 100644 --- a/packages/client/connection/README.i18n.yaml +++ b/packages/client/connection/README.i18n.yaml @@ -21,8 +21,8 @@ en: ab52ef146d0b5b42 zh: 6314d50cfe5683e1 /deepseek-ai-dsh-client-connection/connection-generation: - en: db8757857a15c0c1 - zh: c8e15907461ef54e + en: 116f94e44c0d87e7 + zh: 59ef48984154d858 /deepseek-ai-dsh-client-connection/model-experience: en: 88bbc33fe9ae9df8 zh: bfbab6d51b4abd40 @@ -33,5 +33,5 @@ en: 2c964a7546604347 zh: c30b59fc92a01c36 /deepseek-ai-dsh-client-connection/known-limitations-and-deferred-work/dev-note: - en: c51db1312f6a43ef - zh: 7193ff08f53e3230 + en: e67f633dc6fe7773 + zh: e88aeb617ffb74ea diff --git a/packages/client/connection/README.md b/packages/client/connection/README.md index e50f4fb683..f973fe1d4e 100644 --- a/packages/client/connection/README.md +++ b/packages/client/connection/README.md @@ -51,7 +51,7 @@ Authenticated shared HTTP requests pass through the `connection/request` waterfa API Gateway Client registers the internal `$events` logical stream as the sole generation source, independently of whether any `$on` listener exists. The Host attaches all incremental listeners in the API Remotes source factory, then sends one `{ type: 'ready', clientId, host: { home } }` item before events. `ConnectionController` publishes that generation and calls `onConnected` only after the ready item arrives, so baseline acquisition cannot race ahead of incremental observation. -An ended `$events` stream, a Remote stream error, a non-ready opening item, or a malformed event item invalidates the current generation. A pending handshake logs a slow-Host warning after 3 seconds and logs the readiness timeout and aborts after 15 seconds by default, including time spent waiting for the physical socket. The source must stop delivery, release resources, and settle after cancellation before a replacement starts; late readiness from a cancelled source cannot publish a generation. While the browser reports network availability, the controller publishes `connecting` and retries with 50%–100% jitter under caps of 500ms, 1s, 2s, 4s, 8s, and 10s, continuing at the final cap until recovery. Every retry asks Gateway to replace the physical WebSocket once and reopens `$events`. The [continuous recovery decision](../../../.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.md) owns the deadlines and retry policy. +An ended `$events` stream, a Remote stream error, a non-ready opening item, or a malformed event item invalidates the current generation. A pending handshake logs a slow-Host warning after 3 seconds and logs the readiness timeout and aborts after 15 seconds by default, including time spent waiting for the physical socket. The source must stop delivery, release resources, and settle after cancellation before a replacement starts; late readiness from a cancelled source cannot publish a generation. While the browser reports network availability, the controller publishes `connecting` and retries with 50%–100% jitter under caps of 500ms, 1s, 2s, 4s, 8s, and 10s, continuing at the final cap until recovery. Every retry asks Gateway to replace the physical WebSocket once and reopens `$events`. `ctx.connection.reconnect()` interrupts active work, resets the sequence, and starts retry 1 immediately. Browser `offline` aborts active work, publishes `disconnected`, and suspends automatic attempts; the next `online` transition resets the sequence and starts at the 500ms tier. Only a ready item publishes `connected`. Gateway mux owns no independent retry schedule. @@ -85,5 +85,3 @@ None; this package neither assembles nor sends a provider request. None. - -**Runtime invariant:** No companion is published. Browser-session verification reads the credential record asynchronously at the request that authorizes work, while the credentials companion owns record commit-event lifetime. Stream/reconnect sequencing and rpcId round-trip discipline are exercised directly by behavior specs, and route register/dispose symmetry is audited by the webserver companion. diff --git a/packages/client/connection/README.zh.md b/packages/client/connection/README.zh.md index 1bf0733bd6..246d875586 100644 --- a/packages/client/connection/README.zh.md +++ b/packages/client/connection/README.zh.md @@ -51,7 +51,7 @@ cookie 签名密钥是 `ctx.credentials` 中由 `client-connection/browser-sessi API Gateway Client 把内部 `$events` 逻辑流注册为唯一 generation source,与有无 `$on` 订阅无关。Host 在 API Remotes source factory 同步挂好所有增量 listener 后,先发送唯一 `{ type: 'ready', clientId, host: { home } }` 项,再发送事件。`ConnectionController` 仅在收到该 ready 项后发布 generation 并调用 `onConnected`,因此 baseline 不会跑在增量 listener 前面。 -`$events` 结束、Remote 流报错、收到非 ready 首项或畸形事件项,都会使当前 generation 失效。默认情况下,挂起的握手在 3 秒后记录 Host 响应缓慢告警,在 15 秒后记录就绪超时并中止,包含等待物理 socket 的时间。取消后,source 必须停止投递、释放资源并结束,替换 source 才能启动;已取消 source 迟到的 ready 不能发布 generation。浏览器报告网络可用时,Controller 发布 `connecting`,并在 500ms、1s、2s、4s、8s 与 10s 上限内采用 50%–100% 抖动重试,达到终档后继续尝试直到恢复。每次重试都要求 Gateway 替换一次物理 WebSocket,再重开 `$events`。[持续恢复决策](../../../.agents/notes/implemented/bug-fix/2026-09-05-continuous-client-recovery.zh.md)规定握手期限与重试策略。 +`$events` 结束、Remote 流报错、收到非 ready 首项或畸形事件项,都会使当前 generation 失效。默认情况下,挂起的握手在 3 秒后记录 Host 响应缓慢告警,在 15 秒后记录就绪超时并中止,包含等待物理 socket 的时间。取消后,source 必须停止投递、释放资源并结束,替换 source 才能启动;已取消 source 迟到的 ready 不能发布 generation。浏览器报告网络可用时,Controller 发布 `connecting`,并在 500ms、1s、2s、4s、8s 与 10s 上限内采用 50%–100% 抖动重试,达到终档后继续尝试直到恢复。每次重试都要求 Gateway 替换一次物理 WebSocket,再重开 `$events`。 `ctx.connection.reconnect()` 会中断活动工作、重置序列,并立即开始 retry 1。浏览器 `offline` 会中断活动工作、发布 `disconnected` 并暂停自动尝试;下一次 `online` 转换会重置序列并从 500ms 档开始。只有 ready 项会发布 `connected`。Gateway mux 不拥有独立重试调度。 @@ -85,5 +85,3 @@ API Gateway Client 把内部 `$events` 逻辑流注册为唯一 generation sourc 无。 - -**运行时不变式:** 不发布伴生入口。浏览器会话验证会在请求授权工作时异步读取凭据记录,而记录的 commit-event 生命周期由 credentials 伴生入口负责;流与重连的时序及 rpcId 往返约束由行为规范直接验证,路由注册与 dispose(资源释放)的对称性由 webserver 伴生入口审计。 diff --git a/packages/client/connection/package.json b/packages/client/connection/package.json index ac61c45e70..b233722f10 100644 --- a/packages/client/connection/package.json +++ b/packages/client/connection/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-connection", "description": "Authenticated RPC transport and generation lifecycle", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/file-upload/README.i18n.yaml b/packages/client/file-upload/README.i18n.yaml index 2867fd1467..b8f993bd2d 100644 --- a/packages/client/file-upload/README.i18n.yaml +++ b/packages/client/file-upload/README.i18n.yaml @@ -18,8 +18,8 @@ en: 9d5bbf52599b0d00 zh: 7907542ce1e34549 /deepseek-ai-dsh-client-file-upload/understand-the-implementation: - en: da896ccc5ecbe207 - zh: f434a7572085ee1e + en: 77937960a540cf51 + zh: 52c478bd11200975 /deepseek-ai-dsh-client-file-upload/further-exploration: en: ac94eb9da9ecc296 zh: 25cc3362e2bea330 diff --git a/packages/client/file-upload/README.md b/packages/client/file-upload/README.md index e0c0d7637d..b447226218 100644 --- a/packages/client/file-upload/README.md +++ b/packages/client/file-upload/README.md @@ -44,11 +44,11 @@ The package has no Cordis configuration fields. A `Blob` uses XMLHttpRequest ins The Client plugin provides `ctx.fileUpload`. Its `upload()` method receives the owning Session identity, assembles the raw route request, and invokes the generated Remote for exact bytes. The provider reads the optional pre-Cordis `__DSH_FILE_UPLOAD__` hook once. Without a hook, each Blob or stream request owns a short-lived Worker and releases it after completion, failure, or cancellation. With the hook, the service sends the body through the page-owned Fetch carrier; the Web Worker runtime transfers stream bodies through its request frame and exposes them to the Host HTTP bridge as backpressured chunks. -The Host plugin provides `ctx.fileUploads`. It owns the authenticated streaming route, encoded Remote fallback, command receipt resolver, and staged-receipt lifecycle; encoded admission, attachment-error recognition, and byte storage stay behind `ctx.attachments`. Receipt tables use the receiving Agent's Session object as their key. The Session Controller registers the resolver that can resume a cold ordinary Agent and consumes receipts during prompt admission. Prompt delivery holds each receipt binding in a disposable transaction: disposal restores the previous binding until successful delivery commits it, and queue or history observation then retires the committed receipt. +The Host plugin provides `ctx.fileUploads`. It owns the authenticated streaming route, encoded Remote fallback, command receipt resolver, and staged-receipt lifecycle; encoded admission, attachment-error recognition, and byte storage stay behind `ctx.attachments`. Host callers supply an Agent with its runtime-owned context; receipt tables use that Agent's exact Session object as their key. The Session Controller registers the resolver that can resume a cold ordinary Agent and consumes receipts during prompt admission. Prompt delivery holds each receipt binding in a disposable transaction: disposal restores the previous binding until successful delivery commits it, and queue or history observation then retires the committed receipt. | File | Role | |---|---| -| [`src/index.ts`](src/index.ts) | Host streaming route, attachment-service admission, and Agent-scoped receipt lifecycle | +| [`src/index.ts`](src/index.ts) | Host streaming route, attachment-service admission, and Session-owned receipt lifecycle | | [`src/types.ts`](src/types.ts) | encoded request, receipt, and durable result types | | [`src/client/contract.ts`](src/client/contract.ts) | Client upload, progress, and page-hook types | | [`src/client/runtime.ts`](src/client/runtime.ts) | Dedicated Worker and page-owned carrier implementations | @@ -56,7 +56,7 @@ The Host plugin provides `ctx.fileUploads`. It owns the authenticated streaming -**Runtime invariant:** No companion is published. Each upload receipt belongs to one exact Session, and each request uses one selected carrier. Unsupported stream carriers fail before the body is sent. +Each upload receipt belongs to one exact Session, and each request uses one selected carrier. Unsupported stream carriers fail before the body is sent. ----- diff --git a/packages/client/file-upload/README.zh.md b/packages/client/file-upload/README.zh.md index abdf965694..86bc9ef6f5 100644 --- a/packages/client/file-upload/README.zh.md +++ b/packages/client/file-upload/README.zh.md @@ -44,11 +44,11 @@ kind: "package-reference" Client 插件提供 `ctx.fileUpload`。其 `upload()` 方法接收所属 Session 标识,组装原始路由请求,并为精确字节调用生成的 Remote。提供方只读取一次可选的 Cordis 启动前 `__DSH_FILE_UPLOAD__` 钩子。没有该钩子时,每个 Blob 或 stream 请求拥有一个短期 Worker,并在完成、失败或取消后释放。存在该钩子时,服务通过页面自己提供的 Fetch 载体发送请求体;Web Worker runtime 会通过请求帧转移 stream 请求体,再以带背压的分片形式交给 Host HTTP bridge。 -Host 插件提供 `ctx.fileUploads`。它拥有经过认证的流式路由、编码 Remote 兜底、命令凭证解析器与暂存凭证生命周期;编码准入、附件错误识别与字节存储仍由 `ctx.attachments` 提供。凭证表以接收方 Agent 的 Session 对象为键。Session Controller 注册可恢复休眠普通 Agent 的解析器,并在 prompt 准入时消费凭证。Prompt 投递通过可释放事务持有每个凭证绑定。成功投递提交事务前,释放会恢复原绑定;提交后,队列或历史观察会退休该凭证。 +Host 插件提供 `ctx.fileUploads`。它拥有经过认证的流式路由、编码 Remote 兜底、命令凭证解析器与暂存凭证生命周期;编码准入、附件错误识别与字节存储仍由 `ctx.attachments` 提供。Host 调用方传入具有运行时所属上下文的 Agent;凭证表以该 Agent 的准确 Session 对象为键。Session Controller 注册可恢复休眠普通 Agent 的解析器,并在 prompt 准入时消费凭证。Prompt 投递通过可释放事务持有每个凭证绑定。成功投递提交事务前,释放会恢复原绑定;提交后,队列或历史观察会退休该凭证。 | 文件 | 职责 | |---|---| -| [`src/index.ts`](src/index.ts) | Host 流式路由、附件服务准入与按 Agent scope 管理的凭证生命周期 | +| [`src/index.ts`](src/index.ts) | Host 流式路由、附件服务准入与 Session 所属的凭证生命周期 | | [`src/types.ts`](src/types.ts) | 编码请求、凭证与持久结果类型 | | [`src/client/contract.ts`](src/client/contract.ts) | Client 上传、进度与页面钩子类型 | | [`src/client/runtime.ts`](src/client/runtime.ts) | 专用 Worker 与页面自有载体实现 | @@ -56,7 +56,7 @@ Host 插件提供 `ctx.fileUploads`。它拥有经过认证的流式路由、编 -**运行时不变式:** 不发布伴生入口。每个上传凭证只属于一个准确的 Session,每个请求只使用一个已选定载体。载体不支持的 stream 会在发送请求体前失败。 +每个上传凭证只属于一个准确的 Session,每个请求只使用一个已选定载体。载体不支持的 stream 会在发送请求体前失败。 ----- diff --git a/packages/client/file-upload/package.json b/packages/client/file-upload/package.json index cc924e4e34..eecaa28d28 100644 --- a/packages/client/file-upload/package.json +++ b/packages/client/file-upload/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-file-upload", "description": "Agent-scoped browser file upload, streaming intake, and staged receipt service", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -67,15 +67,13 @@ "@deepseek-ai/dsh-brand": "workspace:*" }, "peerDependencies": { - "@deepseek-ai/cordis": "workspace:~", - "@deepseek-ai/dsh-scope": "workspace:*" + "@deepseek-ai/cordis": "workspace:~" }, "devDependencies": { "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-client-connection": "workspace:*", "@deepseek-ai/dsh-commands": "workspace:*", - "@deepseek-ai/dsh-scope": "workspace:*", "@deepseek-ai/dsh-util-crypto": "workspace:*", "@deepseek-ai/dsh-api-remotes": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", diff --git a/packages/client/file-upload/src/index.ts b/packages/client/file-upload/src/index.ts index 2520ba1225..a784096461 100644 --- a/packages/client/file-upload/src/index.ts +++ b/packages/client/file-upload/src/index.ts @@ -1,4 +1,4 @@ -/** Host file-upload service: streamed intake and Agent-scoped staged receipts. */ +/** Host file-upload service: streamed intake and Session-owned staged receipts. */ import { randomUUID } from 'node:crypto' import type { Context } from '@deepseek-ai/cordis' @@ -6,7 +6,6 @@ import type { Agent } from '@deepseek-ai/dsh-agent' import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment' import type {} from '@deepseek-ai/dsh-client-connection' import type { CommandFileReceiptResolver } from '@deepseek-ai/dsh-commands' -import { scopeOf } from '@deepseek-ai/dsh-scope' import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session' import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol' import { handleFileUploadHttp } from './http-route.ts' @@ -53,7 +52,7 @@ class PromptFileBindingGuard implements PromptFileBinding { } } -/** Host service owning upload storage and Agent-scoped staged receipts. */ +/** Host service owning upload storage and receipts keyed by each receiving Agent's exact Session. */ export class FileUploads extends TypertRemoteService { static inject = ['agents', 'attachments', 'commands', 'connection'] @@ -131,13 +130,12 @@ export class FileUploads extends TypertRemoteService { } /** - * Resolve one staged receipt inside its receiving Agent scope. + * Resolve one staged receipt for the receiving Agent's exact Session. * @param agent - receiving Agent. * @param receiptId - opaque receipt minted for one completed upload. * @returns durable file reference, or `undefined` for an unknown or foreign receipt. */ resolve(agent: Agent, receiptId: FileUploadReceiptId): FileAttachmentRef | undefined { - this.assertAgentScope(agent) return this.stagedFiles.get(agent.session)?.get(receiptId)?.file } @@ -154,7 +152,6 @@ export class FileUploads extends TypertRemoteService { receiptIds: readonly FileUploadReceiptId[], requestId: string, ): PromptFileBinding { - this.assertAgentScope(agent) const staged = this.stagedFiles.get(agent.session) const bound = receiptIds.map((receiptId) => { const upload = staged?.get(receiptId) @@ -176,7 +173,6 @@ export class FileUploads extends TypertRemoteService { * @param requestId - prompt identity carried by the queue occurrence. */ retirePrompt(agent: Agent, requestId: string): void { - this.assertAgentScope(agent) this.retire(agent.session, requestId) } @@ -223,12 +219,7 @@ export class FileUploads extends TypertRemoteService { return resolver(sessionId) } - private assertAgentScope(agent: Agent): void { - if (scopeOf(agent.ctx) !== agent) throw new Error('file-upload: operation requires the Agent\'s own scope') - } - private assertOrdinaryAgent(agent: Agent): void { - this.assertAgentScope(agent) if (agent.session.header.origin === 'subagent') { throw new RemoteError( 'subagent/attachment-invalid' as never, diff --git a/packages/client/file-upload/tsconfig.host.json b/packages/client/file-upload/tsconfig.host.json index 6c8c29bc15..9a719da31d 100644 --- a/packages/client/file-upload/tsconfig.host.json +++ b/packages/client/file-upload/tsconfig.host.json @@ -10,7 +10,6 @@ { "path": "../../../vendor/cordis" }, { "path": "../../util/brand" }, { "path": "../../core/agent" }, - { "path": "../../core/scope" }, { "path": "../../core/session" }, { "path": "../../attachment/attachment" }, { "path": "../../interaction/commands" }, diff --git a/packages/client/hmr/package.json b/packages/client/hmr/package.json index 3b1df23377..373c176123 100644 --- a/packages/client/hmr/package.json +++ b/packages/client/hmr/package.json @@ -1,7 +1,7 @@ { "name": "@deepseek-ai/dsh-client-hmr", "description": "Web client graph synchronization and rebuilt-bundle reload transport", - "version": "0.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -18,10 +18,6 @@ "types": "./lib/types/index.d.ts", "default": "./lib/index.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./client": { "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" @@ -49,12 +45,10 @@ "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-client-modules": "workspace:*", "@deepseek-ai/dsh-host-webserver": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~" }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" ] diff --git a/packages/client/hmr/src/invariant.ts b/packages/client/hmr/src/invariant.ts deleted file mode 100644 index 1054d91430..0000000000 --- a/packages/client/hmr/src/invariant.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Package-owned invariant companion for `@deepseek-ai/dsh-client-hmr`. - * @module @deepseek-ai/dsh-client-hmr/invariant - */ - -import type { Context, Fiber } from '@deepseek-ai/cordis' -import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' - -const PACKAGE_NAME = '@deepseek-ai/dsh-client-hmr' - -/** Cordis companion plugin name. */ -export const name = 'client-hmr-invariant' -/** Service required before the companion can reserve package ownership. */ -export const inject = ['invariants'] - -/** Live fs.watchFile pollers (this package is the composition's only stat-poll user). */ -function statWatchers(): number { - return process.getActiveResourcesInfo().filter(kind => kind === 'StatWatcher').length -} - -/** - * Owned relation: every bundle stat watcher the node half starts must die - * with its fiber — a surviving poller would keep re-hashing bundles for a - * torn-down dev chain forever. Checked as a baseline delta: the StatWatcher - * count observed at fiber creation must be restored once disposal has drained - * the fiber's effects (`internal/plugin` fires at dispose start; the microtask - * hop lets the disposer queue its unload before `fiber.await()` joins it). - * SSE-connection and listener teardown live inside the same ctx.effect - * disposers, so the watcher count is the relation's observable proxy. - */ -const install: InvariantInstaller = (ctx, fail) => { - const baselines = new WeakMap() - // Async listener by design: emitPluginDisposed awaits-and-logs returned - // promises, so a violation surfaces loudly instead of unhandled. - // oxlint-disable-next-line typescript/no-misused-promises - ctx.on('internal/plugin', async (fiber) => { - if (fiber.name !== 'client-hmr') return - if (fiber.uid !== null) { - baselines.set(fiber, statWatchers()) - return - } - const baseline = baselines.get(fiber) - if (baseline === undefined) return - await Promise.resolve() - await fiber.await() - const remaining = statWatchers() - if (remaining > baseline) { - fail(`client-hmr fiber disposed but ${remaining - baseline} bundle stat watcher(s) survived teardown`) - } - }, { global: true }) -} - -/** - * Register this package's invariant companion. - * @param ctx - Cordis context carrying the invariant service. - * @returns the installed registration's disposer after setup succeeds. - */ -export const apply = (ctx: Context): Promise<() => void> => - Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install)) diff --git a/packages/client/hmr/tsconfig.json b/packages/client/hmr/tsconfig.json index 29e15882e6..00226929f0 100644 --- a/packages/client/hmr/tsconfig.json +++ b/packages/client/hmr/tsconfig.json @@ -30,9 +30,6 @@ }, { "path": "../../../vendor/schemastery" - }, - { - "path": "../../runtime-diagnostics/invariants" } ] } diff --git a/packages/client/hmr/tsdown.config.ts b/packages/client/hmr/tsdown.config.ts index 9a28baba7c..69b96b4229 100644 --- a/packages/client/hmr/tsdown.config.ts +++ b/packages/client/hmr/tsdown.config.ts @@ -1,3 +1,3 @@ import { clientBundle } from '../tsdown.client.ts' -export default clientBundle('@deepseek-ai/dsh-client-hmr', ['lib/types/index.js', 'lib/types/invariant.js']) +export default clientBundle('@deepseek-ai/dsh-client-hmr', ['lib/types/index.js']) diff --git a/packages/client/locale/README.i18n.yaml b/packages/client/locale/README.i18n.yaml index 61fd606960..4b363e6971 100644 --- a/packages/client/locale/README.i18n.yaml +++ b/packages/client/locale/README.i18n.yaml @@ -48,8 +48,8 @@ en: a382e032c06ced3f zh: 419cb5f60b075388 /deepseek-ai-dsh-client-locale/further-exploration: - en: b6986484dd8ae2c5 - zh: 003635bfa6ea1b9d + en: f73d411c9e90dead + zh: 8dd05f13e081af88 /deepseek-ai-dsh-client-locale/model-experience: en: 209321ba2d2e6710 zh: 21deb6c973f69b66 @@ -60,5 +60,5 @@ en: 500ede048ef44c6a zh: f741fae55b29a627 /deepseek-ai-dsh-client-locale/known-limitations-and-deferred-work/dev-note: - en: 82fb34d8a128a0ac - zh: fa3f40c900032aae + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/locale/README.md b/packages/client/locale/README.md index a21f61dd93..930c715a47 100644 --- a/packages/client/locale/README.md +++ b/packages/client/locale/README.md @@ -112,7 +112,7 @@ The typed object form requires complete dictionaries for both built-in locales. Read these when the locale contract is not enough: the slot face it implements, the settings surface it rides, and the persistence decision behind the preference. - [Client slot system](../ui-slots/README.md) — the slot model and the `LocaleFace` seat this package implements. -- [Host-backed preferences decision](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md) — why the preference persists in Host settings instead of the browser. +- [historical Host-backed preferences decision](../../../.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.md) — why the preference persists in Host settings instead of the browser. - [Settings group map](../../settings/README.md) — the settings service that stores the preference. - [Client group map](../README.md) — the browser half this package belongs to. @@ -146,5 +146,3 @@ These limits define where localization is incomplete or frozen at registration t None. - -**Runtime invariant:** No companion is published. The locale catalog and dictionaries have no independent runtime source to compare against; registration disposal, preference resolution, and fallback lookup are asserted by behavior specs. diff --git a/packages/client/locale/README.zh.md b/packages/client/locale/README.zh.md index c93b555f96..6298fac88f 100644 --- a/packages/client/locale/README.zh.md +++ b/packages/client/locale/README.zh.md @@ -112,7 +112,7 @@ Host 通过 settings 服务为 loopback 页面持久化偏好。Client 会刻意 当仅阅读 locale 约定不足以解答问题时,请继续阅读以下页面,了解它实现的 slot 接口、依托的设置机制,以及偏好背后的持久化决策。 - [客户端 slot 系统](../ui-slots/README.zh.md)——本包实现的 slot 模型与 `LocaleFace` 席位。 -- [Host 支撑偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md)——偏好为何持久化在 Host 设置中而非浏览器里。 +- [历史Host 支撑偏好决策](../../../.agents/notes/archived/bug-fix/2026-08-06-host-backed-web-preferences.md)——偏好为何持久化在 Host 设置中而非浏览器里。 - [设置组地图](../../settings/README.zh.md)——存储该偏好的设置服务。 - [客户端组地图](../README.zh.md)——本包所属的浏览器半侧。 @@ -146,5 +146,3 @@ Host 通过 settings 服务为 loopback 页面持久化偏好。Client 会刻意 无。 - -**运行时不变式:** 不发布伴生入口。locale catalog 与字典没有可供交叉核对的独立运行时来源;注册释放、偏好解析和 fallback 查找由行为测试覆盖。 diff --git a/packages/client/locale/package.json b/packages/client/locale/package.json index da2661cc55..a1577330a2 100644 --- a/packages/client/locale/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, diff --git a/packages/client/modules/package.json b/packages/client/modules/package.json index da38169d13..497adf5366 100644 --- a/packages/client/modules/package.json +++ b/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.2.0-rc.2", + "version": "0.2.1-alpha.1", "publishConfig": { "access": "public" }, @@ -22,10 +22,6 @@ "types": "./lib/types/client/index.d.ts", "default": "./lib/client.js" }, - "./invariant": { - "types": "./lib/types/invariant.d.ts", - "default": "./lib/invariant.js" - }, "./src/*": "./src/*", "./package.json": "./package.json" }, @@ -44,14 +40,12 @@ "devDependencies": { "@deepseek-ai/cordis-plugin-loader": "workspace:~", "@deepseek-ai/dsh-host-webserver": "workspace:*", - "@deepseek-ai/dsh-invariants": "workspace:*", "@deepseek-ai/cordis": "workspace:~", "@deepseek-ai/dsh-package-manifest": "workspace:*", "@deepseek-ai/dsh-client-store": "workspace:*" }, "files": [ "lib/index.js", - "lib/invariant.js", "lib/client.js", "lib/types/**/*.d.ts" ], diff --git a/packages/client/modules/src/client/system.ts b/packages/client/modules/src/client/system.ts index 29da7c4163..6597f54afc 100644 --- a/packages/client/modules/src/client/system.ts +++ b/packages/client/modules/src/client/system.ts @@ -63,15 +63,20 @@ function chunkUrl(row: BootModuleRow, fileName: string, rev: string): string { return `${url.slice(0, resourceStart)}/${row.id}/${fileName}?${url.slice(revisionStart + 1)}` } +/** Untagged