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/manifest.json b/.agents/notes/archived/manifest.json index 44178cc0ec..a3169679cb 100644 --- a/.agents/notes/archived/manifest.json +++ b/.agents/notes/archived/manifest.json @@ -760,6 +760,9 @@ "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-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-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", @@ -1639,9 +1642,15 @@ "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-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-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-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", "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", @@ -1923,6 +1932,27 @@ "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-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-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-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-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-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-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/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-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-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-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-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-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..fb67084c28 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,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-31-ptc-runtime-python-fd3-protocol.md /agent-note-the-ptc-runtime-python-fd-3-frame-protocol: - en: fc43bcfc72cc4476 - zh: c81279cb0da1481c + en: 6489ddc532174f2d + zh: 942f2b8c966afc88 /agent-note-the-ptc-runtime-python-fd-3-frame-protocol/problem: en: 2c36946bbff637b7 zh: f290de0866ba7caa 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..a9297d39ab 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](../process/2026-09-12-experimental-publication-denylist.md) preserves its experimental status. English | [中文](2026-07-31-ptc-runtime-python-fd3-protocol.zh.md) 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..15cd8830dc 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` 名称发布;[发布策略](../process/2026-09-12-experimental-publication-denylist.zh.md)保留其实验性状态。 [English](2026-07-31-ptc-runtime-python-fd3-protocol.md) | 中文 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..f70a99d94e 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: 3770e2032cf334cb + zh: 6b7e0b350efe00e8 /agent-note-client-derived-presentation-from-raw-session-tool-events/decision/goals-and-non-goals: - en: 4ff7b07cb79bc9de - zh: a74ec6e7ebbede0a + en: 83a4fd5e0f0e72ab + zh: c6b2c75796d36f35 /agent-note-client-derived-presentation-from-raw-session-tool-events/terminology: en: 021106dd41176715 zh: 131caff7bd37251b 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..0659b08aa0 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](../bug-fix/2026-09-05-nested-terminal-cards.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](../bug-fix/2026-09-05-nested-terminal-cards.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 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..03544b6be3 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 卡片修复](../bug-fix/2026-09-05-nested-terminal-cards.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 卡片例外](../bug-fix/2026-09-05-nested-terminal-cards.zh.md)和[记录结果的工具详情](../../../../packages/client/ui-tool/README.zh.md)除外 | | 禁止 | 为兼容保留双写、版本协商或旧 `view` 字段 | ## 术语 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 index d7f7c0dc3b..b2db9b1bfc 100644 --- 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 @@ -9,8 +9,8 @@ en: 7a711c1cafe0be88 zh: 7275b36fdd5094f6 /agent-note-shared-client-control-primitives/decision: - en: cc335d6820b9ab1c - zh: e375bababc0fccdb + en: 9646bb5e9e6cac1e + zh: 2a5ac46dff15cd32 /agent-note-shared-client-control-primitives/finding-the-duplicates: en: f50ed2aea00a34c5 zh: a8a27c1d1e0177a8 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 index db8cb9a8bc..003c5d9066 100644 --- 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 @@ -18,7 +18,7 @@ Neither half of the fix was available to an author. No checklist told them to lo `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). +`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 [StateDot reference](../../../../packages/client/ui-primitives/README.md#component-catalog) documents the current solid-dot palette and rotating `ongoing` loader. **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. 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 index 6d4b04a21b..248258553b 100644 --- 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 @@ -18,7 +18,7 @@ Status: implemented `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)负责。 +`StateDotState` 包含 `idle`,表示一个被跟踪的对象当前没有进行中的活动。插件清单的 `pending` 与 `unloading` 相位需要这个语义成员;没有 `idle`,这两个相位会彻底失去标记。五态联合对消费方保持安全,因为每个包都从自己的闭合状态联合**产生** `StateDotState`,没有任何一个对 `StateDotState` 本身做 switch。当前的纯色点配色与旋转 `ongoing` loading 由 [StateDot 参考](../../../../packages/client/ui-primitives/README.zh.md#component-catalog)说明。 **`ui-primitives` README 里的组件目录,是这条规则得以可用的前提。** 它列出每个导出组件的用途,以及它不适用的场合,并点名三组容易混淆的配对:`Tag` 与 `Pill`、`DisclosureRow` 与卡片式折叠、包内的 `FoldToggle` 与对外导出面。`Pill` 是可选中的胶囊按钮——它接受 `active` 与 `onClick`,用于视图切换与筛选器;`Tag` 是只读徽章,两者都不接受。组件注释使用同一套区分。 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-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-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..d2313c4d65 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: e576063fce1eb551 + zh: 84ead6c3a90f9a8f /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..62c7acfbcf 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](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). ⌘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..cef8acb3cd 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 启动前显示窗口](2026-09-09-desktop-immediate-window-and-direct-start.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/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-10-built-bundle-css-exemption.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.i18n.yaml index 7fc8e02b71..a74f8ba9ef 100644 --- 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 @@ -15,5 +15,5 @@ en: 7f0eb995b7f00d73 zh: aaf7b18f7bb2c20d /agent-note-built-bundle-exemption-for-a-failing-stylesheet/consequences: - en: 767857f0bbd93ca8 - zh: f49bf34e304a7dee + en: 11ebe289fb8766b7 + zh: 10a48ef97aaf50f3 diff --git a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md b/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md index e5a5568969..99374f296a 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md +++ b/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.md @@ -23,5 +23,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/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md index 1c1aab35df..0d24eea2c6 100644 --- a/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-09-10-built-bundle-css-exemption.zh.md @@ -23,5 +23,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/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-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-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-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/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml b/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml index 8862a5bc60..7a0509ae09 100644 --- 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 @@ -15,8 +15,8 @@ en: e7e953783cc4a332 zh: be5d47a503b47170 /agent-note-continuable-subagent-current-turn-interrupt/consequences: - en: 8e75516b07cdc842 - zh: 10e31033e14241e7 + en: 082f68884b650eb4 + zh: 0e2cf340d4eeaf78 /agent-note-continuable-subagent-current-turn-interrupt/testing: en: e46413bc89f9238e zh: 4fb1639ae696e9a4 diff --git a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md b/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md index 3da06ff860..36eac7d0dd 100644 --- a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md +++ b/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md @@ -39,7 +39,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/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md index 0ef423905c..99b32d314f 100644 --- a/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md +++ b/.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md @@ -39,7 +39,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/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..1df337c7de 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: d6f551c2a6887f69 + zh: 831c4c07ea515e93 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..3da364df65 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. 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..0913ef54bc 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 排除。 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..51e19f2909 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: 6f2c0ea18abed2e0 + zh: ebb49b437b9892bc /agent-note-experimental-auto-review-before-each-tool-call/decision/effects-and-authority: en: 55a79d8572fb4332 zh: f63d03d0f3bb5811 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..92c89cbf59 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](../process/2026-09-12-experimental-publication-denylist.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. 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..ed3b218568 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 层,按[实验包发布策略](../process/2026-09-12-experimental-publication-denylist.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)负责。重复调用也重新审查。 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..6c68caab01 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 @@ -21,8 +21,8 @@ en: 78c5b5bf86d0293a zh: 28b8856d88b78d1e /agent-note-right-sidebar-docking-infrastructure/decision/state: - en: 7a73e989059c0871 - zh: ef4f8783fc5f3229 + en: 609475545b060408 + zh: 84da4d1885f6346a /agent-note-right-sidebar-docking-infrastructure/decision/beyond-the-surface: en: 82b5fd9d88c3ea43 zh: d8581e996abbe034 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..d25bd8ab99 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 @@ -39,7 +39,7 @@ The right Sidebar uses one mounted content tree in normal and fullscreen modes; ### 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. 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..abeeef5734 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 @@ -39,7 +39,7 @@ Agent 产出的文件是最尖锐的案例。产出文件 chip 或 `read` 行的 ### 状态 -[默认页](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 作用域的浏览器存储和刷新。布局是呈现状态,永不进入会话日志。 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 index 7c077c2da7..8a6597a206 100644 --- 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 @@ -12,8 +12,8 @@ en: f8ea6a52a404b064 zh: c91528d66487bb33 /agent-note-sidebar-text-preview-and-file-tree/decision/the-guide: - en: cc4eaeefe4de9a02 - zh: bc9e727d6316ae88 + en: a493e3c7fdd4d086 + zh: eb5eada98d80ae9a /agent-note-sidebar-text-preview-and-file-tree/decision/the-text-preview: en: efc84d800d52a69e zh: 0061138d7cd12ae7 diff --git a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md b/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md index e978043545..6d6a460a07 100644 --- a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md +++ b/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.md @@ -16,7 +16,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/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md index 088d4a58c9..8ad534b986 100644 --- a/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md +++ b/.agents/notes/implemented/feature/2026-09-05-sidebar-text-preview-and-file-tree.zh.md @@ -16,7 +16,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/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-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-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-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-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-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-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-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/process/2026-08-10-npm-release-sequences.i18n.yaml b/.agents/notes/implemented/process/2026-08-10-npm-release-sequences.i18n.yaml index 40e0e18b71..334466346a 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 @@ -41,9 +41,6 @@ /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 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..4be7af3fda 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 @@ -138,10 +138,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". 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..63c5fe1304 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 @@ -138,10 +138,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` 这类预发布号已经覆盖「先验证再正式发」。 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-12-experimental-publication-denylist.i18n.yaml b/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.i18n.yaml index 0d6fed02a4..1063af00da 100644 --- 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 @@ -9,11 +9,11 @@ en: c267b50c3004619f zh: 91830d04d2972d46 /agent-note-publish-experimental-packages-with-explicit-private-exceptions/decision: - en: 3cf968834876a655 - zh: 7f7bad8b931a5ed2 + en: 6b598fbf4a92dade + zh: b366764f36eac24b /agent-note-publish-experimental-packages-with-explicit-private-exceptions/alternatives-considered: - en: 61369830fb32e695 - zh: 445eb44e1ae3457a + en: dbd6b872575c00f7 + zh: ba0a3fab83d6d0af /agent-note-publish-experimental-packages-with-explicit-private-exceptions/consequences: en: 23f4d725196f1c2d zh: ebd9455778d8a89f diff --git a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md b/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md index f19faa1f39..d80f72d065 100644 --- a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md +++ b/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.md @@ -10,7 +10,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 +20,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/implemented/process/2026-09-12-experimental-publication-denylist.zh.md index 92a1c6d5cd..2960013e3b 100644 --- a/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.zh.md +++ b/.agents/notes/implemented/process/2026-09-12-experimental-publication-denylist.zh.md @@ -10,7 +10,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 +20,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/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/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-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-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-26-web-conversation-clock-fixture-day.i18n.yaml b/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.i18n.yaml index 8f91e16be8..6152af2b8a 100644 --- 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 @@ -12,8 +12,8 @@ en: ba875c82795d58e1 zh: 13a7df6f8c4e0aa6 /agent-note-web-conversation-clocks-on-the-fixture-day/alternatives-considered: - en: 9c0a2afb1473c1d1 - zh: 36c9e72a70072f5e + en: da60c97e29fb93a9 + zh: 88541f2b47a15978 /agent-note-web-conversation-clocks-on-the-fixture-day/verification: en: 5031ca866fed24dd zh: c11f71af70c1f5ec diff --git a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md b/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md index 8b572d43f3..8e31920549 100644 --- a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md +++ b/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.md @@ -32,7 +32,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/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md index 4631d36b8a..ab8edd7bd8 100644 --- a/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md +++ b/.agents/notes/implemented/testing/2026-09-26-web-conversation-clock-fixture-day.zh.md @@ -32,7 +32,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/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 index f50d88bce9..c004bfb942 100644 --- 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 @@ -12,8 +12,8 @@ en: 34bd113b8c9228fb zh: 99843bf33b703b5e /agent-note-oxlint-contract-cases-that-spawn-a-process-carry-a-90-s-ceiling/alternatives-considered: - en: 2b317170890802f7 - zh: 2a5221dcfa8b9116 + en: 5cb9c66b8bbba3aa + zh: 6d786e4c8cc2b1ca /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-oxlint-contract-spawn-case-budgets.md b/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md index fc4e47fdf8..8500cb4071 100644 --- a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md +++ b/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.md @@ -16,7 +16,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/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md index 2b04cfe058..ceeb10e0ad 100644 --- a/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md +++ b/.agents/notes/implemented/testing/2026-09-27-oxlint-contract-spawn-case-budgets.zh.md @@ -16,7 +16,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/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 index f39a22bba4..0e3d4a5f86 100644 --- 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 @@ -9,8 +9,8 @@ en: 71b72bb5b13530bb zh: b78170e3ac09cccf /agent-note-keep-plugin-install-interception-for-the-page-lifetime/decision: - en: 45d689dced9dad9f - zh: cb89fbbb7937ca8e + en: 09afb1a9f0f62b9e + zh: 005435191fe020ed /agent-note-keep-plugin-install-interception-for-the-page-lifetime/alternatives-considered: en: 1027b59d76cd3ff7 zh: c4c60fb406acb1db diff --git a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md b/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md index 1db162d210..408e22eef3 100644 --- a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md +++ b/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.md @@ -12,7 +12,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/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md index 942a201fc8..4ea3bde7b7 100644 --- a/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md +++ b/.agents/notes/implemented/testing/2026-09-27-plugin-install-interceptor-lifetime.zh.md @@ -12,7 +12,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/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/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/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/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.i18n.yaml similarity index 85% rename from .agents/notes/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml rename to .agents/notes/rejected/process/2026-08-27-port-tool-owned-render.i18n.yaml index 38917672a7..c67668f053 100644 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.i18n.yaml +++ b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.i18n.yaml @@ -1,10 +1,10 @@ # 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 +# pnpm run verify-translation-pairing --write .agents/notes/rejected/process/2026-08-27-port-tool-owned-render.md /agent-note-port-tool-owned-render-into-current-dsh-apis: - en: a13180eec1968793 - zh: e47a83ddb329de88 + en: 8e39c9ea745b6680 + zh: 8e26aeaa94769834 /agent-note-port-tool-owned-render-into-current-dsh-apis/problem: en: 3a3ceea534efabd1 zh: f13f8531d1819b8a diff --git a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.md similarity index 95% rename from .agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md rename to .agents/notes/rejected/process/2026-08-27-port-tool-owned-render.md index 4ab27142fd..f14f18efed 100644 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.md +++ b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.md @@ -1,6 +1,6 @@ # Agent Note: Port tool-owned render into current DSH APIs -Status: proposed +Status: rejected — ui-tool already owns the raw-event cards; a separate port package would duplicate shipped presentation English | [中文](2026-08-27-port-tool-owned-render.zh.md) diff --git a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.zh.md similarity index 94% rename from .agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md rename to .agents/notes/rejected/process/2026-08-27-port-tool-owned-render.zh.md index fe8a018235..389362ede3 100644 --- a/.agents/notes/proposed/process/2026-08-27-port-tool-owned-render.zh.md +++ b/.agents/notes/rejected/process/2026-08-27-port-tool-owned-render.zh.md @@ -1,6 +1,6 @@ # Agent Note: 把 tool-owned render 移植到当前 DSH API -Status: proposed +Status: rejected — ui-tool already owns the raw-event cards; a separate port package would duplicate shipped presentation [English](2026-08-27-port-tool-owned-render.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-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/apps/web/tests/README.i18n.yaml b/apps/web/tests/README.i18n.yaml index 1c111b2040..0b302db6f3 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: 263a34ba5c63e316 + zh: ac1b79f39dde721f /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..0fa47d231e 100644 --- a/apps/web/tests/README.md +++ b/apps/web/tests/README.md @@ -16,7 +16,7 @@ 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. 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..782ee743ac 100644 --- a/apps/web/tests/README.zh.md +++ b/apps/web/tests/README.zh.md @@ -16,7 +16,7 @@ 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)负责这些同步规则。 显式滚动使用 `support.ts` 的 `scrollIntoView`:旧元素脱离 DOM 时重新解析 locator,并在同一个浏览器任务中检查连接状态、执行原生滚动。各场景保留滚动后的可见性与几何断言。 diff --git a/docs/web-styling.i18n.yaml b/docs/web-styling.i18n.yaml index 277ae53c32..ece43aabb4 100644 --- a/docs/web-styling.i18n.yaml +++ b/docs/web-styling.i18n.yaml @@ -9,8 +9,8 @@ en: dc8c41fbf9869179 zh: e45976a585cb9c86 /web-ui-style-reference/component-rules: - en: e6072b2fcbf69de6 - zh: bc9ed31961812451 + en: c55bc2d484435164 + zh: 460105a95a38082f /web-ui-style-reference/corner-radii-and-settings-cards: en: 118972f6c2bd6ce4 zh: 85f8416b03e4dd8b diff --git a/docs/web-styling.md b/docs/web-styling.md index 12f036a764..855f7150ff 100644 --- a/docs/web-styling.md +++ b/docs/web-styling.md @@ -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#understand-the-implementation)). 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 diff --git a/docs/web-styling.zh.md b/docs/web-styling.zh.md index 1d1238cb39..1316cb5e2b 100644 --- a/docs/web-styling.zh.md +++ b/docs/web-styling.zh.md @@ -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#understand-the-implementation))。带文字的锚点另以 ui-primitives 的 `LinkIconMedium` 分类图形(随 `currentColor`)作前置;目的地是已知外部站点时改用该站点自己的标记而非地球;workflow 成员链接与只包图片的锚点不带图形,工具行文件链接保持其灰色点线示能。 ## 圆角与设置卡片 diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml index a4822e73a4..18c40a2d42 100644 --- a/packages/client/ui-conversation/README.i18n.yaml +++ b/packages/client/ui-conversation/README.i18n.yaml @@ -18,8 +18,8 @@ en: 13ae68865fa315d7 zh: c152f020abf20f35 /deepseek-ai-dsh-client-ui-conversation/shell-and-standard-props: - en: dfccf6560c8022d4 - zh: 42e07e8c57f85a82 + en: a2492c829005287d + zh: fac283513b835abd /deepseek-ai-dsh-client-ui-conversation/temporary-composer-entries: en: bb1244310f5c0382 zh: 824b84077432b559 diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md index 4ad3bd853b..32a044c931 100644 --- a/packages/client/ui-conversation/README.md +++ b/packages/client/ui-conversation/README.md @@ -84,7 +84,7 @@ Default sends commit optimistically: Enter clears the draft, occurrence table, a Queued submission echoes show “Sending…” beside disabled edit, remove, and steer buttons; a collapsed dock keeps the sending status in its header. A matching Host queue row replaces the echo and enables each action according to its normal text-content and running-state requirements. Prompt acknowledgement alone does not enable queue actions. A failed submission removes its echo and displays an error; the composer restores the failed draft when it is empty or still contains the previous automatic restoration, preserving subsequently typed text. -Disabled Send and Stop buttons suppress their tooltips, including a Stop button that becomes a disabled Send button when the turn ends. While a normal composer is running, its primary pointer action remains Stop when the draft is empty or input is unavailable. Actionable text or attachments switch the same seat to Send; clearing or successfully submitting the draft restores Stop. The busy-Enter setting selects the Queue or Steer delivery for ordinary Sessions and continuable children, and the running Send button delivers through the same mode plain Enter resolves to; while it is enabled (no upload pending) over a plain message draft its label names that mode (Queue message or Steer message), so the setting governs Enter and the button together while Cmd/Ctrl+Enter still uses the other mode, and idle sessions, empty drafts, and `/` command lines keep the plain Send label ([decision](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md)). Their QueueDock rows share Edit, Remove, and Steer, and an empty draft shares the steer-all chord. One-shot children remain read-only. Plan mode and active goals do not change attachment intake. Continuable children keep separate Send and Stop actions but expose no File row, paste, or drop intake; if their parent is offline, Send and the composer gestures lock while QueueDock controls for the live inbox remain available ([decisions](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md), [inbox controls](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md)). +Disabled Send and Stop buttons suppress their tooltips, including a Stop button that becomes a disabled Send button when the turn ends. While a normal composer is running, its primary pointer action remains Stop when the draft is empty or input is unavailable. Actionable text or attachments switch the same seat to Send; clearing or successfully submitting the draft restores Stop. The busy-Enter setting selects the Queue or Steer delivery for ordinary Sessions and continuable children, and the running Send button delivers through the same mode plain Enter resolves to; while it is enabled (no upload pending) over a plain message draft its label names that mode (Queue message or Steer message), so the setting governs Enter and the button together while Cmd/Ctrl+Enter still uses the other mode, and idle sessions, empty drafts, and `/` command lines keep the plain Send label. Their QueueDock rows share Edit, Remove, and Steer, and an empty draft shares the steer-all chord. One-shot children remain read-only. Plan mode and active goals do not change attachment intake. Continuable children keep separate Send and Stop actions but expose no File row, paste, or drop intake; if their parent is offline, Send and the composer gestures lock while QueueDock controls for the live inbox remain available ([decisions](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md), [inbox controls](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md)). File chips and editable skill references share a whole-reference hover background and follow the composer's line height and text baseline. The first click delegates preview opening to the registered reference source immediately, including the first click of a double-click sequence. Subsequent clicks retain native text selection; an existing noncollapsed selection suppresses pointer preview activation. Previewing does not change the draft, its clipboard projection, or submission. diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md index b7e0be6157..0a7b89b26f 100644 --- a/packages/client/ui-conversation/README.zh.md +++ b/packages/client/ui-conversation/README.zh.md @@ -84,7 +84,7 @@ Session 首次绑定或缓存的 Session 成为 current 时,shell 会在渲染 排队提交的本地回显在禁用的编辑、删除、插话按钮旁显示“发送中…”;折叠后的队列在标题栏保留发送状态。匹配的 Host 队列行替换回显后,各操作按原有的纯文本内容和运行状态要求启用。仅收到提示词确认不会启用队列操作。提交失败会移除回显并显示错误;输入框为空或仍保留上一次自动恢复的内容时,composer 恢复失败草稿,保留用户随后输入的文字。 -Send 和 Stop 按钮禁用时不显示提示气泡,轮次结束后由 Stop 切换成禁用 Send 的按钮也遵循此规则。普通 composer 运行时,如果草稿为空或输入不可用,主指针操作保持为 Stop。可提交的文字或附件会把同一位置切换为 Send;清空或成功提交草稿后恢复 Stop。繁忙态 Enter 设置为普通 Session 与可继续 child 选择 Queue 或 Steer 投递,运行中的 Send 按钮按 plain Enter 解析出的同一模式投递;当它在普通消息草稿上可用(没有待上传文件)时,其标签以该模式命名(排队发送或插话发送),因此该设置同时约束 Enter 与按钮,而 Cmd/Ctrl+Enter 仍使用另一模式;空闲会话、空草稿与 `/` 命令行保留普通的 Send 标签([决策](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md))。它们的 QueueDock 行共享 Edit、Remove 与 Steer,空草稿也共享 steer-all 组合键。One-shot child 继续只读。Plan Mode 与 active goal 不改变附件入口。可继续 child 保留独立的 Send 与 Stop 操作,但不提供「文件」菜单项、粘贴或拖放入口;parent 离线时,Send 与 composer 手势锁定,但在线 inbox 的 QueueDock 控制仍可使用([决策](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md)、[inbox 控制](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md))。 +Send 和 Stop 按钮禁用时不显示提示气泡,轮次结束后由 Stop 切换成禁用 Send 的按钮也遵循此规则。普通 composer 运行时,如果草稿为空或输入不可用,主指针操作保持为 Stop。可提交的文字或附件会把同一位置切换为 Send;清空或成功提交草稿后恢复 Stop。繁忙态 Enter 设置为普通 Session 与可继续 child 选择 Queue 或 Steer 投递,运行中的 Send 按钮按 plain Enter 解析出的同一模式投递;当它在普通消息草稿上可用(没有待上传文件)时,其标签以该模式命名(排队发送或插话发送),因此该设置同时约束 Enter 与按钮,而 Cmd/Ctrl+Enter 仍使用另一模式;空闲会话、空草稿与 `/` 命令行保留普通的 Send 标签。它们的 QueueDock 行共享 Edit、Remove 与 Steer,空草稿也共享 steer-all 组合键。One-shot child 继续只读。Plan Mode 与 active goal 不改变附件入口。可继续 child 保留独立的 Send 与 Stop 操作,但不提供「文件」菜单项、粘贴或拖放入口;parent 离线时,Send 与 composer 手势锁定,但在线 inbox 的 QueueDock 控制仍可使用([决策](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md)、[inbox 控制](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md))。 文件标签和可编辑的 skill 引用共用覆盖整个引用的悬停背景,并跟随输入框的行高与文字基线。首次点击立即由已注册的引用来源负责打开预览,包括双击序列的第一次点击。后续点击保留原生文本选择行为;已有非折叠选区时,指针点击不打开预览。预览不改变草稿、剪贴板文本或提交内容。 diff --git a/packages/client/ui-theme/README.i18n.yaml b/packages/client/ui-theme/README.i18n.yaml index c3ab1c7cdc..3d07b3fa11 100644 --- a/packages/client/ui-theme/README.i18n.yaml +++ b/packages/client/ui-theme/README.i18n.yaml @@ -30,8 +30,8 @@ en: 687ff0a8bccc9c0c zh: 463d6b7afd4bd4a4 /deepseek-ai-dsh-client-ui-theme/understand-the-implementation/stylesheets: - en: 4bbf1beae9e25e9c - zh: 6494ac4d34b1fabb + en: a6f0aaf2d24a913b + zh: 4116ea5685953a9c /deepseek-ai-dsh-client-ui-theme/understand-the-implementation/scrollbar-rebinding: en: bf76e7c34a331595 zh: 5accd0260ef7c61e diff --git a/packages/client/ui-theme/README.md b/packages/client/ui-theme/README.md index 38f1ac3372..4452e852ba 100644 --- a/packages/client/ui-theme/README.md +++ b/packages/client/ui-theme/README.md @@ -75,7 +75,7 @@ The `--dsw-alias-turn-trigger-*` tokens provide separate resting and hover backg `corner-shape.css` smooths every rounded corner: inside `@supports (corner-shape: superellipse(1.5))` it defines `--dsw-corner-shape` and applies it to all elements and their `::before`/`::after` through the universal selector, so engines without `corner-shape` keep circular corners. Full-round shapes — `border-radius: 50%` circles and pill radii — pair `corner-shape: round` with their radius in the owning component sheet because a superellipse deforms them; the corner-shape stylesheet spec enforces that pairing across every package stylesheet. -`gradient-shadow-text.css` derives `--dsh-content-font-delta` from `--dsh-content-font-size` and shifts the Markdown heading and base-text ladder by that increment. It also derives the secondary tier `--dsh-content-font-size-secondary` (setting −1 at ≤14, setting −2 above; 13px at the default) with its own `--dsh-content-font-delta-secondary` for the table variants and the flow rows one step under the body. Dense small and code variants stay fixed. Outside the ladder, the user bubble and composer draft read the body pair directly, and flow-row titles and summaries read the secondary pair. The sheet also owns the shadow scale (`--dsw-shadow-lv*`), the translucent-menu `--dsw-menu-backdrop-filter`, and the elevation tokens: `--dsw-elevation-stroke` draws a 0.5px hairline through the rebindable `--dsw-elevation-stroke-color`, and `--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft` (the composer's larger-blur, lower-alpha tier) layer two faint soft shadows over that stroke, so elevated surfaces set `border: 0` and carry no layout-consuming outline; the derived tokens are re-declared per element so a surface's stroke-color rebind takes effect. An elevated surface that paints `--dsw-specific-menu` also applies `backdrop-filter: var(--dsw-menu-backdrop-filter)` ([decision](../../../.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.md)). Dark menus use a 45%-opaque gray fill and the `border-l3` stroke; light menus retain their `border-l1` stroke. +`gradient-shadow-text.css` derives `--dsh-content-font-delta` from `--dsh-content-font-size` and shifts the Markdown heading and base-text ladder by that increment. It also derives the secondary tier `--dsh-content-font-size-secondary` (setting −1 at ≤14, setting −2 above; 13px at the default) with its own `--dsh-content-font-delta-secondary` for the table variants and the flow rows one step under the body. Dense small and code variants stay fixed. Outside the ladder, the user bubble and composer draft read the body pair directly, and flow-row titles and summaries read the secondary pair. The sheet also owns the shadow scale (`--dsw-shadow-lv*`), the translucent-menu `--dsw-menu-backdrop-filter`, and the elevation tokens: `--dsw-elevation-stroke` draws a 0.5px hairline through the rebindable `--dsw-elevation-stroke-color`, and `--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft` (the composer's larger-blur, lower-alpha tier) layer two faint soft shadows over that stroke, so elevated surfaces set `border: 0` and carry no layout-consuming outline; the derived tokens are re-declared per element so a surface's stroke-color rebind takes effect. An elevated surface that paints `--dsw-specific-menu` also applies `backdrop-filter: var(--dsw-menu-backdrop-filter)` ([styling reference](../../../docs/web-styling.md#component-rules)). Dark menus use a 45%-opaque gray fill and the `border-l3` stroke; light menus retain their `border-l1` stroke. `brand-font.css` references the bundled `montserrat-regular.woff2` / `montserrat-light.woff2` / `montserrat-medium.woff2`, Montserrat Regular, Light, and Medium under the SIL Open Font License shipped with the stylesheet and WOFF2 under `lib/styles/`. `--dsw-font-family-brand` selects this face for brand text; ordinary UI keeps the system font stack. The source is Google Fonts' Montserrat distribution. The Web entry imports the package's `./brand-font.css` export so Vite emits and resolves the font asset; the Web build also includes its license. The Web application, including Desktop onboarding, loads the font offline. The native credential welcome retains its system font. diff --git a/packages/client/ui-theme/README.zh.md b/packages/client/ui-theme/README.zh.md index 5a345a4e8e..225251dde3 100644 --- a/packages/client/ui-theme/README.zh.md +++ b/packages/client/ui-theme/README.zh.md @@ -75,7 +75,7 @@ kind: "package-reference" `corner-shape.css` 平滑所有圆角:在 `@supports (corner-shape: superellipse(1.5))` 内定义 `--dsw-corner-shape`,并通过通配选择器应用到所有元素及其 `::before`/`::after`,因此不支持 `corner-shape` 的引擎保持普通圆弧。正圆形状——`border-radius: 50%` 的圆与胶囊半径——因超级椭圆会使其变形,须在所属组件样式表中把 `corner-shape: round` 与半径声明配对;corner-shape 样式表 spec 跨全部包样式表强制这一配对。 -`gradient-shadow-text.css` 从 `--dsh-content-font-size` 派生 `--dsh-content-font-delta`,并以该增量移动 Markdown 标题与基础文本阶梯。它同时派生低一档变量 `--dsh-content-font-size-secondary`(设置 ≤14 时为设置值 −1,>14 时为设置值 −2;默认设置下为 13 px)及配套的 `--dsh-content-font-delta-secondary`,供表格变体与比正文低一档的流内行使用。紧凑的小号文本与代码变体保持固定字号。阶梯之外,用户气泡与 composer 草稿直接读取正文字号变量对,流内行的标题及摘要读取低一档变量对。该表还持有阴影阶(`--dsw-shadow-lv*`)、半透明菜单使用的 `--dsw-menu-backdrop-filter` 与 elevation token:`--dsw-elevation-stroke` 经可重绑的 `--dsw-elevation-stroke-color` 画 0.5 px 发丝描边,`--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft`(composer 专用的更大模糊、更低透明度档)在描边之上叠两层极淡柔光,因此高层级表面设 `border: 0`,不会产生占布局的轮廓;派生 token 逐元素重声明,使表面对描边色的重绑真实生效。绘制 `--dsw-specific-menu` 的高层级表面还会应用 `backdrop-filter: var(--dsw-menu-backdrop-filter)`([决定](../../../.agents/notes/implemented/feature/2026-09-17-compact-translucent-menu-surfaces.zh.md))。 深色菜单使用不透明度为 45% 的灰色底与 `border-l3` 描边;浅色菜单保留 `border-l1` 描边。 +`gradient-shadow-text.css` 从 `--dsh-content-font-size` 派生 `--dsh-content-font-delta`,并以该增量移动 Markdown 标题与基础文本阶梯。它同时派生低一档变量 `--dsh-content-font-size-secondary`(设置 ≤14 时为设置值 −1,>14 时为设置值 −2;默认设置下为 13 px)及配套的 `--dsh-content-font-delta-secondary`,供表格变体与比正文低一档的流内行使用。紧凑的小号文本与代码变体保持固定字号。阶梯之外,用户气泡与 composer 草稿直接读取正文字号变量对,流内行的标题及摘要读取低一档变量对。该表还持有阴影阶(`--dsw-shadow-lv*`)、半透明菜单使用的 `--dsw-menu-backdrop-filter` 与 elevation token:`--dsw-elevation-stroke` 经可重绑的 `--dsw-elevation-stroke-color` 画 0.5 px 发丝描边,`--dsw-elevation-panel`/`--dsw-elevation-prominent`/`--dsw-elevation-soft`(composer 专用的更大模糊、更低透明度档)在描边之上叠两层极淡柔光,因此高层级表面设 `border: 0`,不会产生占布局的轮廓;派生 token 逐元素重声明,使表面对描边色的重绑真实生效。绘制 `--dsw-specific-menu` 的高层级表面还会应用 `backdrop-filter: var(--dsw-menu-backdrop-filter)`([样式参考](../../../docs/web-styling.zh.md#component-rules))。 深色菜单使用不透明度为 45% 的灰色底与 `border-l3` 描边;浅色菜单保留 `border-l1` 描边。 `brand-font.css` 引用随包提供的 `montserrat-regular.woff2` / `montserrat-light.woff2` / `montserrat-medium.woff2`,其中包含 Montserrat Regular、Light 和 Medium 字体,SIL Open Font License 与样式表和 WOFF2 一同随包保存在 `lib/styles/`。`--dsw-font-family-brand` 为品牌文字选择该字体,普通界面仍使用系统字体栈。源文件来自 Google Fonts 的 Montserrat 发布。Web 入口导入包的 `./brand-font.css` 导出,由 Vite 输出并解析字体资源,Web 构建也包含其许可。Web 应用(包括 Desktop 引导)可离线加载字体;原生凭证欢迎页保留系统字体。 diff --git a/packages/client/ui-tool/README.i18n.yaml b/packages/client/ui-tool/README.i18n.yaml index 0dacd90707..a58dc17c71 100644 --- a/packages/client/ui-tool/README.i18n.yaml +++ b/packages/client/ui-tool/README.i18n.yaml @@ -30,8 +30,8 @@ en: 2959b10b01e08a7b zh: 7084ca22650cbb5b /deepseek-ai-dsh-client-ui-tool/understand-the-implementation/cards: - en: 936ae18b0927971f - zh: 170fb021d250fb58 + en: d202f30c846543d2 + zh: a84656956c66a574 /deepseek-ai-dsh-client-ui-tool/understand-the-implementation/declared-optional-capabilities: en: c1926deb4e5f3734 zh: c5845f465b94a459 diff --git a/packages/client/ui-tool/README.md b/packages/client/ui-tool/README.md index 5f4243229e..c54b9c8da5 100644 --- a/packages/client/ui-tool/README.md +++ b/packages/client/ui-tool/README.md @@ -83,7 +83,7 @@ Chat diff cards keep nine rows before folding, enough for a file header, one rem An Auto denial takes precedence over keyed specialized views. Its generic row preserves the call identity, omits raw arguments, and normalizes the stored reason only for display: trim surrounding whitespace and collapse line separators to spaces, with localized fallback for an empty result. Session and SDK error details keep the original reason. -Recorded tool details cover goal and schedule tools, Cordis inspection, workflow and Ralph reports, Session event/search/trace queries, agent and teammate controls, background jobs, persistent terminals, and LSP navigation. These expanded bodies read successful logged results, preserve generic input/output for failures or unsupported data, and keep Inspect available. Dates include the viewer's time zone, and statuses reflect the call result rather than current session state. Session traces preserve descendant indentation. LSP results open filesystem paths through the Host callback and display other URIs as text. The browser adapter consumes recorded producer text and JSON; Host service objects and presenter callbacks do not cross into the Client. [Compact tool details](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.md) records the presentation trade-offs. +Recorded tool details cover goal and schedule tools, Cordis inspection, workflow and Ralph reports, Session event/search/trace queries, agent and teammate controls, background jobs, persistent terminals, and LSP navigation. These expanded bodies read successful logged results, preserve generic input/output for failures or unsupported data, and keep Inspect available. Dates include the viewer's time zone, and statuses reflect the call result rather than current session state. Session traces preserve descendant indentation. LSP results open filesystem paths through the Host callback and display other URIs as text. The browser adapter consumes recorded producer text and JSON; Host service objects and presenter callbacks do not cross into the Client. Expanded status dots and labels use static semantic colors. Receipt and job-output headers keep neutral text and omit the status while expanded. An interruption receipt confirms only that interruption was requested. diff --git a/packages/client/ui-tool/README.zh.md b/packages/client/ui-tool/README.zh.md index c1b396ee4a..64dcb2823a 100644 --- a/packages/client/ui-tool/README.zh.md +++ b/packages/client/ui-tool/README.zh.md @@ -83,7 +83,7 @@ Chat diff 卡片在折叠前保留九行,足以容纳文件标题、一对删 Auto 拒绝优先于按工具名选择的专门视图。其通用行保留调用身份、省略原始参数,并且只在显示时归一化存储的理由:去除首尾空白,把行分隔符折叠为空格,结果为空时使用本地化通用理由。Session 与 SDK 错误详情保留原始理由。 -记录结果的工具详情覆盖目标和定时任务工具、Cordis 检查、workflow 与 Ralph 报告、Session 事件/搜索/轨迹查询、Agent 与 teammate 控制、后台作业、持久终端以及 LSP 导航。展开内容读取成功的记录结果,为失败或不支持的数据保留通用输入/输出,并保留 Inspect。日期包含查看者的时区,状态反映调用结果而非当前会话状态。Session 轨迹保留后代的缩进。LSP 结果通过 Host 回调打开文件系统路径,其他 URI 则显示为文本。浏览器适配器消费已记录的 producer 文本与 JSON;Host service 对象和 presenter 回调不会进入 Client。[紧凑工具详情](../../../.agents/notes/implemented/architecture/2026-09-10-compact-tool-details.zh.md)记录了呈现取舍。 +记录结果的工具详情覆盖目标和定时任务工具、Cordis 检查、workflow 与 Ralph 报告、Session 事件/搜索/轨迹查询、Agent 与 teammate 控制、后台作业、持久终端以及 LSP 导航。展开内容读取成功的记录结果,为失败或不支持的数据保留通用输入/输出,并保留 Inspect。日期包含查看者的时区,状态反映调用结果而非当前会话状态。Session 轨迹保留后代的缩进。LSP 结果通过 Host 回调打开文件系统路径,其他 URI 则显示为文本。浏览器适配器消费已记录的 producer 文本与 JSON;Host service 对象和 presenter 回调不会进入 Client。 展开后的状态圆点和文字使用静态语义色。操作回执和任务输出的标题保持中性色,展开时省略标题中的状态。中断回执仅确认已发出中断请求。 diff --git a/website/AGENTS.md b/website/AGENTS.md index ba1853f8f0..b4943fdf78 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -20,4 +20,4 @@ Content-page Markdown actions use the projector's `rawMarkdownPath` and the site The default-theme extension shares one fullscreen modal and pan/zoom controller between Mermaid diagrams and standalone content images. Keep enhancements separate from Markdown projection and preserve source elements. Images must be loaded, have nonempty alt text, and occupy their own paragraph; linked, inline, decorative, and `data-no-zoom` images keep their existing interaction. Image views expose intrinsic size through the 100% control. Route, language, theme, and source replacement changes close the active view; theme disposal releases every observer, listener, and scroll lock. The [viewer decision](../.agents/notes/implemented/feature/2026-09-14-docs-mermaid-viewer.md) explains SVG isolation and verification. -Native code groups keep their radio controls in separate forms so copied search excerpts cannot clear the page's selected platform. Search excerpts and MPA builds display every command block because tab switching is unavailable; the [platform-tab decision](../.agents/notes/implemented/feature/2026-09-15-docs-platform-command-tabs.md) owns the interaction and verification. +Native code groups keep their radio controls in separate forms so copied search excerpts cannot clear the page's selected platform. Search excerpts and MPA builds display every command block because tab switching is unavailable; the [code-group adapter](.vitepress/code-groups.ts) and its [focused tests](tests/code-groups.spec.ts) own this behavior.