Merge newer master into skill catalog hot refresh

This commit is contained in:
Tianyi Cui
2026-07-28 01:12:11 +08:00
299 changed files with 8292 additions and 645 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-11-tool-schemas-in-prompt-assembly.md: 3643ac3d61be08f629ef0cd0424fef5cb9696c3a
2026-06-11-tool-schemas-in-prompt-assembly.zh.md: 10389fd7c63755e5b00b3c508fd303a541287f2c
2026-06-11-tool-schemas-in-prompt-assembly.md: 4d5251163969127ad9fd8ed0c64416057713dfa8
2026-06-11-tool-schemas-in-prompt-assembly.zh.md: c04246baa196f50ec1c0dc6f91e9b7b12f67c5e0
@@ -1,6 +1,7 @@
# Agent Note: Tool schemas are part of the system-prompt assembly
Status: implemented
Archived: 2026-07-27
English | [中文](2026-06-11-tool-schemas-in-prompt-assembly.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 工具 schema 是系统提示词组装的一部分
Status: implemented
Archived: 2026-07-27
[English](2026-06-11-tool-schemas-in-prompt-assembly.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-20-package-hierarchy.md: 4e05e3487483ab8d710959c1888ec1f5c3b37432
2026-06-20-package-hierarchy.zh.md: f57704ad082c4961aa48056af1b4b279d2f2c055
2026-06-20-package-hierarchy.md: 43435279cdf4eec544638a5b36c53b4833b4cbbd
2026-06-20-package-hierarchy.zh.md: a44304d071d165cfe8f112e987160dd37626a58a
@@ -1,6 +1,7 @@
# Agent Note: Reorganize packages into a modular hierarchy
Status: implemented
Archived: 2026-07-27
English | [中文](2026-06-20-package-hierarchy.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 将包重组为模块化层级结构
Status: implemented
Archived: 2026-07-27
[English](2026-06-20-package-hierarchy.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-02-result-time-applied-hunk-diffs.md: 55e1612aacd9070ede2f0079c73c30975e1bd5cf
2026-07-02-result-time-applied-hunk-diffs.zh.md: 6fe0032a507af2915bdf79a43578082785411479
2026-07-02-result-time-applied-hunk-diffs.md: 19b41cc441f5cab299dbeded0a9eae25ab7e9f97
2026-07-02-result-time-applied-hunk-diffs.zh.md: b77ec75f0f8af615bde3b95d8fb4620fddedbc9c
@@ -1,6 +1,7 @@
# Agent Note: Result-time applied-hunk diffs for file mutations
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-02-result-time-applied-hunk-diffs.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 结果时刻的 applied-hunk diff 用于文件变更
Status: implemented
Archived: 2026-07-27
[English](2026-07-02-result-time-applied-hunk-diffs.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-25-ask-user-question.md: 51b9a67bfc0fdc88d84a9fa51662d02b127f7925
2026-06-25-ask-user-question.zh.md: 4bd2c2b8664ba085919eb9d4b8c5dee92d4bf72e
2026-06-25-ask-user-question.md: 0d7b2c4d9e8f986366fc56d1123b322333a48262
2026-06-25-ask-user-question.zh.md: fd5c0d3f339074cccb3f1b3b10109afc06b14774
@@ -1,6 +1,7 @@
# Agent Note: Ask-user question capability
Status: implemented
Archived: 2026-07-27
English | [中文](2026-06-25-ask-user-question.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: ask-user 提问能力
Status: implemented
Archived: 2026-07-27
[English](2026-06-25-ask-user-question.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-07-session-prefix.md: df007012165da2da9b7de4bd9ae83534e45a439d
2026-07-07-session-prefix.zh.md: d0c574706352bfaad9d6b462a7866e509976f84e
2026-07-07-session-prefix.md: 8b74e956db42631914a64fe81c1581ff7748a955
2026-07-07-session-prefix.zh.md: c33c4971984f0486ea2302ed739f006ec95a2e5b
@@ -1,11 +1,12 @@
# Agent Note: The session prefix — request-only messages in front of the derived history
Status: implemented
The request-only prefix seam described below was later removed by the [unified sourced-message decision](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md). Current producers inject durable sourced `user/message` context at `agent/step`; this record preserves the earlier design and its trade-offs.
Archived: 2026-07-28
English | [中文](2026-07-07-session-prefix.zh.md)
The request-only prefix seam described below was later removed by the [unified sourced-message decision](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md). Current producers inject durable sourced `user/message` context at `agent/step`; this record preserves the earlier design and its trade-offs.
## Problem
A plugin often owns a session-stable opener the model must always see — a skills catalog, an AGENTS.md digest, a workspace baseline. Before this seam the harness offered two homes, and both are wrong for that content. The system prompt is one rendered string: message-shaped content (a user-role `<system-reminder>` envelope, a multi-message primer) does not fit it, and providers weight conversation messages differently from system text. Durable history (`agent.inject()`, a `context/message` at session start) makes the opener permanent: every `deriveMessages()` consumer replays it, the compaction retention walk owns it, forks bake it in stale, and a resume cannot refresh it — a catalog captured at session birth outlives the world it described.
@@ -1,11 +1,12 @@
# Agent Note: 会话前缀——派生历史之前的仅请求消息
Status: implemented
下文所述的仅请求前缀 seam 后来已被[统一带来源消息的决策](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md)移除。当前的生产方在 `agent/step` 时注入持久的带来源 `user/message` 上下文;本记录保留了早先的设计及其权衡。
Archived: 2026-07-28
[English](2026-07-07-session-prefix.md) | 中文
下文所述的仅请求前缀 seam 后来已被[统一带来源消息的决策](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md)移除。当前的生产方在 `agent/step` 时注入持久的带来源 `user/message` 上下文;本记录保留了早先的设计及其权衡。
## 问题
插件经常拥有一段会话级别稳定的开场内容,模型必须始终看到它:技能目录、AGENTS.md 摘要、工作区基线。在引入本 seam 之前,harness 为这类内容提供了两个归属位置,但两者都不合适。系统提示词是一个渲染后的单一字符串:消息形态的内容(user 角色的 `<system-reminder>` 信封、多消息引导序列)放不进去,而且提供方对会话消息和系统文本的权重处理不同。持久化历史(`agent.inject()`、会话启动时的 `context/message`)使开场内容变为永久:每个 `deriveMessages()` 消费方都会回放它,压缩(compaction)的保留遍历拥有它,fork 会将其以陈旧状态固化,恢复也无法刷新它——会话诞生时捕获的目录会比它所描述的世界活得更久。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-08-repeat-tool-guard.md: 67ec29c6c9fa38bf1d5935c469f3f71b1119dc3a
2026-07-08-repeat-tool-guard.zh.md: 01037f29810c781c33beee414e50412a0c9b0f89
2026-07-08-repeat-tool-guard.md: 218ed381c67a62e3af65ebb375d8800f908414cd
2026-07-08-repeat-tool-guard.zh.md: e522908fb08fe0294cdcac034e301febf0f8683f
@@ -1,6 +1,7 @@
# Agent Note: Repeat-tool-call guard plugin
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-08-repeat-tool-guard.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 重复工具调用守卫插件
Status: implemented
Archived: 2026-07-27
[English](2026-07-08-repeat-tool-guard.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-09-bash-backed-grep-glob-discovery.md: 9c25afb44885ca2519c5d74a3d721b34fe3561de
2026-07-09-bash-backed-grep-glob-discovery.zh.md: d0dee8e49e0a500c87afbd59423fb65416c1dcc8
2026-07-09-bash-backed-grep-glob-discovery.md: 63dacd97443e0dfa44108fec42b5f1cf1640c96f
2026-07-09-bash-backed-grep-glob-discovery.zh.md: 325e27c54ed975522819053863cc57705991fbdb
@@ -1,6 +1,7 @@
# Agent Note: Bash-backed grep and glob discovery tools
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-09-bash-backed-grep-glob-discovery.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 由 Bash 支持的 grep 与 glob 发现工具
Status: implemented
Archived: 2026-07-27
[English](2026-07-09-bash-backed-grep-glob-discovery.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-10-session-query-service.md: 42d12fe2c5e34e71a6166816857b9ced52a61a95
2026-07-10-session-query-service.zh.md: 2c8d322ca6099db1c8ddbb8a02efbc8729e83dbf
2026-07-10-session-query-service.md: 135385a94a8368af93efeb3937d87c86c129915a
2026-07-10-session-query-service.zh.md: 283f0be3fb40a07d494f55aea94f8222be521d9f
@@ -1,6 +1,7 @@
# Agent Note: Exact session query service
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-10-session-query-service.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 精确会话查询服务
Status: implemented
Archived: 2026-07-27
[English](2026-07-10-session-query-service.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-26-code-mode-trajectory-waterfall-spans.md: fe4dcc25dbf211cf69e0d33937cf87a7482852e2
2026-07-26-code-mode-trajectory-waterfall-spans.zh.md: aaae06b1fca1b5587d06aa7704adec421d2b2c27
2026-07-26-code-mode-trajectory-waterfall-spans.md: 2e158878faff29aa1ccd03b9b07a370b10cbb4f8
2026-07-26-code-mode-trajectory-waterfall-spans.zh.md: 462968c9dae66880778b7a5e871a54983402e221
@@ -1,6 +1,7 @@
# Agent Note: Code Mode sub-calls in the trajectory and waterfall views
Status: implemented
Archived: 2026-07-28
English | [中文](2026-07-26-code-mode-trajectory-waterfall-spans.zh.md)
@@ -1,6 +1,7 @@
# Agent Notetrajectory 与 waterfall 视图中的 Code Mode 子调用
# Agent Note: trajectory 与 waterfall 视图中的 Code Mode 子调用
Status: implemented
Archived: 2026-07-28
[English](2026-07-26-code-mode-trajectory-waterfall-spans.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-27-user-message-icon-actions.md: 869e7a2518a3ec927c0689a10816a410dc5f0862
2026-07-27-user-message-icon-actions.zh.md: 353e5ac765bb2fbab9932449cf2247768a1f412f
2026-07-27-user-message-icon-actions.md: 3a6944143972e8e7f6dcc59033c933d112708b63
2026-07-27-user-message-icon-actions.zh.md: da80bd9242940da56271c2639114b78e43cd1041
@@ -1,6 +1,7 @@
# Agent Note: User-message IconActions under the bubble
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-27-user-message-icon-actions.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 用户消息气泡下方的 IconActions
Status: implemented
Archived: 2026-07-27
[English](2026-07-27-user-message-icon-actions.md) | 中文
+67 -1
View File
@@ -4,9 +4,18 @@
"architecture/2026-06-11-custom-schema-dsl.i18n.yaml": "sha256:f05d94c11762e506183044ddb1494a2b200ca16999ef3cef51c7b3a324eec945",
"architecture/2026-06-11-custom-schema-dsl.md": "sha256:71286f2676f8b47d0bd56c6cc43cf8102946e6d195942860a5810b9c534d2b2b",
"architecture/2026-06-11-custom-schema-dsl.zh.md": "sha256:999ff59565a4459184a644c4de6ef98c1bb1a174712e529f5c8417342abdd437",
"architecture/2026-06-11-tool-schemas-in-prompt-assembly.i18n.yaml": "sha256:37230a2f5b9dbe160b4f36b6906065637846261cbef380c3e2b777cf0d8e9013",
"architecture/2026-06-11-tool-schemas-in-prompt-assembly.md": "sha256:6f7b7f15f53f857ccb8477b3fdf65bf2de3a6f96acd93049c03ce4aff1629538",
"architecture/2026-06-11-tool-schemas-in-prompt-assembly.zh.md": "sha256:fd5ddfc53f8a1c4afa86599c858c2ad27858166e676754ee6e2ce0881f55cef7",
"architecture/2026-06-20-extract-example-app-packages.i18n.yaml": "sha256:d99b612cc1051c86d883d74737c72e921735e7a28e0b5e6351d3870c664bdcc4",
"architecture/2026-06-20-extract-example-app-packages.md": "sha256:9c7aca3a1e9a1ccc3729961663bc649b90076e671cae23e3db8203305983ccce",
"architecture/2026-06-20-extract-example-app-packages.zh.md": "sha256:19bd50232d9f25d35aa3f9dc72d9af0df457dd0eaca8b982d5aa625e5b95bcff",
"architecture/2026-06-20-package-hierarchy.i18n.yaml": "sha256:02b9c383c0fc4e9d6cdd2d28b70b73d02ed913861834db8e2a3111ad82e05dee",
"architecture/2026-06-20-package-hierarchy.md": "sha256:a0c6b3022e27f6106996b7aaf5d087b3e3f66f24f7b5fd7b7c12f172ab5dc2f4",
"architecture/2026-06-20-package-hierarchy.zh.md": "sha256:40eec478cc5ff8ae76996e567d1f58309ed71b3bf0068ee06b088deae283512a",
"architecture/2026-07-02-result-time-applied-hunk-diffs.i18n.yaml": "sha256:e26441a73c76fb4217d7458c2772d0cefc09eaef638ea9d302ebcf5d65c42c18",
"architecture/2026-07-02-result-time-applied-hunk-diffs.md": "sha256:537f57877cc2e86b2d3a8a482043db8d7a51880159b1077c5a45b89cd5d0114c",
"architecture/2026-07-02-result-time-applied-hunk-diffs.zh.md": "sha256:7661634cb816caea859287327e37c5536381ee7ad9c77e320ff9b8bb7a7e62ce",
"architecture/2026-07-03-filesystem-directory-listing-seam.i18n.yaml": "sha256:636a822f3240e0401cdddad6a21f3454af1c1593fff14d4c9ce6613495f7dac1",
"architecture/2026-07-03-filesystem-directory-listing-seam.md": "sha256:809a3c79f4d602607e8fa93aafd1ebccf4fae50c31f1fb1b1e386bb7ad089153",
"architecture/2026-07-03-filesystem-directory-listing-seam.zh.md": "sha256:13735cd4c9fe990e6df3b028d6da01da89e94fde454dc0e968e517151cbd4281",
@@ -40,12 +49,27 @@
"feature/2026-06-18-acp-terminal-and-tool-rendering.i18n.yaml": "sha256:79592f96bb25713d01865f37972a6919b2bfb3b66368df0f275bbd59d09ebcf6",
"feature/2026-06-18-acp-terminal-and-tool-rendering.md": "sha256:946d0c580705ef2e7c7ac1897ada074e72f2ec4209c1e7531b6eccf116e9aecc",
"feature/2026-06-18-acp-terminal-and-tool-rendering.zh.md": "sha256:fd815817925a038f79b52b6fab43abdb2d655db3ec07974ce1320ea3674f2afc",
"feature/2026-06-25-ask-user-question.i18n.yaml": "sha256:8ed9d420908e79650a427c82970407c0b8fd49e00e129d2bd0bf3ff3e0c63ed4",
"feature/2026-06-25-ask-user-question.md": "sha256:d9ecff7baec5ca59c61822f7a41e719c17558044b8d23a30a8573e794ed828b7",
"feature/2026-06-25-ask-user-question.zh.md": "sha256:bdeb4869f880d6083563b4e6aefaa601d2fd1de7d46f3a7fa6e0bed376bc34ee",
"feature/2026-06-30-subagent-observe-enrich.i18n.yaml": "sha256:08c2478ba394429f46c1e87a9f055e88704a9000e5d250d5600c0c85124cb17f",
"feature/2026-06-30-subagent-observe-enrich.md": "sha256:0630975c3e325975a932f58a65a178b79c624dc56ebd29e288e96f5a189cfbfa",
"feature/2026-06-30-subagent-observe-enrich.zh.md": "sha256:b9fbb44a7d81f4063faf3baaf97c382a2f5106be533feb4de792ee57b766c1a4",
"feature/2026-07-07-plan-mode.i18n.yaml": "sha256:c59b6a6c218d741cdef8edf625f1d015e409a39411fa64e65200fdebb1c49394",
"feature/2026-07-07-plan-mode.md": "sha256:7bf1bb8e826edf68f0ec919dfd4f66955b935b46b4400d7de85fac3e4663edbc",
"feature/2026-07-07-plan-mode.zh.md": "sha256:5b08cbcd8023f26744e481386177dd0e82423e8b0032829d0dbc22a92cced0cd",
"feature/2026-07-07-session-prefix.i18n.yaml": "sha256:943c50f778521f1800009cebed56d9ec192fda23b5ee9025a4f2b0e4dd8bd729",
"feature/2026-07-07-session-prefix.md": "sha256:1cfd8e46467111b671b0bd9c2370ef07316ff9b2846fecc0cd29389686c6bbf8",
"feature/2026-07-07-session-prefix.zh.md": "sha256:9a15bbed3a53db84ac12e3dfb93b00bf93dabe6b9e39bebfdfc04e00ba8ff135",
"feature/2026-07-08-repeat-tool-guard.i18n.yaml": "sha256:e4382e8d25f23d728f1869c5195723698842dfe5422443a22f21fa8ae9b2c63c",
"feature/2026-07-08-repeat-tool-guard.md": "sha256:95df2c423624c5c56e0b3bb76ced49e91878c7e5096cfef0faf4b20203d8a2aa",
"feature/2026-07-08-repeat-tool-guard.zh.md": "sha256:cbe63d163aa19fc5a63233b05c8f6ed9a8a0ca215cf6f337a3c1e8e48af11b28",
"feature/2026-07-09-bash-backed-grep-glob-discovery.i18n.yaml": "sha256:8eb6ec1312c55b93c2d51c6e54585d6f4d5bf6db444fb9c336681848676e3638",
"feature/2026-07-09-bash-backed-grep-glob-discovery.md": "sha256:908521563012a17d312de664f380444aeafffd401d2200ec97e7bf004bac9ee7",
"feature/2026-07-09-bash-backed-grep-glob-discovery.zh.md": "sha256:52eec49e454357a7e0c3f27c8aaa63840b5822b5d103f49ed48a5c4f25081b00",
"feature/2026-07-10-session-query-service.i18n.yaml": "sha256:5c3f2ce6db1add66b9cdb82d3d115dde0f68762810bdbf7558070b9ff98c371e",
"feature/2026-07-10-session-query-service.md": "sha256:2d7cdb2629856eb16679c576c3d54b9f0468456ddc5be1cbaa4008d4f353033d",
"feature/2026-07-10-session-query-service.zh.md": "sha256:48d5a2d5f679bad90124c19a2b7a563f5d6551274c2bef9b3ce3eb9612ba0649",
"feature/2026-07-14-time-context-plugin.i18n.yaml": "sha256:670c093817c77e093562e02f43984d42ed44ebcced7c91d09366839e412d05e1",
"feature/2026-07-14-time-context-plugin.md": "sha256:618b121da38a8b610bcadaecf121ca823b2c8c13598c012b350c214b82fd238f",
"feature/2026-07-14-time-context-plugin.zh.md": "sha256:1e9eee8ba427a6f2ee08c79e2fcb33c0948e67a80758fdf9f8c9f7dff9aea361",
@@ -91,24 +115,54 @@
"feature/2026-07-24-new-session-clears-to-empty-state.i18n.yaml": "sha256:978638cbf18bc6dce9fea0817654f41cc307f99004a637b85a63ae2208fe9095",
"feature/2026-07-24-new-session-clears-to-empty-state.md": "sha256:b6b71d3883a167056070713e3dffb5046de953bdd218074d17c88e7690e03d83",
"feature/2026-07-24-new-session-clears-to-empty-state.zh.md": "sha256:82a80b48337487029acd05a0137d268f0850f46801fa44a0e62733cacd00d5e9",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.i18n.yaml": "sha256:916525fdc3a12061928380fd8f7b81cd9763a7873663aad564843fccab0ccedc",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.md": "sha256:a822963e4c34c9737681d6d70d8167731d4350e66ef7f356684ad096c04fa7ab",
"feature/2026-07-26-code-mode-trajectory-waterfall-spans.zh.md": "sha256:ef01163adf1245f75cc8291db70d389314dee43b6bff958a9da49b097531218d",
"feature/2026-07-27-user-message-icon-actions.i18n.yaml": "sha256:b33e480f19ec58c8c60417a6c03999953d463ca54606a5ac80ec528edf57c49b",
"feature/2026-07-27-user-message-icon-actions.md": "sha256:b6332e67c6dad0a3fcdb597cec9e4dc32b44ad33665f39c1a50501cf38d3f5ad",
"feature/2026-07-27-user-message-icon-actions.zh.md": "sha256:0fc824eac66a18063f7098e1c395c09b580d5a30b96f2b856608c084212c2ac2",
"process/2026-06-11-doc-sync-enforcement.i18n.yaml": "sha256:33b6d5874427bd7a2bd82e7e2f4f482b12448b2464aef15a9c57975edb48554d",
"process/2026-06-11-doc-sync-enforcement.md": "sha256:aa2fe83d519fc30d48dff19e596e83c8922aacc9e063e14fe2cc35b769b9100e",
"process/2026-06-11-doc-sync-enforcement.zh.md": "sha256:698017bd35f030fdea3eac51df9e43138c48140f504739d687b7251d13fced2b",
"process/2026-06-11-tsdown-over-dumble.i18n.yaml": "sha256:22791adb84a4b6c545173d4f1708eea51151d57e426d875e0e5423be9b6e0212",
"process/2026-06-11-tsdown-over-dumble.md": "sha256:8d3c35dddd8869cc3361059dfe4b7b8ab6716d29dda232c97c2f37e92c841dc0",
"process/2026-06-11-tsdown-over-dumble.zh.md": "sha256:cf11c651c13f5ffef5474e7795006ba3653c5eb08eae75a879be6499353455dc",
"process/2026-07-03-documentation-graph-atlas.i18n.yaml": "sha256:b1e1ed4b7865d87f939dbf8c94c0ea1069fdf7af6fa68f695e6c9d6eccbeb123",
"process/2026-07-03-documentation-graph-atlas.md": "sha256:b62e92bb12123bfa4c4dac806f584aabb6b60af4c5a6a4ab88f84bb9153e766d",
"process/2026-07-03-documentation-graph-atlas.zh.md": "sha256:3485ede4a5e695643bcf9e744a62f8914cff788ae35717dac5eb6bf77e0d65cf",
"process/2026-07-04-cordis-jsdoc-completeness-gate.i18n.yaml": "sha256:92ab8aca8856324acb383927d4500d2f2b3b8ad64d24bd855632035a52671032",
"process/2026-07-04-cordis-jsdoc-completeness-gate.md": "sha256:a9404acbfeaa83f032d17414627b2cce5e280cad8651c6ff078a2ea6af458285",
"process/2026-07-04-cordis-jsdoc-completeness-gate.zh.md": "sha256:e8f4737ecc0925e3dcd60f8319d9cd0fd24de5c7468e1ede4438748d3ad378a1",
"process/2026-07-04-persistence-log-catalog.i18n.yaml": "sha256:23119a0769808279d7caef9e84934cc53b6a86580b703c205fa47ad181a2bf76",
"process/2026-07-04-persistence-log-catalog.md": "sha256:fbb44218b77fd92f7b0cc7af2559200547018ceb5c34aca560371227c8bf5898",
"process/2026-07-04-persistence-log-catalog.zh.md": "sha256:a39ee31bec043e97ad6b7e8433d4c1d24200642049e371ac0167a2092a6e5286",
"process/2026-07-06-generated-config-catalog.i18n.yaml": "sha256:48e64ccfc7622559a9b964e2741ad82a9fa37243a0d5c472f0482d9c748d7739",
"process/2026-07-06-generated-config-catalog.md": "sha256:c8654a4e084bfbf4f7d8eb78239dc1d5d62e1384b2aed309fa2526a4d6de0f04",
"process/2026-07-06-generated-config-catalog.zh.md": "sha256:5ab8d964f12e0cf9a04251e988f0b2a114d7ae8ea7ed337c00f625213cb1dc94",
"process/2026-07-06-parallel-github-ci-gates.i18n.yaml": "sha256:0f6ece268d9a51bc20cb8eb929f26d8838761603a64eb08dc24521198f10da36",
"process/2026-07-06-parallel-github-ci-gates.md": "sha256:6249bd7396ae7f2d0dc671879ce21cefab33a47ace6ef17a25a70e8650b815af",
"process/2026-07-06-parallel-github-ci-gates.zh.md": "sha256:cf7edb9bcf97ab1d4e452330c0df0b127a664509ec3f11597ace3eabeb663a5b",
"process/2026-07-17-run-ci-examples-from-built-lib.i18n.yaml": "sha256:0a2e97095be592e2c603f23d9b3b8959c4556b5636fa2c6e4539be18cfcc7a6c",
"process/2026-07-17-run-ci-examples-from-built-lib.md": "sha256:d7e8901c67e9850b05cef65132f193b8be566be40d0f54b28b11043871955263",
"process/2026-07-17-run-ci-examples-from-built-lib.zh.md": "sha256:9bb720b65a0d5933b5804dfbe0104c831a26695687b7f1a72f4ed4cfb4bd4415",
"process/2026-07-20-generated-cordis-core-api.i18n.yaml": "sha256:4405f216d4beefdbb33f521dc8126be4f8d3a664abfed3a4cdad04fef84ab0f9",
"process/2026-07-20-generated-cordis-core-api.md": "sha256:eed4dde36984f584c211bd00fa88ce4aef9ee296a0814b46a8d759f953a90660",
"process/2026-07-20-generated-cordis-core-api.zh.md": "sha256:74f4316abc78500097f1b25bac06b460cb20a7dd3d5f323da875cc0575148991",
"process/2026-07-21-doc-sync-through-gate-scheduler.i18n.yaml": "sha256:1dbe70d21dd510bec4f2f56ae39d0fdc7290d5648280ca0b67224cd23b3a02a8",
"process/2026-07-21-doc-sync-through-gate-scheduler.md": "sha256:b3eb3f2395ad8f1b77f44aa3fdac79856e5d0b6b4873560d0cc87b63de2ea2e0",
"process/2026-07-21-doc-sync-through-gate-scheduler.zh.md": "sha256:e262e02c3d08057b83b0d29281eadb92723f0fe5b3f54424528f47be137bc760",
"process/2026-07-22-cordis-tutorial-docs.i18n.yaml": "sha256:4a7d80b72fd1d86379937250817459324a71b03385ebd35cd6e5a7c8cdc32dd9",
"process/2026-07-22-cordis-tutorial-docs.md": "sha256:a4332b4357eb1f6f0f576e4f87c0d5697c12c8486dc2d92785556a54f2c7af90",
"process/2026-07-22-cordis-tutorial-docs.zh.md": "sha256:dd9edc0e37abbf6f6975fdf0df5d498bc3589acf23bb8b31d72e3216fd930a62",
"process/2026-07-22-installer-in-repo-skip-clone.i18n.yaml": "sha256:677aa91c3ccd9eda8a658b10410699ac608d3891d2fa32529898a3432fb56660",
"process/2026-07-22-installer-in-repo-skip-clone.md": "sha256:4e30c0dd5429db33638a91a30afdd3386ac1a4705bd259a5eef325b5f86cced8",
"process/2026-07-22-installer-in-repo-skip-clone.zh.md": "sha256:1d93c99f5a8d56077e766242c33245621626be55cf481d01c83bb5cbbe9a74d7",
"process/2026-07-23-browser-demo-gif-recording.i18n.yaml": "sha256:808ccdda39e540645b440e40a2124baed737b98636265d8f6d8cf036a70f0d50",
"process/2026-07-23-browser-demo-gif-recording.md": "sha256:4d3a3dc829c75b66f4f57a6a763b4b9562ce10efb90a19308142f598c5ea8524",
"process/2026-07-23-browser-demo-gif-recording.zh.md": "sha256:409e5d31ea87f35c5227fcd1bd105167f580a8a24d29a223ce390b29eb31639a",
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.i18n.yaml": "sha256:89b6dc255cb0dd9d97ae6f34f37d185412d4260d7bf2f87d6b25e340f01dc26e",
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.md": "sha256:1742e09435ade4a09349c8843eb381e870a2be2d51b74449f08260422c8096b3",
"process/2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md": "sha256:220bd53a88617b09ee8970627c46540fa8951724b102acfe296a7ed7d0f7b5fe",
"simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.i18n.yaml": "sha256:ad3d1263cb0051b885173bf064de62065e2c646ccaae2d7250723da3b4eab90c",
"simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md": "sha256:8fb061d51c8c23b47d2367814bab3623c6d5b972f38d207a273caa9030b579bd",
"simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.zh.md": "sha256:2ffeaca91f82844a5616d6dcce6b4af514bb8a7c46f78e47f668b204ac6edc04",
@@ -118,6 +172,9 @@
"simplification/2026-06-20-prune-dead-seam-methods.i18n.yaml": "sha256:0594648368c942f429599ac0ff5977d62c89c70a31d4bdbac61b0a30fe15ef3b",
"simplification/2026-06-20-prune-dead-seam-methods.md": "sha256:fd3b0eaf600e178eeeef0c6cedc71f2382878733c557f1915d3b47f74a1d0d6d",
"simplification/2026-06-20-prune-dead-seam-methods.zh.md": "sha256:4f5feef9331e3a1346bc362ffb39cfa373db2c609041bfeee6d88a10392464b1",
"simplification/2026-07-02-remove-stream-chunk-mirror.i18n.yaml": "sha256:eef600eafd70a576b2ac16a74f5dd5010ee601376008a90be10f1da56d746cee",
"simplification/2026-07-02-remove-stream-chunk-mirror.md": "sha256:0c22a896260c6eb8991cc8babd8172f7b6889fc41bb891d748f34650b3eee5ec",
"simplification/2026-07-02-remove-stream-chunk-mirror.zh.md": "sha256:ae7a3c2450b16fdf2f8da9e1f83cd987bf387671ecc8ebb76d2e7541695e7ee1",
"simplification/2026-07-04-drop-inert-request-knobs.i18n.yaml": "sha256:e4c992a27ae0e37e5ef663c2cddf55eefe20387fd6103bebf655834d8e75e9db",
"simplification/2026-07-04-drop-inert-request-knobs.md": "sha256:8735c2b868a85b13235e0491a0fa7b9570dd090eef5170324fc5e93782687b67",
"simplification/2026-07-04-drop-inert-request-knobs.zh.md": "sha256:78b243f5d580f2a6fbbdb7d26574295d6ed74feb8d9bba34bbcdf4aa87624b5c",
@@ -163,6 +220,12 @@
"simplification/2026-07-21-tui-todo-write-opt-in.i18n.yaml": "sha256:633975e45444f179e5fcd258d3c4bce924975583505fa97f18cff21861a88ca2",
"simplification/2026-07-21-tui-todo-write-opt-in.md": "sha256:7c4c0818f5cb5b1a506dabb71a56b7d79b811e4b912d492865f1404f4d1ece99",
"simplification/2026-07-21-tui-todo-write-opt-in.zh.md": "sha256:2c121b8ea03182f7854e7d834b07967fdb6790af6a2a38c1d24bb0ca968496ba",
"simplification/2026-07-22-tui-titles-from-session-title-service.i18n.yaml": "sha256:b02b34f76599eef44ef6cbacac36345716bed52eafe8f30e5300d654a93eecbb",
"simplification/2026-07-22-tui-titles-from-session-title-service.md": "sha256:c84b34c2c4700f78e3060e64f49f16625bab002e8211dcd6fce431e7fd076392",
"simplification/2026-07-22-tui-titles-from-session-title-service.zh.md": "sha256:99df66f452004df84efb215e3b58bf73e39af0538ddf038bbed4ac6b65c630ea",
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.i18n.yaml": "sha256:7acf002ea8c1533f052c7bfc0c4e3da013ecf43c5872866a3ee4a8c2691c5e33",
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.md": "sha256:f18a913096b7defd2192c4bac888a33f68075c3662703a0e28a6146897d17777",
"simplification/2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md": "sha256:ff48a37673c97059536fe5b61aff746133eac682145550badb049eb5c83b097c",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.i18n.yaml": "sha256:4177012c0821a8c22499852ecdf096af56d7263cb91c5d9d1bcd552cc26a3e00",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md": "sha256:45234e7cc04b6010c6141f8d5924c04547300098f96262d423c50108e7c7011a",
"testing/2026-06-20-remove-redundant-snapshot-log-expected-output.zh.md": "sha256:15e5a4ad3dee0bb711480cabe45cd97ec37bbdba19c2c2b47d1e9c203b07a48b",
@@ -177,6 +240,9 @@
"testing/2026-07-04-single-source-acp-replay-config.zh.md": "sha256:bed4dcd236a07192dd3de6c76e4a5c47dd5ec35963ce830bd5521bbb41d3f3a3",
"testing/2026-07-06-pin-request-header-content-in-one-scenario.i18n.yaml": "sha256:4f3ebae0faea8a38ffe0d5291a33b3bcf99ed723f8e0cc5cccecbedbf4fb9ce9",
"testing/2026-07-06-pin-request-header-content-in-one-scenario.md": "sha256:050bf8044ce22a27a0f57b5cef84ccff0dc45b1a3f6b70aa41950d41038d0702",
"testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md": "sha256:cac75d4475666239bbe0030b90c0fa7cc66024af5b9f8ef217e53018be64890e"
"testing/2026-07-06-pin-request-header-content-in-one-scenario.zh.md": "sha256:cac75d4475666239bbe0030b90c0fa7cc66024af5b9f8ef217e53018be64890e",
"testing/2026-07-08-shared-acp-snapshot-package.i18n.yaml": "sha256:fc37fcdfe8744f8c8f39eda3f494eed25ee8c4d5322d1f3f671eabd2c5d7026e",
"testing/2026-07-08-shared-acp-snapshot-package.md": "sha256:285b4a3c0b1ef7a837e6713cf0192ddc8a26101f6737fa3923682a9e91350c50",
"testing/2026-07-08-shared-acp-snapshot-package.zh.md": "sha256:02da3f910c2060f70038a0d86a7ddae4a8890905600440e1373412f54fbdcea8"
}
}
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-11-tsdown-over-dumble.md: e8cdaeb1e3331ffb04de024acff5b0e6ca3e6366
2026-06-11-tsdown-over-dumble.zh.md: bb5feef585c1748f41d1b204313f1a4d8b357a17
2026-06-11-tsdown-over-dumble.md: b622fd41b262147e3a42ca590ccb192dd83d0b9f
2026-06-11-tsdown-over-dumble.zh.md: 2200cb8c1776c5f50913ed7e3cfb9a6c305d31e0
@@ -1,6 +1,7 @@
# Agent Note: tsdown for JS bundling instead of dumble
Status: implemented
Archived: 2026-07-27
English | [中文](2026-06-11-tsdown-over-dumble.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 使用 tsdown 替代 dumble 进行 JS 打包
Status: implemented
Archived: 2026-07-27
[English](2026-06-11-tsdown-over-dumble.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-cordis-jsdoc-completeness-gate.md: c7c39986414437ce4d0d4c64f9e25f47485fdf5c
2026-07-04-cordis-jsdoc-completeness-gate.zh.md: f1e7ec824ebae119c1cb27ca4dd9f0d8330dde1a
2026-07-04-cordis-jsdoc-completeness-gate.md: 4b033ab52c8bfe79d1ca9db4baf3db2a533223be
2026-07-04-cordis-jsdoc-completeness-gate.zh.md: f1c1c9976deadaaa700a8a152e4f29a31122ca1a
@@ -1,6 +1,7 @@
# Agent Note: JSDoc completeness gate for the cordis surface
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-04-cordis-jsdoc-completeness-gate.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 针对 Cordis 对外服务接口的 JSDoc 完整性门禁
Status: implemented
Archived: 2026-07-27
[English](2026-07-04-cordis-jsdoc-completeness-gate.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-04-persistence-log-catalog.md: 1529f41b485c1bc8ca029c0a9264574fa7a886a0
2026-07-04-persistence-log-catalog.zh.md: f3f77bb66f8798d953fed16bc79cadadc35c036a
2026-07-04-persistence-log-catalog.md: f000b41c24761fa4914f296d0c2c07e4a26770d6
2026-07-04-persistence-log-catalog.zh.md: 58680e56c5b8f5959baa90318f1747303392c9aa
@@ -1,6 +1,7 @@
# Agent Note: Generated persistence log event catalog
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-04-persistence-log-catalog.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 生成式持久化日志事件目录
Status: implemented
Archived: 2026-07-27
[English](2026-07-04-persistence-log-catalog.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-06-generated-config-catalog.md: f39f5138526d3278e839ee0053d5051bb8bc1c36
2026-07-06-generated-config-catalog.zh.md: 825046914dad8e1a7d87340a310b252f03cbecb9
2026-07-06-generated-config-catalog.md: b7c69f21c0e2152235f57e90f88f670769eb9d3b
2026-07-06-generated-config-catalog.zh.md: 59d632df0219c09d4afac9c859e2ed0a88d62550
@@ -1,6 +1,7 @@
# Agent Note: Generated plugin config catalog
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-06-generated-config-catalog.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 生成式插件配置目录
Status: implemented
Archived: 2026-07-27
[English](2026-07-06-generated-config-catalog.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-17-run-ci-examples-from-built-lib.md: 22f69ed56bfc479281648bfb40df5acbd129ebc0
2026-07-17-run-ci-examples-from-built-lib.zh.md: 74b985f578dd25f785e556c0cd493a7a9292fc43
2026-07-17-run-ci-examples-from-built-lib.md: 3063bdcc8a170713151229a2a4cee22a91f1e8a9
2026-07-17-run-ci-examples-from-built-lib.zh.md: b70367c1d25f4942043a60458b0197288c8094ba
@@ -1,6 +1,7 @@
# Agent Note: Run CI examples from built lib
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-17-run-ci-examples-from-built-lib.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 在 CI 中从构建后的 lib 运行示例
Status: implemented
Archived: 2026-07-27
[English](2026-07-17-run-ci-examples-from-built-lib.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-20-generated-cordis-core-api.md: 848dec2dba6f432c706798c40abe98e8937da651
2026-07-20-generated-cordis-core-api.zh.md: c40a480224f4e1387b71ade9264458cd84403584
2026-07-20-generated-cordis-core-api.md: b6a66e518744354771f25d44d662e2655fa7c758
2026-07-20-generated-cordis-core-api.zh.md: 82dbb308386b7adc678c6e4bf8ef3f06c3b2df77
@@ -1,6 +1,7 @@
# Agent Note: Generate the Cordis core API reference
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-20-generated-cordis-core-api.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 生成 Cordis 核心 API 参考文档
Status: implemented
Archived: 2026-07-27
[English](2026-07-20-generated-cordis-core-api.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-cordis-tutorial-docs.md: 45abb8f524218ce0b2678606ae62c7bf2dbca00b
2026-07-22-cordis-tutorial-docs.zh.md: cd1a62e2a6e7f2e28bc32109dd67746840f8b8b7
2026-07-22-cordis-tutorial-docs.md: a882f3f58fe4be5388bf142424e0e99e140410fc
2026-07-22-cordis-tutorial-docs.zh.md: 4caf03ee05a120584dabef91f6ebafc78594d43a
@@ -1,6 +1,7 @@
# Agent Note: Tutorial-style Cordis docs under docs/cordis-tutorial
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-22-cordis-tutorial-docs.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: `docs/cordis-tutorial` 下的 Cordis 实操教程文档
Status: implemented
Archived: 2026-07-27
[English](2026-07-22-cordis-tutorial-docs.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-26-gui-pr-gif-evidence-and-assets-branch.md: 221df5dc82dfb437261f6a836dcc6c2972f30939
2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md: 861cedb5d15c8e057c2a5f9001453fdc3a3c6757
2026-07-26-gui-pr-gif-evidence-and-assets-branch.md: 18e56e44190e0c44fcf2cae8bf1d66f91ef9a4c7
2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md: 945dd7a0242d9829334ddd79ab569c6723a64185
@@ -1,6 +1,7 @@
# Agent Note: GUI pull request GIF evidence and assets-branch publication
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-26-gui-pr-gif-evidence-and-assets-branch.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: GUI PR 的 GIF 证据与 assets 分支发布
Status: implemented
Archived: 2026-07-27
[English](2026-07-26-gui-pr-gif-evidence-and-assets-branch.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-02-remove-stream-chunk-mirror.md: cc75f71407a3c54d71b11e91cea2eb2658649b5e
2026-07-02-remove-stream-chunk-mirror.zh.md: 47e4c42daedadb09b6f51508af84e593ee8218a9
2026-07-02-remove-stream-chunk-mirror.md: 99086a179dfd1bbaac570e437a66b9be19c05ef4
2026-07-02-remove-stream-chunk-mirror.zh.md: 7a03e476dd8d0ba725d16d220e9aef3a3fc445f6
@@ -1,6 +1,7 @@
# Agent Note: Stop mirroring the token stream as an agent event
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-02-remove-stream-chunk-mirror.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 停止将 token 流镜像为 agent 事件
Status: implemented
Archived: 2026-07-27
[English](2026-07-02-remove-stream-chunk-mirror.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-22-tui-titles-from-session-title-service.md: 04355b9c426af423dec347997f3b8ac62483eb7f
2026-07-22-tui-titles-from-session-title-service.zh.md: 5fc783c5ca08baba12a60f2aa6b5e286307aa9f0
2026-07-22-tui-titles-from-session-title-service.md: 946ef283f91249c70f82280b754ef991665dbaa3
2026-07-22-tui-titles-from-session-title-service.zh.md: f2fa4772c9fa404540dcf8a514cf571a284d4967
@@ -1,6 +1,7 @@
# Agent Note: TUI titles come from the session-title service
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-22-tui-titles-from-session-title-service.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: TUI 标题来自 session-title 服务
Status: implemented
Archived: 2026-07-27
[English](2026-07-22-tui-titles-from-session-title-service.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-26-consolidate-gate-scripts-on-existing-deps.md: 6370c8f92eff7296327e941e698ec4f733100bb2
2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md: 3587c92e0e9655d8b38d24d184c1c68c44b131d4
2026-07-26-consolidate-gate-scripts-on-existing-deps.md: 9ad27ff8b2fe7a68511c01a1125dd01289aa4a7f
2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md: b01133fad22c8d8244d787f8740d2d1c29b2c41e
@@ -1,6 +1,7 @@
# Agent Note: Consolidate gate scripts on already-present deps and builtins
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-26-consolidate-gate-scripts-on-existing-deps.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 把门禁脚本统一到已有依赖与内置模块上
Status: implemented
Archived: 2026-07-27
[English](2026-07-26-consolidate-gate-scripts-on-existing-deps.md) | 中文
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-08-shared-acp-snapshot-package.md: dc86bf020b159a1c4af26bbc49725ce2b7de8180
2026-07-08-shared-acp-snapshot-package.zh.md: 19eb070bbc5aa1a0b72c0cc874225064e92632b8
2026-07-08-shared-acp-snapshot-package.md: 6952f79eff8a764cd296bef98e9cd7b40e59cf01
2026-07-08-shared-acp-snapshot-package.zh.md: 35246fc4f98f616d70e31eb55499cca15f04c94e
@@ -1,6 +1,7 @@
# Agent Note: Extract the ACP snapshot suite into a support package
Status: implemented
Archived: 2026-07-27
English | [中文](2026-07-08-shared-acp-snapshot-package.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 将 ACP 快照套件提取为支持包
Status: implemented
Archived: 2026-07-27
[English](2026-07-08-shared-acp-snapshot-package.md) | 中文
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-06-11-dev-invariants-over-deep-readonly.md: 8f0e79f15af82ce3125b1f6f767d4ea727aa6d29
2026-06-11-dev-invariants-over-deep-readonly.zh.md: 2f787bbd55b5a9a91bc5342351756e45cb0515d3
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md
2026-06-11-dev-invariants-over-deep-readonly.md: f1a741927cf63b43b1aaf558efc148ff80d2d881
2026-06-11-dev-invariants-over-deep-readonly.zh.md: 67439c674f5a77112fc0619c774f7897cf49e835
@@ -40,7 +40,7 @@ When the session companion attaches to an existing or seeded session, it replays
### Pervasive deep-readonly types
[The rejected immutable-public-surfaces proposal](../../rejected/architecture/2026-06-11-immutable-public-surfaces.md) would apply a recursive readonly type across public log and message surfaces. That provides editor feedback but not a runtime guarantee: TypeScript types are erased and plugin code can cast through them. It also pushes readonly types into consumers where mutation is intentional. Runtime ownership at the `Session` boundary protects every caller without that type propagation.
A rejected companion proposal would apply a recursive `DeepReadonly<T>` type across public log and message surfaces, flipping session read paths (`events`, `session/event` listeners, `deriveMessages()`) to deep-readonly while keeping in-flight waterfalls mutable. That provides editor feedback but not a runtime guarantee: TypeScript types are erased and plugin code can cast through them. It also pushes readonly types into consumers where mutation is intentional. Runtime ownership at the `Session` boundary protects every caller without that type propagation.
### Development-only freezing
@@ -40,7 +40,7 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行
### 全面的 deep-readonly 类型
[被否决的不可变公共表面提案](../../rejected/architecture/2026-06-11-immutable-public-surfaces.md)会在公共日志和消息表面上应用递归 readonly 类型。这能提供编辑器反馈,但无法提供运行时保证:TypeScript 类型在运行时被擦除,插件代码可以通过类型转换绕过。它还会将 readonly 类型推入有意进行修改的消费方。在 `Session` 边界处的运行时所有权保护所有调用方,无需这种类型传播。
一个被否决的姊妹提案会在公共日志和消息表面上应用递归 `DeepReadonly<T>` 类型,将会话读取路径(`events``session/event` 监听器、`deriveMessages()`)翻转为深只读,同时保持进行中的 waterfall 可变。这能提供编辑器反馈,但无法提供运行时保证:TypeScript 类型在运行时被擦除,插件代码可以通过类型转换绕过。它还会将 readonly 类型推入有意进行修改的消费方。在 `Session` 边界处的运行时所有权保护所有调用方,无需这种类型传播。
### 仅在开发模式冻结
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-02-tool-render-intent-union.md: 84423e9000526848a111591c1bb2ab92067bbe50
2026-07-02-tool-render-intent-union.zh.md: 43873c622fc8483b4a7033d17b4b4fab1342a56b
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md
2026-07-02-tool-render-intent-union.md: d82141f519bff66df000f1316093aacd38b8e42b
2026-07-02-tool-render-intent-union.zh.md: 71fe81ba8f87cb707512cf7880126c006321c799
@@ -14,7 +14,7 @@ A tool declares how its calls render in a UI (an editor's tool-call card) throug
- Which combinations are *valid* is unwritten: a `terminal` call that also sets `content` means "description above the card"; a generic call that sets `terminal` is meaningless but representable. The type permits nonsense.
- There is no way to express the one file-tool affordance an editor most wants — a **diff card** (`{path, oldText, newText}`, which Zed renders as an inline diff / new-file preview). `ToolCallPresentation.content` is the *LLM* `ContentBlock[]` vocabulary (text/image), so a tool literally cannot ask for a diff.
The existing `FIXME(tool-presentation)` in `packages/core/tools/src/index.ts` named the fix: "redesign the type so a tool declares its render INTENT once (e.g. a tagged union over card kinds) rather than a bag of optional fields the bridge stitches together." The rejected Agent Note [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) deferred it explicitly: rich rendering "should return later as a tagged render-intent union after there are at least two real tools and two real consumers to validate the vocabulary." That bar is met by multiple producer families plus the TUI and host/client-runtime (Web) consumers.
The existing `FIXME(tool-presentation)` in `packages/core/tools/src/index.ts` named the fix: "redesign the type so a tool declares its render INTENT once (e.g. a tagged union over card kinds) rather than a bag of optional fields the bridge stitches together." An earlier rejected collapse-tool-owned-presentation proposal deferred it explicitly: rich rendering "should return later as a tagged render-intent union after there are at least two real tools and two real consumers to validate the vocabulary." That bar is met by multiple producer families plus the TUI and host/client-runtime (Web) consumers.
## Decision
@@ -62,7 +62,7 @@ The terminal intent is display-only. The harness still executes the command thro
## Alternatives considered
- **Delete tool-owned presentation entirely** — [the rejected collapse proposal](../../rejected/simplification/2026-06-20-generic-tool-rendering.md); its own verdict deferred to exactly this union once two real tools and two real consumers existed, and that bar is now met.
- **Delete tool-owned presentation entirely** — the rejected collapse proposal this note supersedes; its own verdict deferred to exactly this union once two real tools and two real consumers existed, and that bar is now met.
- **Let a UI execute terminal intents** — rejected because it would bypass the harness's bash policy and ownership contracts and fork command execution across backends. A terminal card describes harness-owned execution; it never authorizes client-side execution.
- **A merge-extensible union** (the `ContentBlockMap` pattern) — rejected: a new render intent needs new bridge code to render it anyway, so a plugin-added variant the bridge silently drops would be worse than the compile error the closed union raises at the bridge's `assertNever` switch.
- **Keeping the optional-field bag** — the status quo the Problem dissects: invalid states representable, undocumented field interactions, and no way to ask for a diff card at all.
@@ -77,6 +77,6 @@ A new render intent is a compile-breaking change at the bridge switch — delibe
## Related
- Supersedes the deferral in [Collapse tool-owned UI presentation](../../rejected/simplification/2026-06-20-generic-tool-rendering.md) (rejected — "wait for two real tools and two real consumers, then a tagged render-intent union"). That bar is now met; this is that union.
- Extended by [Result-time applied-hunk diffs](2026-07-02-result-time-applied-hunk-diffs.md), which adds a persisted `meta` channel so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card.
- Supersedes the deferral in the earlier rejected collapse-tool-owned-presentation proposal (rejected — "wait for two real tools and two real consumers, then a tagged render-intent union"). That bar is now met; this is that union.
- Extended by [Result-time applied-hunk diffs](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md) (archived), which added a persisted `meta` channel — the value/presentation split and the persisted `presentationMeta` channel are now owned by [the canonical tool output contract](2026-07-20-canonical-tool-output-contract.md) so write/edit emit a result-time `DiffResultView` — the applied change (a contextual hunk with context lines / one per `replace_all` site, or a whole-file diff for a create) — on top of this union's call-time diff card.
- Folds `ToolTerminal` into the tagged `terminal` views used by current UI transports.
@@ -14,7 +14,7 @@ Status: implemented
- 哪些组合是*合法的*没有文档说明:一个设置了 `content``terminal` 调用意味着「卡片上方的描述」;一个设置了 `terminal` 的 generic 调用毫无意义但类型上可表达。类型允许无意义的状态存在。
- 无法表达编辑器最需要的文件工具能力:**diff 卡片**(`{path, oldText, newText}`,Zed 将其渲染为内联 diff / 新文件预览)。`ToolCallPresentation.content` 使用的是 *LLM(大语言模型)*`ContentBlock[]` 词汇(text/image),工具根本无法请求 diff 展示。
`packages/core/tools/src/index.ts` 中已有的 `FIXME(tool-presentation)` 指出了修复方向:「重新设计类型,让工具一次性声明其渲染意图(例如按卡片种类的带标签联合类型),而非一堆由 bridge 拼接的可选字段。」被否决的 Agent Note [折叠工具拥有的 UI 呈现](../../rejected/simplification/2026-06-20-generic-tool-rendering.md)明确推迟了此事:富渲染「应当在至少有两个真实工具和两个真实消费方验证词汇之后,以带标签 render-intent 联合类型的形式回归。」该条件已由多个生产者族,加上 TUI 与宿主/客户端运行时(Web)这些消费方满足。
`packages/core/tools/src/index.ts` 中已有的 `FIXME(tool-presentation)` 指出了修复方向:「重新设计类型,让工具一次性声明其渲染意图(例如按卡片种类的带标签联合类型),而非一堆由 bridge 拼接的可选字段。」一个早先被否决的折叠工具自有呈现提案明确推迟了此事:富渲染「应当在至少有两个真实工具和两个真实消费方验证词汇之后,以带标签 render-intent 联合类型的形式回归。」该条件已由多个生产者族,加上 TUI 与宿主/客户端运行时(Web)这些消费方满足。
## 决策
@@ -62,7 +62,7 @@ terminal 意图只用于展示。harness 仍通过自身的 bash 服务执行命
## 曾考虑的替代方案
- **完全删除工具自有的展示**:即[被否决的 collapse 提案](../../rejected/simplification/2026-06-20-generic-tool-rendering.md);其自身的结论正是推迟到两个真实工具和两个真实消费方存在后再做此联合类型,该条件现已满足。
- **完全删除工具自有的展示**:即本 Agent Note 所取代的那个被否决的 collapse 提案;其自身的结论正是推迟到两个真实工具和两个真实消费方存在后再做此联合类型,该条件现已满足。
- **让 UI 执行 terminal 意图**:否决。这样会绕过 harness 的 bash 策略与归属契约,并把命令执行分裂到不同后端。terminal 卡片描述的是 harness 拥有的执行,绝不授权客户端侧执行。
- **可合并扩展的联合类型**`ContentBlockMap` 模式):否决。新的渲染意图无论如何需要新的 bridge 代码来渲染,因此一个被 bridge 静默丢弃的插件添加变体,比封闭联合类型在 bridge 的 `assertNever` switch 处引发的编译错误更糟糕。
- **保留可选字段集合**:即「问题」一节所剖析的现状:无效状态可表达、字段交互无文档、且完全无法请求 diff 卡片。
@@ -77,6 +77,6 @@ terminal 意图只用于展示。harness 仍通过自身的 bash 服务执行命
## 相关
- 取代[折叠工具拥有的 UI 呈现](../../rejected/simplification/2026-06-20-generic-tool-rendering.md)(已否决——「等两个真实工具和两个真实消费方,然后做带标签 render-intent 联合类型」)中的推迟决定。该条件现已满足;本 Agent Note 即为那个联合类型。
- 被[结果时已应用 hunk 差异](2026-07-02-result-time-applied-hunk-diffs.md)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff),叠加在本联合类型的调用时 diff 卡片之上
- 取代早先被否决的折叠工具自有呈现提案(已否决——「等两个真实工具和两个真实消费方,然后做带标签 render-intent 联合类型」)中的推迟决定。该条件现已满足;本 Agent Note 即为那个联合类型。
- 被[结果时已应用 hunk 差异](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md)(已归档)扩展:后者添加了一个持久化的 `meta` 通道,使 write/edit 在结果时输出 `DiffResultView`(应用后的变更:带上下文行的 contextual hunk / 每个 `replace_all` 位点一个,或创建时的整文件 diff)——值/呈现拆分与持久化的 `presentationMeta` 通道现由[规范工具输出契约](2026-07-20-canonical-tool-output-contract.md)拥有
-`ToolTerminal` 折入当前 UI 传输层使用的带标签 `terminal` 视图。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-15-llm-model-catalog-and-acp-selection.md: adbd8671f0ea5cd2e0c049c32616453882329396
2026-07-15-llm-model-catalog-and-acp-selection.zh.md: dfcb581e43149281cd18b28f1411ea98944983ad
2026-07-15-llm-model-catalog-and-acp-selection.md: 9edc723b0dfafeaf395eb9325373835138ddbc41
2026-07-15-llm-model-catalog-and-acp-selection.zh.md: 677d9e2a200d488fa9fc27fc2a922dc8f1f871d1
@@ -24,7 +24,7 @@ ACP selection must also preserve the provider dimension. The same model id may a
Catalog membership is advisory. It drives selectors and diagnostics but never changes `stream()` routing and never rejects an otherwise valid request. Provider ownership remains exclusive and lifecycle-bound; model ids remain request-time adapter input.
`dsh-llm-pi-ai` maps the configured provider's installed `getModels(provider)` entries into the neutral catalog. Its existing request-time catalog lookup remains authoritative and still rejects unknown models with `UNKNOWN_MODEL`. `dsh-llm-deepseek` accepts an optional `models` config containing display entries, defaulting to `deepseek-v4-flash` and `deepseek-v4-pro`. An explicit list replaces those defaults and an empty list disables discovery. The entries improve selector UX for known public or private models, while every unlisted model id continues to pass through unchanged.
`dsh-llm-pi-ai` maps the configured provider's installed `getModels(provider)` entries into the neutral catalog. Its existing request-time catalog lookup remains authoritative and still rejects unknown models with `UNKNOWN_MODEL`. `dsh-llm-deepseek` accepts an optional `models` config containing display entries, defaulting to `deepseek-v4-flash` named `DeepSeek-V4-Flash` and `deepseek-v4-pro` named `DeepSeek-V4-Pro`. An explicit list replaces those defaults and an empty list disables discovery. The entries improve selector UX for known public or private models, while every unlisted model id continues to pass through unchanged.
### Per-session selection in the front door
@@ -24,7 +24,7 @@ ACP 选择还必须保留提供方维度。同一个模型 ID 可能存在于多
目录成员关系仅提供建议。它驱动选择器与诊断,但不会改变 `stream()` 路由,也不会拒绝原本有效的请求。提供方所有权仍然具有排他性并绑定生命周期;模型 ID 仍是请求时传给适配器的输入。
`dsh-llm-pi-ai` 将已配置提供方的安装目录 `getModels(provider)` 映射为中立目录。其现有请求时目录查询仍是权威依据,未知模型仍以 `UNKNOWN_MODEL` 失败。`dsh-llm-deepseek` 接受可选的 `models` 配置作为展示条目,默认包含 `deepseek-v4-flash` `deepseek-v4-pro`。显式列表会替换这些默认值,空列表则关闭发现。这些条目改善已知公开或私有模型的选择体验,而所有未列出的模型 ID 仍会原样透传。
`dsh-llm-pi-ai` 将已配置提供方的安装目录 `getModels(provider)` 映射为中立目录。其现有请求时目录查询仍是权威依据,未知模型仍以 `UNKNOWN_MODEL` 失败。`dsh-llm-deepseek` 接受可选的 `models` 配置作为展示条目,默认包含名为 `DeepSeek-V4-Flash``deepseek-v4-flash` 和名为 `DeepSeek-V4-Pro` `deepseek-v4-pro`。显式列表会替换这些默认值,空列表则关闭发现。这些条目改善已知公开或私有模型的选择体验,而所有未列出的模型 ID 仍会原样透传。
### 前门内的会话级选择
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-19-gui-web-client-architecture.md: eeae5fb3ad8eb3e9842b497ee51258375760bc93
2026-07-19-gui-web-client-architecture.zh.md: c6f4b10c2a4c2d210c6bd7a7fa1ac470cab7c0a7
2026-07-19-gui-web-client-architecture.md: cfc2a7e62358e6282148b2d024ef3b162a903642
2026-07-19-gui-web-client-architecture.zh.md: b5b082c25f664cfcb0ddd3fcc6c4cd3d58472218
@@ -30,7 +30,7 @@ Both ends run cordis. The host is a cordis plugin tree; the browser runs a secon
## The client cordis tree and the loading chain
The loading chain — the two package kinds (plain vs dshClient plugin), the module-system/plugin-governor split, the two-phase boot over the host-authored entry graph with revisions, and hot reload — is owned by the [client plugin loading RFC](2026-07-23-client-plugin-loading-model.md). The load-bearing facts for this document: the browser boots the same vendored `@cordisjs/plugin-loader` as the host with a client module system (`ctx.modules`, `packages/client/modules`) filling its `internal` seam; every unit with product behavior is an entry in the host-authored `__DSH_BOOT__` graph — all nine plugin packages (infrastructure included) carry the `dshClient` declaration and arrive as fetched `./client` tsdown closure bundles, `immediately` rows differing only in boot phase-one prefetch, while plain packages (react family, cordis, the not-yet-promoted libraries) stay shell-bundled, seeded, and invisible to the graph; bundles execute `window.__ModuleLoader__.load({ id, factory })` and their `require` is answered from the lazy CJS module table (seed words + registered factories, materialized and memoized on first require — cross-plugin value imports are a build error, cooperation goes through cordis services); plugin CSS is inlined in the bundle and injected as `<style data-plugin="<id>">` at materialization (CSS Modules hashing + ownership tag = isolation, removal on reload); hot reload is live in dev graphs — the webserver stat-polls the bundles it serves and broadcasts `rebuilt` SSE frames, and the `client-hmr` plugin swaps one fiber per frame. The settled flip (`loader.await()` + an all-ACTIVE sweep) still switches the shell from the loading page to the real UI in one pass — settled means every entry is created and every fiber reached ACTIVE, with FAILED/PENDING fibers listed loud; there is no partial-availability mode (progressive rendering is deferred work).
The loading chain — the two package kinds (plain vs dshClient plugin), the module-system/plugin-governor split, the two-phase boot over the host-authored entry graph with revisions, and hot reload — is owned by the [client plugin loading RFC](2026-07-23-client-plugin-loading-model.md). The load-bearing facts for this document: the browser boots the same vendored `@cordisjs/plugin-loader` as the host with a client module system (`ctx.modules`, `packages/client/modules`) filling its `internal` seam; every unit with product behavior is an entry in the host-authored `__DSH_BOOT__` graph — every production plugin package (infrastructure included) carries the `dshClient` declaration and arrives as a fetched `./client` tsdown closure bundle, `immediately` rows differing only in boot phase-one prefetch, while plain packages (react family, cordis, the not-yet-promoted libraries) stay shell-bundled, seeded, and invisible to the graph; bundles execute `window.__ModuleLoader__.load({ id, factory })` and their `require` is answered from the lazy CJS module table (seed words + registered factories, materialized and memoized on first require — cross-plugin value imports are a build error, cooperation goes through cordis services); plugin CSS is inlined in the bundle and injected as `<style data-plugin="<id>">` at materialization (CSS Modules hashing + ownership tag = isolation, removal on reload); hot reload is live in dev graphs — the webserver stat-polls the bundles it serves and broadcasts `rebuilt` SSE frames, and the `client-hmr` plugin swaps one fiber per frame. The settled flip (`loader.await()` + an all-ACTIVE sweep) still switches the shell from the loading page to the real UI in one pass — settled means every entry is created and every fiber reached ACTIVE, with FAILED/PENDING fibers listed loud; there is no partial-availability mode (progressive rendering is deferred work).
Type universes stay split at the aggregate level — `tsconfig.host.json` is the host program and `tsconfig.client.json` the client program, both referenced by the solution root `tsconfig.json` — because both sides merge cordis `Context` under the same keys (`sessions`, `loader`) with different services; client packages consume the wire vocabulary through pure type subpaths (`@deepseek-ai/dsh-session/types` and kin) so no host augmentation rides into the client program.
@@ -30,7 +30,7 @@ Status: implemented
## client cordis 树与装载链
装载链——两类包(普通包 vs dshClient 插件)、模块系统/插件治理器之分、host 独家撰写的带修订号 entry 图之上的双层 boot、热重载——归 [client 插件装载 RFC](2026-07-23-client-plugin-loading-model.md) 所有。本篇赖以立足的事实:浏览器启动与 host 相同的 vendored `@cordisjs/plugin-loader`,由 client 模块系统(`ctx.modules``packages/client/modules`)填上其 `internal` seam;凡带产品行为的单元都是 host 独家撰写的 `__DSH_BOOT__` 图里的 entry——全部九个插件包(含基础设施)都携带 `dshClient` 声明、以 fetch 到达的 `./client` tsdown 闭包 bundle 供给,`immediately` 行的差别仅在 boot 第一层预取,而普通包(react 家族、cordis、尚未升格的库)保持打进壳、已播种、对图不可见;bundle 执行 `window.__ModuleLoader__.load({ id, factory })`,其 `require` 由 lazy CJS 模块表应答(种子词条 + 已登记工厂,首次 require 时物化并记忆化——跨插件值 import 是构建错误,协作走 cordis 服务);插件 CSS 内联在 bundle 里、物化时注入为 `<style data-plugin="<id>">`CSS Modules 哈希 + 归属标记 = 隔离,重载时移除);热重载已在 dev 图落地——webserver 对自己供给的 bundle 做 stat 轮询并广播 `rebuilt` SSE 帧,`client-hmr` 插件每帧换掉一个 fiber。settled 翻转(`loader.await()` + 一次全 ACTIVE 扫描)依旧让壳从 loading 页一次切换到真 UI——settled 意味着每个 entry 已创建、每个 fiber 都到达 ACTIVEFAILED/PENDING 的 fiber 被大声列出;不存在部分可用模式(渐进渲染为后置工作)。
装载链——两类包(普通包 vs dshClient 插件)、模块系统/插件治理器之分、host 独家撰写的带修订号 entry 图之上的双层 boot、热重载——归 [client 插件装载 RFC](2026-07-23-client-plugin-loading-model.md) 所有。本篇赖以立足的事实:浏览器启动与 host 相同的 vendored `@cordisjs/plugin-loader`,由 client 模块系统(`ctx.modules``packages/client/modules`)填上其 `internal` seam;凡带产品行为的单元都是 host 独家撰写的 `__DSH_BOOT__` 图里的 entry——每个生产插件包(含基础设施)都携带 `dshClient` 声明、以 fetch 到达的 `./client` tsdown 闭包 bundle 供给,`immediately` 行的差别仅在 boot 第一层预取,而普通包(react 家族、cordis、尚未升格的库)保持打进壳、已播种、对图不可见;bundle 执行 `window.__ModuleLoader__.load({ id, factory })`,其 `require` 由 lazy CJS 模块表应答(种子词条 + 已登记工厂,首次 require 时物化并记忆化——跨插件值 import 是构建错误,协作走 cordis 服务);插件 CSS 内联在 bundle 里、物化时注入为 `<style data-plugin="<id>">`CSS Modules 哈希 + 归属标记 = 隔离,重载时移除);热重载已在 dev 图落地——webserver 对自己供给的 bundle 做 stat 轮询并广播 `rebuilt` SSE 帧,`client-hmr` 插件每帧换掉一个 fiber。settled 翻转(`loader.await()` + 一次全 ACTIVE 扫描)依旧让壳从 loading 页一次切换到真 UI——settled 意味着每个 entry 已创建、每个 fiber 都到达 ACTIVEFAILED/PENDING 的 fiber 被大声列出;不存在部分可用模式(渐进渲染为后置工作)。
类型宇宙在聚合层拆分——`tsconfig.host.json` 是 host program、`tsconfig.client.json` 是 client program,二者由 solution 根 `tsconfig.json` 引用,因为两侧都在相同键(`sessions``loader`)上对 cordis `Context` 做声明合并且服务不同;client 包经纯类型子路径(`@deepseek-ai/dsh-session/types` 等)消费协议词汇,host 侧的声明合并不会搭车进入 client program。
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-20-routed-model-context-and-compaction-policy.md: b00c744e4ac79983a1f492d710e7b1542278c4f4
2026-07-20-routed-model-context-and-compaction-policy.zh.md: 88e26aa7a99e8deb434d9a8fd80784be410a999a
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md
2026-07-20-routed-model-context-and-compaction-policy.md: 6a2626d4eea3e30533199320b6b0ed5b05e7fd28
2026-07-20-routed-model-context-and-compaction-policy.zh.md: 2881b2d4205359b715303622268ee9218dd75831
@@ -16,7 +16,7 @@ Neither obvious configuration owner is sufficient. Compact-basic is optional and
`LlmAdapter.resolveModel(provider, model, signal?)` returns aggregate metadata for one exact route, with optional `LlmModelContext` under its `context` field. `LlmService.resolveModelInfo()` selects the registered route owner, validates a positive integer `contextWindow`, and returns detached metadata. The query is independent of `listModels()`: an unlisted dynamic model may have capacity metadata, and an absent `context` means only that the adapter cannot describe capacity.
The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The two built-in model entries each publish an exact 128,000-token capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model.
The hand-rolled DeepSeek adapter accepts optional `contextWindow` on each configured model plus an adapter-wide `defaultContextWindow`. Exact model capacity wins; an entry without capacity and an unlisted pass-through id inherit the adapter default, or omit `context` when it is absent. The two built-in model entries each publish an exact 256,000-token capacity. The pi-ai adapter resolves capacity from the same catalog descriptor that authoritatively resolves the request model.
### Token measurement remains model-agnostic
@@ -16,7 +16,7 @@ Status: implemented
`LlmAdapter.resolveModel(provider, model, signal?)` 返回一条精确路由的聚合元数据,其中可选的 `LlmModelContext` 位于 `context` 字段下。`LlmService.resolveModelInfo()` 选择已注册的路由所属方,验证 `contextWindow` 为正整数,并返回分离的元数据。该查询独立于 `listModels()`:不在目录中的动态模型也可以拥有容量元数据,而缺少 `context` 只表示适配器无法描述容量。
手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。两个内置模型项都公开精确的 128,000 token 容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。
手写 DeepSeek 适配器允许每个已配置模型提供可选 `contextWindow`,并支持适配器级 `defaultContextWindow`。精确模型容量优先;未提供容量的模型项与未列出的透传 id 会继承适配器默认值,若默认值也不存在则省略 `context`。两个内置模型项都公开精确的 256,000 token 容量。pi-ai 适配器从同一个目录描述符解析容量,该描述符也用于权威解析请求模型。
### Token 计量保持模型无关
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-23-client-plugin-loading-model.md: 3513e026785fc366455bb32bf788a3a098275bb1
2026-07-23-client-plugin-loading-model.zh.md: f31a1b076a5d93db44c67463730b38283d44ff7f
2026-07-23-client-plugin-loading-model.md: 4028f50bf5cf8a05063df3bf5e2b4b7f45a3c02e
2026-07-23-client-plugin-loading-model.zh.md: 7dc96b47c950af07adb71ba7fe131d53e5c0e51d
@@ -27,7 +27,7 @@ The first-generation client loader (`createClientLoader`) hand-wrote both layers
What makes a package a plugin? One rule: **a package is a plugin package once its consumption is cordis dependency injection; until then it is a plain package.** How code reaches the page is not part of the taxonomy — arrival follows from the kind instead of defining it.
- **Plain packages** are the absolute base the module system itself needs, plus libraries not yet converted to DI: the react family, cordis, `@deepseek-ai/dsh-client-modules` (the module system itself — it can never be a plugin, because modules precede all modules), the web shell kernel, and — for now — ui-slots, web-react, ui-primitives. Plain packages are shell-bundled, seeded into the module table, and invisible to the host graph.
- **Plugin packages** are everything else. Each one carries a `dshClient` manifest declaration (`{ platform, inject, immediately? }`) and one uniform shape: the shared tsdown preset emits `lib/client.js`, and `exports["./client"]` points at that bundle. Each is a governed entry of the host-authored graph. Nine exist today: connection, runtime, ui-theme, i18n, hmr (dev graphs only), ui-layout, ui-sidebar, ui-conversation, ui-trajectory.
- **Plugin packages** are everything else. Each one carries a `dshClient` manifest declaration (`{ platform, inject, immediately? }`) and one uniform shape: the shared tsdown preset emits `lib/client.js`, and `exports["./client"]` points at that bundle. Each is a governed entry of the host-authored graph. The current set is connection, runtime, ui-theme, i18n, hmr (dev graphs only), ui-layout, ui-sidebar, ui-conversation, ui-model-selector, ui-question, and ui-trajectory.
The manifest owns the package's loading contract: its `inject` dependency edges, plus the optional `immediately` prefetch mark (absent means lazy). The composing app owns only the roster and the `--dev` switch.
@@ -39,7 +39,7 @@ Four edge rules govern imports across the two kinds. None of them depends on any
- **Plugin ↔ plugin value imports are a build error.** This holds regardless of either side's `immediately` declaration — the rule must not depend on a mark someone can flip. Cooperation goes through cordis inject/services. `import type` is exempt; the type chain is untouched. This rule is why `scopeOf` is a `SessionsService` method and why `transportError` lives in `dsh-host-apiproxy`'s wire layer (its `RpcResult` home, inline-safe).
- **Plugin → plain package value imports are externals**, judged against the platform list. That list is one constant in the shell (`platform.ts`: react family, cordis, ui-slots, web-react, ui-primitives), imported by both the tsdown preset (for the external judgement) and `seed.ts` (for the table warm-up). One constant, two consumers — the hand-sync drift class stays dead.
- **The purity gate covers all nine plugin packages.** Its three branches: platform imports become externals; INLINE_SAFE wire layers are inlined; any other workspace leak is a build error. The uniform bundle shape is what makes this coverage total — every plugin builds through the same preset, so no package can sit outside the gate.
- **The purity gate covers every plugin package.** Its three branches: platform imports become externals; INLINE_SAFE wire layers are inlined; any other workspace leak is a build error. The uniform bundle shape is what makes this coverage total — every plugin builds through the same preset, so no package can sit outside the gate.
- **The shell is self-sufficient.** The kernel (boot + loading page) value-imports no plugin package; its status stores are hand-rolled. The fail-loud presentation must not depend on the system whose failure it reports.
### One module system, one plugin governor
@@ -88,7 +88,7 @@ On the browser side, the driver reloads one plugin per frame, serialized:
6. `entry.refresh()` — re-imports, materializing the fresh factory. CSS re-injects here, under the same stable tag ids.
7. `fiber.await()` — rethrows loud.
All nine plugins share this one semantics; an `immediately` row reloads exactly like a lazy one. Dependency cascade costs zero client code: a fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-loads every dependent through cordis itself. Reloading connection or runtime cascades the whole UI — correct, if heavy.
Every plugin shares this one semantics; an `immediately` row reloads exactly like a lazy one. Dependency cascade costs zero client code: a fiber's activation epoch strings its service providers' uids, so replacing a provider's fiber re-loads every dependent through cordis itself. Reloading connection or runtime cascades the whole UI — correct, if heavy.
The support boundary, stated honestly. Reload is coarse by design: fresh fiber, fresh components, React state lost, data layer untouched — react-refresh-grade state preservation conflicts with "re-executing the bundle re-runs the factory" and is deliberately out. Plain packages (react family, shell kernel, not-yet-promoted libraries) are not entries: changing them means a shell rebuild and a full page reload. No rollback in v1: an import failure leaves the entry fiberless and the next rebuilt frame retries from scratch; an apply failure leaves a FAILED fiber for the status projection; both log loudly. Self-reload works — the in-flight reload finishes in the old bundle's closure and the new apply opens a fresh SSE channel — but frames arriving in the gap are lost, and the next rebuild renotifies. One known dev-only race: a rebuilt frame overlapping a still-in-flight boot arrival shares that arrival's task and may materialize the pre-rebuild bytes; the next frame self-heals.
@@ -27,7 +27,7 @@ host 侧,cordis 插件装载站在 Node 的模块机制之上——require cac
什么让一个包成为插件?只有一条规则:**一个包的消费方式一旦是 cordis 依赖注入,它就是插件包;在此之前它是普通包。**代码怎么到达页面不属于分类体系——到达方式由包的类别推得,而不是反过来定义类别。
- **普通包**是模块系统自身所需的绝对基座,加上尚未转成 DI 的库:react 家族、cordis、`@deepseek-ai/dsh-client-modules`(模块系统本身——它永远不可能是插件,因为模块先于一切模块)、web 壳内核,以及——暂时——ui-slots、web-react、ui-primitives。普通包打进壳 bundle、播种进模块表、对 host 图不可见。
- **插件包**是其余一切。每个都携带 `dshClient` manifest(元数据清单)声明(`{ platform, inject, immediately? }`)和同一种统一形态:共享 tsdown 预设产出 `lib/client.js``exports["./client"]` 指向该 bundle。每个都是 host 独家撰写的图里受治理的 entry。现有九个connection、runtime、ui-theme、i18n、hmr(仅进 dev 图)、ui-layout、ui-sidebar、ui-conversation、ui-trajectory。
- **插件包**是其余一切。每个都携带 `dshClient` manifest(元数据清单)声明(`{ platform, inject, immediately? }`)和同一种统一形态:共享 tsdown 预设产出 `lib/client.js``exports["./client"]` 指向该 bundle。每个都是 host 独家撰写的图里受治理的 entry。当前包括connection、runtime、ui-theme、i18n、hmr(仅进 dev 图)、ui-layout、ui-sidebar、ui-conversation、ui-model-selector、ui-question、ui-trajectory。
manifest 拥有包的装载契约:它的 `inject` 依赖边,加可选的 `immediately` 预取标记(缺省即 lazy)。负责组合的 app 只拥有名册与 `--dev` 开关。
@@ -39,7 +39,7 @@ manifest 拥有包的装载契约:它的 `inject` 依赖边,加可选的 `im
- **插件 ↔ 插件的值 import 是构建错误。**与两侧的 `immediately` 声明无关——规则不得依赖一个人人可翻转的标记。协作走 cordis inject/服务。`import type` 豁免;类型链分毫未动。这条规则正是 `scopeOf``SessionsService` 方法、`transportError` 住在 `dsh-host-apiproxy` wire 层(它的 `RpcResult` 老家,内联安全)的原因。
- **插件 → 普通包的值 import 外置为 external**,按平台清单判定。清单是壳里的一个常量(`platform.ts`react 家族、cordis、ui-slots、web-react、ui-primitives),tsdown 预设(external 判定)与 `seed.ts`(模块表预热)都 import 它。一个常量、两个消费方——人肉同步这一漂移缺陷类死透。
- **纯度门禁覆盖全部九个插件包。**它的三条分支:平台 import 外置为 externalINLINE_SAFE wire 层内联;其余任何 workspace 泄漏即构建错误。正是统一的 bundle 形态让这一覆盖不留死角——每个插件都经同一预设构建,没有包能坐在门禁之外。
- **纯度门禁覆盖个插件包。**它的三条分支:平台 import 外置为 externalINLINE_SAFE wire 层内联;其余任何 workspace 泄漏即构建错误。正是统一的 bundle 形态让这一覆盖不留死角——每个插件都经同一预设构建,没有包能坐在门禁之外。
- **壳自足。**内核(boot + loading 页)对任何插件包零值 import;其状态 store 为手写。大声失败的呈现不得依赖它所报告失败的那个系统。
### 一套模块系统,一个插件治理器
@@ -88,7 +88,7 @@ vendored Loader 经其 `internal` seam 消费模块系统——唯一调用点
6. `entry.refresh()`——重新 import,物化新工厂。CSS 在这里重新注入,沿用同一批稳定标签 id。
7. `fiber.await()`——让失败大声重抛。
个插件共享同一套语义;`immediately` 行的重载与 lazy 行分毫不差。依赖级联不花一行 client 代码:fiber 的激活纪元串接着它各服务提供方的 uid,因此换掉提供方的 fiber,每个依赖方都会经 cordis 本身重新装载。重载 connection 或 runtime 会级联整个 UI——正确,虽然重。
个插件共享同一套语义;`immediately` 行的重载与 lazy 行分毫不差。依赖级联不花一行 client 代码:fiber 的激活纪元串接着它各服务提供方的 uid,因此换掉提供方的 fiber,每个依赖方都会经 cordis 本身重新装载。重载 connection 或 runtime 会级联整个 UI——正确,虽然重。
支持边界,如实陈述。重载粒度刻意做粗:全新 fiber、全新组件、React 状态丢失、数据层不动——react-refresh 级的状态保留与「重执行 bundle 即重跑工厂」相冲突,属刻意不做。普通包(react 家族、壳内核、尚未升格的库)不是 entry:改它们意味着壳重建加整页刷新。v1 不做回滚:import 失败让 entry 失去 fiber,下一个 rebuilt 帧从头重试;apply 失败留下 FAILED fiber 交给状态投影;两者都大声记录。自我重载可行——在途的重载在旧 bundle 的闭包里跑完,新的 apply 再开一条新 SSE 通道——但空窗期到达的帧会丢失,下次重建会再次通知。一处已知的仅限 dev 竞态:rebuilt 帧与仍在途的 boot 到达重叠时共享那次到达的任务,可能物化重建前的字节;下一帧自愈。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-24-web-config-tree-boot-and-transport-layering.md: 377ebd2b3cf9ff1dff81dd3546bb262e0ebde88a
2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 403e95fb3088d2164d50710d2a69de5526807764
2026-07-24-web-config-tree-boot-and-transport-layering.md: 99e5d0d95f320464a6f12857e9d6e90e3587c777
2026-07-24-web-config-tree-boot-and-transport-layering.zh.md: 89c1a578f0ad0841eea7512538e3de8095b82c4d
@@ -12,7 +12,7 @@ English | [中文](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md)
## Decision
**Composition is one flat config tree.** `apps/cli/cordis.yml` holds every row — the host runtime (32 rows), the `api-gateway` row, the `webserver` row, and the ten `dshClient` rows (the browser roster; the modules row is simultaneously a host row). No spine bundle: every plugin is one row and every config field is yml-editable. `--dev` appends the `dsh-client-hmr` row in code before the settle sweep — prod and dev differ by exactly that row. Row order carries no load semantics; activation is service-availability driven, and the boot compensates with a fail-loud triple: `assertEntriesLoaded` (import failures), `installFailLoud` (late apply rejections), and an all-ACTIVE sweep (PENDING fibers — cordis inject waiting has no timeout).
**Composition is one flat config tree.** `apps/cli/cordis.yml` holds every row — the host runtime (32 rows), the `api-gateway` row, the `webserver` row, and the `dshClient` rows (the browser roster; the modules row is simultaneously a host row). No spine bundle: every plugin is one row and every config field is yml-editable. `--dev` appends the `dsh-client-hmr` row in code before the settle sweep — prod and dev differ by exactly that row. Row order carries no load semantics; activation is service-availability driven, and the boot compensates with a fail-loud triple: `assertEntriesLoaded` (import failures), `installFailLoud` (late apply rejections), and an all-ACTIVE sweep (PENDING fibers — cordis inject waiting has no timeout).
**Boot glue is a class pair.** `AppCLIEntry` (apps/cli) and `AppWebEntry` (the shell kernel) hold only what must exist independently of cordis: argv facts, the composed patch set, the parsed boot manifest, the module system instance, loading-page handles — everything else lives in plugins. `AppCLIEntry.run()` is three stages: layered env (ambient > cwd `.env` > `$DSH_HOME/.env`, closing the defect above) → patch composition → Loader include boot plus the triple. `AppWebEntry.run()` mirrors it browser-side: parse `window.__DSH_BOOT__` into a `BootManifest` (two views: npm-package rows for the module table, cordis-plugin rows for entry composition; malformed wire throws), build the module system, render the loading page, prefetch the `immediately` tier in parallel with Context/Loader setup, **await the prefetch before creating entries** (materialization is `tree.import`'s synchronous require, unprotected by fiber inject waiting; cross-package require edges such as i18n → runtime/client need every immediately-tier factory registered first — an empirically found 1025% boot race otherwise), adopt the modules entry, create the graph rows, settle, sweep.
@@ -12,7 +12,7 @@ Status: implemented
## 决策
**组合是一棵平铺 config tree。** `apps/cli/cordis.yml` 持有全部行——host runtime32 行)、`api-gateway` 行、`webserver` 行、十个 `dshClient` 行(浏览器 rostermodules 行同时是 host 行)。不做 spine bundle:每插件一行、每个 config 字段 yml 可改。`--dev` 在 settle sweep 之前由代码追加 `dsh-client-hmr` 行——prod 与 dev 的全部差异就是这一行。行序无装载语义;激活由服务可用性驱动,boot 以 fail-loud 三件套补偿:`assertEntriesLoaded`import 失败)、`installFailLoud`(迟到的 apply 拒绝)、all-ACTIVE sweepPENDING fiber——cordis inject 等待没有超时)。
**组合是一棵平铺 config tree。** `apps/cli/cordis.yml` 持有全部行——host runtime32 行)、`api-gateway` 行、`webserver` 行、`dshClient` 行(浏览器 rostermodules 行同时是 host 行)。不做 spine bundle:每插件一行、每个 config 字段 yml 可改。`--dev` 在 settle sweep 之前由代码追加 `dsh-client-hmr` 行——prod 与 dev 的全部差异就是这一行。行序无装载语义;激活由服务可用性驱动,boot 以 fail-loud 三件套补偿:`assertEntriesLoaded`import 失败)、`installFailLoud`(迟到的 apply 拒绝)、all-ACTIVE sweepPENDING fiber——cordis inject 等待没有超时)。
**boot 胶水是一对 class。** `AppCLIEntry`apps/cli)与 `AppWebEntry`(壳内核)只持有独立于 cordis 必须提前存在的东西:argv 事实、合成的 patch 集、解析出的 boot manifest、模块系统实例、loading 页句柄——其余一律进插件。`AppCLIEntry.run()` 三段:分层 envambient > cwd `.env` > `$DSH_HOME/.env`,顺手关掉上述缺陷)→ patch 合成 → Loader include boot 加三件套。`AppWebEntry.run()` 在浏览器侧镜像它:把 `window.__DSH_BOOT__` 解析成 `BootManifest`(双视角:npm 包行给模块表、cordis 插件行给 entry 组合;畸形 wire 大声抛)、建模块系统、渲染 loading 页、immediately 层预取与 Context/Loader 准备并行、**create entry 之前等预取齐**(物化是 `tree.import` 的同步 require,不受 fiber inject 等待保护;i18n → runtime/client 这类跨包 require 边要求 immediately 层工厂全部注册完——否则有实测 10–25% 的 boot 竞态)、收编 modules entry、逐图行 create、settle、sweep。
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-21-semantic-session-checkpoints.md: 0034cde40e5b07bda1573ca39fb7d51816006140
2026-07-21-semantic-session-checkpoints.zh.md: 3351221d7eeaf1353b4adb0fa4c4dc324ec33da5
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md
2026-07-21-semantic-session-checkpoints.md: 927a4c5d6d2aad5dea460ea29f97c1686e9d5398
2026-07-21-semantic-session-checkpoints.zh.md: 6454b496aa8c03c172d6a4bc969e43e8dbca2430
@@ -10,11 +10,11 @@ Persistence buffered every synchronous `session/event` until the loop's final tu
## Decision
`dsh-session-checkpoint-policy` owns semantic durability barriers as a zero-config plugin beside a persistence backend. It wraps `llm/stream` lazily and flushes the live session after `request/header` is logged but before the adapter stream is constructed. It wraps top-level `tools/execute` after ordered pre-execute policy and flushes the recorded `tool/call` before the tool body; nested dispatches reuse the outer model-visible call. It flushes at `agent/post-step` after the assistant message and ordered results are recorded. The loop's existing final `turn/end` checkpoint remains the closing boundary.
`dsh-session-checkpoint-policy` owns semantic durability barriers as a zero-config plugin beside a persistence backend. At `agent/step`, it flushes pending prompt input or the preceding response/result batch before the next request is derived. It wraps `llm/stream` lazily and flushes the live session after `request/header` is logged but before the adapter stream is constructed. It wraps top-level `tools/execute` after ordered pre-execute policy and flushes the recorded `tool/call` before the tool body; nested dispatches reuse the outer model-visible call. The loop's final `turn/end` checkpoint remains the closing boundary and settles before another queued turn or idle observation.
Persistence and checkpoint scheduling remain separate Cordis plugins. A backend makes requested `session/flush` boundaries durable but does not choose them; loading it without this policy is valid and retains the loop's coarser checkpoints. First-party persisted apps and runtimes explicitly mount both, while a specialized deployment may intentionally omit or replace the policy. Registration order governs whether events appended by other `agent/post-step` listeners join this checkpoint; the loop-owned assistant message and ordered results always precede the event.
Persistence and checkpoint scheduling remain separate Cordis plugins. A backend makes requested `session/flush` boundaries durable but does not choose them; loading it without this policy is valid and retains the loop's coarser checkpoints. First-party persisted apps and runtimes explicitly mount both, while a specialized deployment may intentionally omit or replace the policy. Registration order governs whether events appended by other `agent/step` listeners precede this checkpoint; prompt input and the preceding loop-owned assistant message and ordered results are already in the log.
Checkpoint failure and cancellation are fail-closed at effect boundaries. A rejected request checkpoint prevents adapter dispatch; a rejected tool checkpoint becomes an error result without invoking the tool body. If cancellation lands while the tool checkpoint is pending, the policy rechecks the signal and returns the canonical `ABORTED_BEFORE_DISPATCH` result. A rejected post-step checkpoint stops continuation before another model request. Persistence serialization continues to belong to the coordinator, so concurrent tool checkpoints cannot duplicate event sequences.
Checkpoint failure and cancellation are fail-closed at effect boundaries. A rejected request checkpoint prevents adapter dispatch; a rejected tool checkpoint becomes an error result without invoking the tool body. If cancellation lands while the tool checkpoint is pending, the policy rechecks the signal and returns the canonical `ABORTED_BEFORE_DISPATCH` result. A rejected between-step checkpoint closes the turn before another model request. A rejected final turn checkpoint is reported live and does not prevent later queued work. Persistence serialization continues to belong to the coordinator, so concurrent tool checkpoints cannot duplicate event sequences.
The ACP app owns its bridge, checkpoint policy, and persistence backend in one ordered Cordis effect. Cordis unloads sibling plugin effects concurrently, so independent mounts would let persistence detach while bridge teardown was still closing an interrupted turn. The composite lifecycle unloads the bridge first, waits for its agents to quiesce and flush the real `step/end` and `turn/end`, then removes checkpoint scheduling and persistence.
@@ -26,4 +26,4 @@ Flushing every event or streaming chunk minimizes loss but turns local append an
## Consequences
Hard-crash recovery retains the complete model request, durable tool intent, and complete settled step at the nearest semantic boundary while allowing partial streaming chunks since the previous boundary to remain lossy. Default CLI, TUI, ACP, Python SDK runtime, headless persistence tests, and JSON-RPC compositions mount the policy with their persistence backend. Unit tests cover ordering, cancellation during a checkpoint, fail-closed behavior, nested dispatch, disposal, and Loader shape; a real child process killed with `SIGKILL` proves request and tool-intent recovery through JSONL, and the shared persistence contract proves both recovery classifications across backends. The crash harness waits for the expected marker contents rather than path existence, so open-before-write visibility cannot trigger the kill early. Keyless ACP snapshots prove both that retry-risk guidance reaches resumed history and the next model turn and that graceful cancellation persists the loop's real closing boundaries.
Hard-crash recovery retains the complete model request, durable tool intent, and complete settled step at the nearest semantic boundary while allowing partial streaming chunks since the previous boundary to remain lossy. Default CLI, TUI, ACP, Python SDK runtime, headless persistence tests, and JSON-RPC compositions mount the policy with their persistence backend. Unit tests cover ordering, cancellation during a checkpoint, fail-closed behavior, nested dispatch, disposal, Loader shape, and final-checkpoint ordering and failure containment; a real child process killed with `SIGKILL` proves request and tool-intent recovery through JSONL, and the shared persistence contract proves both recovery classifications across backends. The crash harness waits for the expected marker contents rather than path existence, so open-before-write visibility cannot trigger the kill early. Keyless ACP and SDK snapshots prove that retry-risk guidance reaches resumed history and the next model turn, graceful cancellation persists the loop's real closing boundaries, and SDK shutdown observes the complete persisted turn.
@@ -10,11 +10,11 @@ Status: implemented
## 决策
`dsh-session-checkpoint-policy` 以零配置插件的形式与持久化后端共同加载,并负责语义持久性屏障。该插件惰性包装 `llm/stream`,在记录 `request/header` 之后、构造适配器流之前,刷新活动会话。该插件还在有序的执行前策略之后包装顶层 `tools/execute`,在进入工具主体前刷新已记录的 `tool/call`;嵌套分发则复用外层模型可见调用。它还会在 `agent/post-step` 时刷新会话,此时模型消息与按序结果都已记录。现有的最终 `turn/end` 检查点仍是轮次的收尾边界。
`dsh-session-checkpoint-policy` 以零配置插件的形式与持久化后端共同加载,并负责语义持久性屏障。`agent/step` 时,该插件会在推导下一个请求前刷新待持久化的提示词输入或前一批响应/结果。该插件惰性包装 `llm/stream`,在记录 `request/header` 之后、构造适配器流之前,刷新活动会话。该插件还在有序的执行前策略之后包装顶层 `tools/execute`,在进入工具主体前刷新已记录的 `tool/call`;嵌套分发则复用外层模型可见调用。循环的最终 `turn/end` 检查点仍是轮次的收尾边界,并会在处理另一个已排队轮次或观察到空闲状态之前完成
持久化与检查点调度仍是相互独立的 Cordis 插件。后端使请求的 `session/flush` 边界持久化,但不选择边界;只加载后端而不加载本策略仍是有效组合,并保留循环提供的较粗检查点。第一方持久化应用与运行时会显式加载两者,专用部署则可以有意省略或替换本策略。注册顺序决定其他 `agent/post-step` 监听器追加的事件是否会纳入本检查点;循环自身记录的助手消息与有序结果始终先于该事件
持久化与检查点调度仍是相互独立的 Cordis 插件。后端使请求的 `session/flush` 边界持久化,但不选择边界;只加载后端而不加载本策略仍是有效组合,并保留循环提供的较粗检查点。第一方持久化应用与运行时会显式加载两者,专用部署则可以有意省略或替换本策略。注册顺序决定其他 `agent/step` 监听器追加的事件是否先于本检查点;提示词输入以及前一批由循环自身记录的助手消息与有序结果都已在日志中
检查点失败与取消在副作用边界上采取失败关闭策略。请求检查点被拒绝时,系统不会分发给适配器;工具检查点被拒绝时,系统会返回错误结果,不调用工具主体。如果在工具检查点等待期间收到取消,策略会重新检查信号,并返回标准的 `ABORTED_BEFORE_DISPATCH` 结果。步骤检查点被拒绝时,系统会在发起下一个模型请求前停止继续执行。持久化写入的串行化仍由协调器负责,因此并发的工具检查点不会产生重复的事件序号。
检查点失败与取消在副作用边界上采取失败关闭策略。请求检查点被拒绝时,系统不会分发给适配器;工具检查点被拒绝时,系统会返回错误结果,不调用工具主体。如果在工具检查点等待期间收到取消,策略会重新检查信号,并返回标准的 `ABORTED_BEFORE_DISPATCH` 结果。步骤检查点被拒绝时,系统会在发起下一个模型请求前结束该轮次。轮次的最终检查点被拒绝时,系统会实时报告该失败,但不会阻止后续排队工作。持久化写入的串行化仍由协调器负责,因此并发的工具检查点不会产生重复的事件序号。
ACPAgent Client Protocol)应用在一个有序 Cordis effect 中统一持有其桥接层、检查点策略与持久化后端。Cordis 会并发卸载同级插件的 effect;如果分别加载,桥接层仍在为被中断的轮次收尾时,持久化后端就可能已经卸载。组合生命周期会先卸载桥接层,等待其各 agent 达到静止,并刷新真实的 `step/end``turn/end`,再移除检查点调度与持久化。
@@ -26,4 +26,4 @@ ACPAgent Client Protocol)应用在一个有序 Cordis effect 中统一持
## 后果
发生硬崩溃时,崩溃恢复会在最近的语义边界保留完整的模型请求、持久化的工具意图与完整且已结束的步骤,但允许上一个边界之后的部分流式分片仍可能丢失。默认的 CLI(命令行界面)、TUI、ACP、Python SDK 运行时、headless 持久化测试与 JSON-RPC 组合都会在持久化后端旁加载该策略。单元测试覆盖顺序、检查点期间的取消、失败关闭行为、嵌套分发、dispose(资源释放)与 Loader 形状;一个被 `SIGKILL` 终止的真实子进程通过 JSONL 证明系统可以恢复请求与工具意图,共享持久化契约则证明各后端都支持这两种恢复分类。崩溃 harness 会等待预期的标记内容,而不是仅等待路径存在,因此文件在写入前因打开而可见时,不会导致该 harness 提前终止子进程。无密钥 ACP 快照证明重试风险指引会进入恢复后的历史记录与下一个模型轮次,也证明取消流程正常收尾时系统会持久化由循环实际生成的闭合边界。
发生硬崩溃时,崩溃恢复会在最近的语义边界保留完整的模型请求、持久化的工具意图与完整且已结束的步骤,但允许上一个边界之后的部分流式分片仍可能丢失。默认的 CLI(命令行界面)、TUI、ACP、Python SDK 运行时、headless 持久化测试与 JSON-RPC 组合都会在持久化后端旁加载该策略。单元测试覆盖顺序、检查点期间的取消、失败关闭行为、嵌套分发、dispose(资源释放)与 Loader 形状,以及最终检查点的顺序与故障隔离;一个被 `SIGKILL` 终止的真实子进程通过 JSONL 证明系统可以恢复请求与工具意图,共享持久化契约则证明各后端都支持这两种恢复分类。崩溃 harness 会等待预期的标记内容,而不是仅等待路径存在,因此文件在写入前因打开而可见时,不会导致该 harness 提前终止子进程。无密钥 ACP 与 SDK 快照证明重试风险指引会进入恢复后的历史记录与下一个模型轮次,取消流程正常收尾时系统会持久化由循环实际生成的闭合边界,且 SDK 关闭流程会观察到已完整持久化的轮次
@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-05-skill-system.md: 95772067b3b224f15fd290876c297eccbe5ab97e
2026-07-05-skill-system.zh.md: 589410076b6da1b108b01f55bf217001b2a8404f
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md
2026-07-05-skill-system.md: 4fc621a9fdfa8042ebf3eb1975f0930cb1bf116c
2026-07-05-skill-system.zh.md: 0dbebd211a1fa9e434d3f0a189c936f2b1c76574
@@ -12,9 +12,9 @@ DeepSeek Harness uses the same primitive so project-specific review, plugin-auth
## Decision
`@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-local` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the session-prefix catalog and model-facing loader tool. `dsh-agent-spine-demo` loads the registry, local provider, and consumer by default so TUI, headless, and ACP apps get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Its `skills` config forwards `registry`, `local`, and `tool` branches to those owners.
`@deepseek-ai/dsh-skill` is the pure provider registry (`ctx.skills`), `@deepseek-ai/dsh-skill-local` is the shipped local filesystem provider, and `@deepseek-ai/dsh-tool-skill` owns the durable session catalog and model-facing loader tool. `dsh-agent-spine-demo` loads the registry, local provider, and consumer by default so TUI, headless, and ACP apps get the same behavior while embedded or remote providers contribute skills without changing the registry or consumer. Its `skills` config forwards `registry`, `local`, and `tool` branches to those owners.
Provider plugins register synchronously during `apply()`. Provider membership is direct effect-owned state: registration and disposal invalidate completed catalogs synchronously, and discovery reads the current provider map on demand rather than observing registry-change events. Provider catalogs return ranked candidates from awaited `list()` calls, where remote providers perform initialization, authentication, and discovery while honoring the lookup abort signal. The registry validates each candidate, resolves same-name skills first-wins by rank, provider registration order, and provider-local order, then sorts summaries by skill name for deterministic consumers. It caches only completed catalog snapshots and retries when a provider/runtime revision changes during discovery, so an unload cannot freeze a stale, unresolvable skill into a session prefix. Runtime `ctx.skills.register(...)` remains a convenience for embedded in-process skills and uses project-over-user priority; `runtime` is reserved as the registry-owned provider name.
Provider plugins register synchronously during `apply()`. Provider membership is direct effect-owned state: registration and disposal invalidate completed catalogs synchronously, and discovery reads the current provider map on demand rather than observing registry-change events. Provider catalogs return ranked candidates from awaited `list()` calls, where remote providers perform initialization, authentication, and discovery while honoring the lookup abort signal. The registry validates each candidate, resolves same-name skills first-wins by rank, provider registration order, and provider-local order, then sorts summaries by skill name for deterministic consumers. It caches only completed catalog snapshots and retries when a provider/runtime revision changes during discovery, so an unload cannot freeze a stale, unresolvable skill into a session catalog. Runtime `ctx.skills.register(...)` remains a convenience for embedded in-process skills and uses project-over-user priority; `runtime` is reserved as the registry-owned provider name.
The local provider scans cwd-sensitive project roots, custom roots, and user roots in first-wins rank order: project `.dsh`, project `.agents`, `customSkillDirs`, user `.dsh`, then user `.agents`. The user `.dsh/skills` scan skips `.system` so a system-owned directory is not treated as normal user content. DeepSeek Harness does not ship built-in system skills; embedded or remote providers supply additional skills when configured.
@@ -22,7 +22,7 @@ Each skill is either `<name>/SKILL.md` or `<name>.md` with YAML frontmatter. `na
Local skill filesystem I/O goes through `ctx.fs` when a filesystem service is loaded: project-root lookup probes `.git` with `resolve` and `stat`, root discovery uses `listDir`, and skill reads use `readText`. The Node filesystem remains a fallback for minimal contexts that mount `dsh-skill-local` without the fs seam. Missing roots, unreadable or malformed skill files, and transient provider `list()` failures degrade to warn-and-skip so one bad source does not make every agent request fail; malformed candidates still fail fast because they are provider contract violations.
`dsh-tool-skill` contributes one user-role `<system-reminder>` catalog through [`agent/session-prefix`](2026-07-07-session-prefix.md). The catalog contains sorted skill name and description only; it excludes bodies, paths, sources, providers, and routing hints. Descriptions are whitespace-normalized, XML-escaped, and capped by `catalogDescriptionMaxLength`, whose default is `500` and minimum is `3`. The session-prefix seam freezes the request-only catalog per loop instance and records it in the request header, preserving reconstructability without adding it to durable history. Full skill bodies are never included in the catalog.
`dsh-tool-skill` injects one durable user-role `<system-reminder>` catalog as a sourced `user/message` at the session's first `agent/step`, and only when that agent's tool view resolves this plugin's exact `skill` registration. The catalog contains sorted skill name and description only; it excludes bodies, paths, sources, providers, and routing hints. Descriptions are whitespace-normalized, XML-escaped, and capped by `catalogDescriptionMaxLength`, whose default is `500` and minimum is `3`. Full skill bodies are never included in the catalog. (The catalog originally rode the request-only [session-prefix seam](../../archived/feature/2026-07-07-session-prefix.md), archived; the [unified sourced-message decision](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md) moved it into durable history.)
The `skill({ name })` tool loads one full skill for the current agent cwd and returns a tool result containing `<skill_content name="...">`, `<skill_resources>`, and `<skill_instructions>`. `resourceBase` supplies a directory, URL, or opaque provider-managed base for explicitly referenced scripts, references, and assets; resources load only as needed, without directory enumeration. An unresolved name reports that the skill is unknown or no longer available; invalid names and skills marked `disableModelInvocation` retain distinct tool errors. The tool result is the model-visible disclosure path.
@@ -36,7 +36,7 @@ The data structures and catalog/tool contract are documented in [skills.md](../.
**Put local filesystem scanning directly inside `ctx.skills`.** Rejected because coding agents, web agents, and future plugin ecosystems need different skill sources. A provider registry mirrors the subagent seam: the registry owns conflict resolution and consumers, while implementations own loading.
**Use a system-prompt section.** Rejected because the rendered system prompt is a single string, while the catalog is a user-role `<system-reminder>` message with request-only lifecycle requirements. [`agent/session-prefix`](2026-07-07-session-prefix.md) is the selected mechanism: it places the catalog ahead of derived history and records the composed message in the request header.
**Use a system-prompt section.** Rejected because the rendered system prompt is a single string, while the catalog is a user-role `<system-reminder>` message. The [request-only session-prefix seam](../../archived/feature/2026-07-07-session-prefix.md) (archived) was the original mechanism; after the unified sourced-message decision removed that seam, the catalog became a durable sourced injection with the same message shape.
**Materialize built-in DSH authoring skills under `~/.dsh/skills/.system`.** Rejected because bundled skills do not write user home on startup, and embedded or remote providers supply configured skills.
@@ -46,7 +46,7 @@ The data structures and catalog/tool contract are documented in [skills.md](../.
## Consequences
The agent-core spine includes one session-prefix contributor, one local provider, and one model-facing tool. Skill discovery is cwd-sensitive, so callers that create agents with different session cwd values can observe different project skill overrides by design.
The agent-core spine includes one catalog contributor, one local provider, and one model-facing tool. Skill discovery is cwd-sensitive, so callers that create agents with different session cwd values can observe different project skill overrides by design.
The catalog is deterministic for a fixed root set and runtime registration revision, but disk changes are not watched; discovery is memoized until runtime registration invalidates the cache or the process restarts.
@@ -12,9 +12,9 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和
## 决策
`@deepseek-ai/dsh-skill` 是纯提供方注册表(`ctx.skills`),`@deepseek-ai/dsh-skill-local` 是随附的本地文件系统提供方,`@deepseek-ai/dsh-tool-skill` 负责会话前缀目录与面向模型的 loader 工具。`dsh-agent-spine-demo` 默认加载注册表、本地提供方和消费方,使 TUI、headless 与 ACPAgent Client Protocol)应用获得相同行为,同时嵌入式或远程提供方可在不修改注册表或消费方的前提下贡献 skill。其 `skills` 配置将 `registry``local``tool` 分支分别转发给对应的所有者。
`@deepseek-ai/dsh-skill` 是纯提供方注册表(`ctx.skills`),`@deepseek-ai/dsh-skill-local` 是随附的本地文件系统提供方,`@deepseek-ai/dsh-tool-skill` 负责持久化会话目录与面向模型的 loader 工具。`dsh-agent-spine-demo` 默认加载注册表、本地提供方和消费方,使 TUI、headless 与 ACPAgent Client Protocol)应用获得相同行为,同时嵌入式或远程提供方可在不修改注册表或消费方的前提下贡献 skill。其 `skills` 配置将 `registry``local``tool` 分支分别转发给对应的所有者。
提供方插件在 `apply()` 期间同步注册。提供方成员资格是由直接 effect 持有的状态:注册与 dispose(资源释放)同步地使已完成的目录失效,发现操作按需读取当前提供方映射而非监听注册表变更事件。提供方目录从等待的 `list()` 调用返回排序后的候选项,远程提供方在此过程中执行初始化、认证和发现,同时遵守查找的 abort 信号。注册表校验每个候选项,按排名、提供方注册顺序和提供方内部顺序以先到先得方式解决同名 skill 冲突,然后按 skill 名称排序摘要以保证消费方获得确定性结果。它仅缓存已完成的目录快照,并在发现过程中提供方/运行时修订版本发生变化时重试,因此卸载操作不会将一个陈旧且不可解析的 skill 冻结到会话前缀中。运行时 `ctx.skills.register(...)` 仍作为嵌入式进程内 skill 的便捷方式保留,使用 project 优先于 user 的优先级;`runtime` 保留为注册表拥有的提供方名称。
提供方插件在 `apply()` 期间同步注册。提供方成员资格是由直接 effect 持有的状态:注册与 dispose(资源释放)同步地使已完成的目录失效,发现操作按需读取当前提供方映射而非监听注册表变更事件。提供方目录从等待的 `list()` 调用返回排序后的候选项,远程提供方在此过程中执行初始化、认证和发现,同时遵守查找的 abort 信号。注册表校验每个候选项,按排名、提供方注册顺序和提供方内部顺序以先到先得方式解决同名 skill 冲突,然后按 skill 名称排序摘要以保证消费方获得确定性结果。它仅缓存已完成的目录快照,并在发现过程中提供方/运行时修订版本发生变化时重试,因此卸载操作不会将一个陈旧且不可解析的 skill 冻结到会话目录中。运行时 `ctx.skills.register(...)` 仍作为嵌入式进程内 skill 的便捷方式保留,使用 project 优先于 user 的优先级;`runtime` 保留为注册表拥有的提供方名称。
本地提供方按先到先得的排名顺序扫描 cwd 敏感的项目根目录、自定义根目录和用户根目录:项目 `.dsh`、项目 `.agents``customSkillDirs`、用户 `.dsh`,然后是用户 `.agents`。用户 `.dsh/skills` 扫描跳过 `.system`,以免系统拥有的目录被当作普通用户内容处理。DeepSeek Harness 不随附内置系统 skill;嵌入式或远程提供方在配置后提供额外 skill。
@@ -22,7 +22,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和
本地 skill 的文件系统 I/O 在加载了文件系统服务时通过 `ctx.fs` 进行:项目根目录查找使用 `resolve``stat` 探测 `.git`,根目录发现使用 `listDir`skill 读取使用 `readText`。Node 文件系统作为后备,供在不挂载 fs seam 的最小上下文中加载 `dsh-skill-local` 时使用。缺失的根目录、不可读或格式错误的 skill 文件、以及提供方 `list()` 的瞬态失败均降级为警告并跳过,使一个坏源不会导致所有 agent 请求失败;格式错误的候选项仍然快速失败,因为它们违反了提供方契约。
`dsh-tool-skill` 通过 [`agent/session-prefix`](2026-07-07-session-prefix.md) 贡献一个 user-role `<system-reminder>` 目录。该目录仅包含排序后的 skill 名称与描述;不包含正文、路径、来源、提供方和路由提示。描述经过空白规范化、XML 转义,并受 `catalogDescriptionMaxLength` 上限约束,其默认值为 `500`,最小值为 `3`。session-prefix seam 将仅用于请求的目录按 loop 实例冻结,并记录在请求头中,在不将其入持久化历史的前提下保持可重建性。完整的 skill 正文从不包含在目录中
`dsh-tool-skill` 在会话的第一个 `agent/step` 注入一个持久化的 user-role `<system-reminder>` 目录,作为带来源的 `user/message`,且仅当该 agent 的工具视图解析到本插件精确的 `skill` 注册时才注入。该目录仅包含排序后的 skill 名称与描述;不包含正文、路径、来源、提供方和路由提示。描述经过空白规范化、XML 转义,并受 `catalogDescriptionMaxLength` 上限约束,其默认值为 `500`,最小值为 `3`。完整的 skill 正文从不包含在目录中。(目录最初通过仅请求的[会话前缀 seam](../../archived/feature/2026-07-07-session-prefix.md)(已归档)传递;[统一带来源消息的决策](../architecture/2026-07-22-unified-send-and-coalesced-user-messages.md)将其入持久化历史。
`skill({ name })` 工具为当前 agent cwd 加载一个完整 skill,返回包含 `<skill_content name="...">``<skill_resources>``<skill_instructions>` 的工具结果。`resourceBase` 提供一个目录、URL 或不透明的提供方管理的基路径,用于显式引用的脚本、参考资料和资产;资源仅按需加载,不进行目录枚举。无法解析的名称报告该 skill 未知或不再可用;无效名称和标记了 `disableModelInvocation` 的 skill 保留不同的工具错误。工具结果是面向模型的可见披露路径。
@@ -36,7 +36,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和
**将本地文件系统扫描直接放入 `ctx.skills`。** 否决,因为编码 agent、Web agent 和未来的插件生态需要不同的 skill 来源。提供方注册表与 subagent seam 镜像:注册表拥有冲突解决和消费方,实现拥有加载。
**使用系统提示词段落。** 否决,因为渲染后的系统提示词是单一字符串,而目录是一条具有仅请求生命周期要求的 user-role `<system-reminder>` 消息。[`agent/session-prefix`](2026-07-07-session-prefix.md) 是选定的机制:它将目录置于派生历史之前,并将组合后的消息记录在请求头中
**使用系统提示词段落。** 否决,因为渲染后的系统提示词是单一字符串,而目录是一条 user-role `<system-reminder>` 消息。[仅请求的会话前缀 seam](../../archived/feature/2026-07-07-session-prefix.md)(已归档)是最初的机制;统一带来源消息的决策移除该 seam 后,目录改为具有相同消息形状的持久化带来源注入
**在 `~/.dsh/skills/.system` 下物化内置 DSH 编写 skill。** 否决,因为打包的 skill 不应在启动时写入用户主目录,嵌入式或远程提供方在配置后提供 skill。
@@ -46,7 +46,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和
## 后果
agent-core 主干包含一个 session-prefix 贡献者、一个本地提供方和一个面向模型的工具。Skill 发现是 cwd 敏感的,因此以不同会话 cwd 值创建 agent 的调用方可以按设计观察到不同的项目 skill 覆盖。
agent-core 主干包含一个目录贡献者、一个本地提供方和一个面向模型的工具。Skill 发现是 cwd 敏感的,因此以不同会话 cwd 值创建 agent 的调用方可以按设计观察到不同的项目 skill 覆盖。
目录对于固定的根目录集合和运行时注册修订版本是确定性的,但不监视磁盘变化;发现结果被缓存,直到运行时注册使缓存失效或进程重启。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-24-web-session-model-selector.md
2026-07-24-web-session-model-selector.md: 4bd52bba8d3ba16c01fc59d2b561516f6fd0bb87
2026-07-24-web-session-model-selector.zh.md: 35f3525b80e7d27fb573e854aefc317ed354df53
@@ -0,0 +1,43 @@
# Agent Note: Session model selection in the Web composer
Status: implemented
English | [中文](2026-07-24-web-session-model-selector.zh.md)
## Problem
The Web conversation displayed and sent through the Host's fixed provider/model route without exposing that route or letting a user change it. The TUI already had a session-local route target, but copying its presentation or hardcoding DeepSeek models in the browser would split model discovery and step-boundary semantics across front doors. A switch made while a response is running also needs one atomic boundary: prompt variables and request routing cannot observe different targets.
## Decision
The Web Host reuses `installAgentLlmTarget` for every created or resumed agent. The provider/model/reasoning target starts from the latest `request/header` when the session has used a model, otherwise from the Host default route. `session.selectModel` changes the session-local mutable target, and prompt assembly captures it with request routing; a switch during a running step therefore applies to the next assembled step. The next consumed target persists through the existing full `request/header` snapshot, while a choice that has not reached a request remains process-local.
The session RPC domain exposes a `session.models` directory and `session.selectModel`. The directory is built dynamically from the LLM registry and grouped by provider; each listed model's exact metadata adds adapter-owned reasoning effort ids, names, descriptions, and optional default. Provider catalogs and exact metadata load concurrently by provider and fail independently, so successful groups remain usable alongside retryable failure records. Catalog membership stays advisory: the current model is inserted as an unlisted row when its registered provider omits it, while exact resolution decides whether a route and explicit effort are available. Selection uses `resolveCallConfig` to reject unsupported effort ids and materialize an adapter-configured default before updating the target.
The browser `ModelService` owns one `ModelDirectory` per live session. Its snapshot contains the current complete target, grouped catalog, provider failures, operation error, and `idle`/`loading`/`ready`/`selecting`/`error` state. Mounting primes the trigger label and each menu open refreshes the directory. Directory and selection calls share an operation generation so older responses cannot replace a newer result; connection reset discards the process-local projection before restoring the Host target. Failures retain the previous current target and usable groups.
`@deepseek-ai/dsh-client-ui-conversation` declares the session-scoped single slot `conversation.input.model` as a child of its composer-bar entry. InputBar renders the seat in its trailing controls immediately before the pending indicator and primary button; the seat receives the bar's `locked` owner prop and session scope. `@deepseek-ai/dsh-client-ui-model` occupies that seat and also contributes `/model` over the same directory. Its compact trigger displays the catalog model name and effective reasoning label, falling back to ids when metadata is absent. The upward menu first offers Model and, when the current exact model supports it, Effort; Model drills into provider groups, while Effort drills into the adapter-ordered levels. The provider-default row appears only when the adapter does not configure a model default.
The production browser roster is the flat config tree in `apps/cli/cordis.yml`; the model feature is one `dshClient` row rather than a package hardcoded in Web boot code. Its package manifest orders it after the runtime and command feature, while Cordis service injection waits for the conversation slot before registering the composer occupant.
## Alternatives considered
**Use separate provider and model dropdowns.** The model list depends on the provider and repeats a two-stage interaction for every change. One grouped menu keeps the provider visible as organization without lengthening the trigger or each row.
**Hardcode the current DeepSeek catalog in the Web client.** This would drift from registered adapters and exclude deployment-owned providers. The LLM registry remains the source of provider and model metadata, including partial lookup failures.
**Keep `High`/`Max` as client-local UI state.** Static DeepSeek labels cannot represent `off`, pi-ai provider vocabularies, adapter defaults, validation, resume, or the next provider request. Exact-model metadata owns the selectable vocabulary, and the session target owns the selected id.
**Make the selection a global default.** A global mutation would unexpectedly redirect other open conversations. The target belongs to one live session, while Host configuration remains the default for sessions without a logged request.
**Reject changes while an agent is running.** The shared atomic target already separates the assembled step from the next selection. Keeping the selector available lets the user prepare the following step without altering the in-flight request.
**Persist every click as a new session event.** A choice is not model-visible until prompt assembly consumes it. Persisting unused UI intent would add a durable event that does not reconstruct a model request; the existing `request/header` records the first request that actually uses the route.
## Consequences
Any Host-backed Web conversation, including a blank session, can switch among dynamically discovered provider groups and adapter-owned reasoning levels without displaying duplicated `provider/model` labels. The current consumed target survives resume and reconnect; catalog names remain presentation-only, while selection and persistence use provider/model/effort ids. A provider catalog or exact-metadata outage degrades only that group. Route changes can reduce provider-side cache reuse, but the selector adds no prompt content and does not disturb the in-flight step. A model without reasoning metadata has no Effort row.
## Testing
Host tests pin grouped discovery, catalog and exact-metadata failure isolation, logged effort restoration, unlisted current targets, unsupported effort rejection, default materialization, and next-assembly switching. Client tests pin the shared directory, reconnect restoration, and complete-target submission. Component tests pin dynamic effort labels, descriptions, provider-default exposure, and effort submission. The keyless built-app fixture loads the production model plugin, selects OpenAI's GPT-5 and its Max effort, sends a turn, and verifies that the next generated response reports both ids.
@@ -0,0 +1,43 @@
# Agent Note: Web 对话输入区的会话模型选择
Status: implemented
[English](2026-07-24-web-session-model-selector.md) | 中文
## 问题
Web 对话原本通过 Host 固定的提供方与模型路由显示并发送消息,既不呈现该路由,也不允许用户更改。TUI 已经具备会话级的路由目标,但如果照搬其呈现方式,或在浏览器中硬编码 DeepSeek 模型,就会让模型发现逻辑和步骤边界语义分散到不同前门中。响应运行期间发生的切换还需要一个原子边界:提示词变量与请求路由不能观测到不同的目标。
## 决策
Web Host 为每个新建或恢复的 agent(智能体)复用 `installAgentLlmTarget`。如果会话已经使用过模型,提供方/模型/推理(reasoning)目标从最新的 `request/header` 开始;否则采用 Host 默认路由。`session.selectModel` 会更改会话级可变目标,提示词组装则将该目标与请求路由一并捕获,因此运行中步骤发生的切换会应用于下一个组装步骤。下一条实际采用的目标通过现有的完整 `request/header` 快照持久化;尚未进入请求的选择则仅保存在当前进程中。
会话 RPC 领域公开 `session.models` 模型目录与 `session.selectModel`。该目录从 LLM(大语言模型)注册表动态构建,并按提供方分组;每个已列出模型的精确元数据还会加入由适配器持有的推理强度 ID、名称、说明和可选默认值。各提供方的目录与精确元数据会按提供方并发加载,且彼此独立失败,因此成功加载的分组仍可与可重试的失败记录一同使用。模型是否位于目录仅供参考:如果当前模型的已注册提供方没有列出该模型,系统会将其作为未列出行插入;精确解析则决定路由与显式推理强度是否可用。选择操作通过 `resolveCallConfig` 拒绝不支持的推理强度 ID,并在更新目标前具体化适配器配置的默认值。
浏览器中的 `ModelService` 为每个实时会话持有一个 `ModelDirectory`。其快照包含当前完整目标、分组目录、提供方失败记录、操作错误,以及 `idle``loading``ready``selecting``error` 状态。挂载时会预先填充触发器标签,此后每次打开菜单都会刷新目录。目录与选择调用共用操作代次,防止较早响应覆盖较新结果;连接重置会先丢弃当前进程中的投影,再恢复 Host 目标。失败时保留先前的当前目标和可用分组。
`@deepseek-ai/dsh-client-ui-conversation` 将会话作用域的单实例 slot `conversation.input.model` 声明为其输入栏 entry 的子 slot。InputBar 在尾部控件区将该 seat 渲染于 pending 指示器与主按钮之前;该 seat 接收输入栏的 `locked` owner prop 与会话作用域。`@deepseek-ai/dsh-client-ui-model` 占用该 seat,并在同一目录上提供 `/model`。其紧凑型触发器显示目录中的模型名称与生效的推理强度标签;元数据缺失时则回退到相应 ID。向上展开的菜单首先提供 Model,并在当前精确模型支持时提供 Effort;Model 可深入提供方分组,Effort 可深入适配器排序的级别。仅当适配器没有配置模型默认值时,才显示提供方默认值行。
生产环境的浏览器名册是 `apps/cli/cordis.yml` 中的平铺 config tree;模型功能对应其中一行 `dshClient` 配置项,而不是 Web boot 代码中硬编码的包。其包 manifest(元数据清单)将加载顺序置于运行时与命令功能之后;Cordis 服务注入则等待 conversation slot 可用,再注册 composer 占用方。
## 考虑过的替代方案
**分别使用提供方与模型下拉框。** 模型列表依赖提供方,每次更改都需要经过两阶段交互。单个分组菜单仍以提供方组织模型,同时不会增加触发器或各行的显示长度。
**在 Web 客户端中硬编码当前 DeepSeek 目录。** 该目录会与已注册适配器发生偏离,也会排除部署自有的提供方。LLM 注册表继续作为提供方与模型元数据的真源,也负责呈现部分查询失败。
**将 `High``Max` 保留为客户端本地 UI 状态。** 静态 DeepSeek 标签无法覆盖 `off`、pi-ai 的提供方词汇、适配器默认值与校验,也不能参与恢复或下一次提供方请求。精确模型元数据拥有可选词汇,会话目标则拥有已选择的 ID。
**将选择设为全局默认值。** 全局变更会意外改道其他已打开的对话。目标仅属于一个实时会话;对于没有已记录请求的会话,Host 配置仍是默认值。
**agent 运行期间拒绝更改。** 共享原子目标已经将当前组装步骤与下一次选择分离。保持选择器可用,可以让用户为下一个步骤预先选择模型,而不会改变正在执行的请求。
**将每次点击作为新的会话事件持久化。** 只有提示词组装采用某项选择后,该选择才对模型可见。持久化尚未使用的 UI 意图,会增加一个无法重建模型请求的持久事件;现有 `request/header` 会记录首次实际使用该路由的请求。
## 影响
任何由 Host 支撑的 Web 对话(包括空白会话)都可以在动态发现的提供方分组和适配器持有的推理级别之间切换,而无需显示重复的 `provider/model` 标签。当前实际采用的目标会在恢复和重连后保留;目录名称仅用于呈现,而选择和持久化使用提供方/模型/推理强度 ID。某个提供方的目录或精确元数据不可用时,只有相应分组会降级。路由变更可能降低提供方侧的缓存复用率,但选择器不会添加任何提示词内容,也不会干扰正在执行的步骤。没有推理元数据的模型不显示 Effort 行。
## 测试
Host 测试固定分组发现、目录与精确元数据失败隔离、已记录推理强度恢复、当前未列出目标、不支持的推理强度拒绝、默认值具体化,以及切换仅影响下一次组装。客户端测试固定共享目录、重连恢复与完整目标提交。组件测试固定动态推理强度标签、说明、提供方默认值展示与推理强度提交。无密钥 built-app fixture(测试前置数据)加载生产模型插件,选择 OpenAI 的 GPT-5 及其 Max 推理强度,发起一个轮次,并验证下一条生成的响应会报告两个 ID。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md
2026-07-27-typescript-sdk-and-sdk-subagent-backend.md: a1481bb9e2c3abfc3111dce8a1c38835436e3e13
2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md: 64a55f6aa0cb4b9a4a5efc625ece6a0800217f52
@@ -0,0 +1,47 @@
# Agent Note: TypeScript SDK client and the SDK subagent backend
Status: implemented
English | [中文](2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md)
## Problem
The stdio JSON-RPC serving surface (`@deepseek-ai/dsh-jsonrpc`, the [single-exe Agent Note](../architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md)) had exactly one client: the Python SDK. TypeScript consumers wanting the same drive-a-harness-as-a-subprocess capability — repo tests, automation, and above all a subagent backend whose child is a *complete harness runtime* rather than a generic ACP agent — had nothing to import: the request/notification payload shapes existed only as anonymous object literals inside the server, and the transport class lived inside the server plugin package.
## Decision
Three packages, layered exactly like the existing Python stack, plus one seam registration:
- **`@deepseek-ai/dsh-sdk-protocol`** (`packages/sdk/sdk-protocol/`) — the wire made shared and nominal. `JsonRpcLineTransport` moves here verbatim from `dsh-jsonrpc` (which now imports it), and `types.ts` names every payload the server speaks: `InitializeParams/Result`, `SessionPromptParams/Result`, the four notification payloads, and the `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` indexes. The server's `notify()` call sites are typed against these named payloads, so server drift breaks compilation, not clients. One behavioral change: an error response now rejects with `JsonRpcResponseError` carrying the wire `code`/`data` (the Python client already preserved these; the old transport threw a bare `Error` with only the message).
- **`@deepseek-ai/dsh-sdk-client`** (`packages/sdk/sdk-client/`) — the TypeScript twin of `python/sdk`: `HarnessClient` (spawn, frame, fan out notifications, typed error surfaces, close-to-quiescence via the shared dispose ladder) under `DeepSeekHarness`/`HarnessSession` (lazy start, memoized `initialize`, `run()` pairing one `session/prompt` with its `session.finished`). Session-tree scoping from `subagent.started` lineage edges is client-side, mirroring `client.py`. Deliberate asymmetries with Python: the launch spec is explicit `command`/`args` (no bundled-runtime resolution — that is a distribution concern with no TS consumer yet); `env` replaces rather than merges (callers own credential policy; `scrubbedParentEnv` from the subprocess seam is one import away); `TurnResult` carries the structured `reason` (Python exposes only `status`); teardown walks a private stdin-EOF → SIGTERM → SIGKILL ladder to actual exit (the client runs outside any harness context, so it cannot ride `ctx.subprocess`).
- **`@deepseek-ai/dsh-subagent-dsh-sdk`** (`packages/subagent/subagent-dsh-sdk/`) — the second out-of-process `SubagentProvider`, structured as `subagent-acp`'s sibling: same all-false capabilities and `inheritsParentContext: false`, same publish-after-handshake ownership transaction, same result-never-rejects flattening through an `onError` sink, same parent-namespace run id. The child answer is read from streamed `session.event`s — the last complete `assistant/message`, else accumulated `text-delta` chunks, so partial answers survive cancellation. Stop reasons map from the child's structured `TurnEndReason` (`completed`/`max-tokens`/`aborted` pass through; everything else, including a settled-without-turn child, is `error`). Its `provider`/`model` config feeds the child's `initialize`; `env` is where deployments pass the child's own key and `DSH_CORDIS_CONFIG`.
- **The subagent seam grows `out-of-process.ts`**: the provider-side vocabulary both out-of-process backends share — `NO_START_CAPABILITIES`, timing-bound validation, child cwd resolution (config override, else the delegating parent session's workspace), the never-reject `settleRunResult`, and the `subprocessRunHandle` publication. Process mechanics (spawn, env scrub, tree-scoped teardown) live in the `dsh-subprocess` seam; `subagent-acp` spawns through `ctx.subprocess`, while this backend spawns through the SDK client (the subprocess README's documented exception for SDK-managed transports) and applies the seam's `scrubbedParentEnv()` itself.
`dsh-jsonrpc` keeps serving unchanged (the wire is byte-identical); `dsh-jsonrpc-agent-pkg` (the Python runtime closure) gains the `dsh-sdk-protocol` dependency line.
## Testing
Four tiers, per [testing policy](../../../../docs/testing.md):
- **Keyless unit** — `sdk-client` drives a scripted fake runtime (`tests/fake-runtime.ts`, env-scripted, protocol-only — the Python `test_client.py` pattern) over real stdio; `subagent-dsh-sdk` drives the same fake through the real provider. 100% per-file coverage on all three packages.
- **Keyless Loader composition** — `subagent-dsh-sdk/tests/loader-composition.e2e.ts` boots a test-only cordis.yml (`examples/jsonrpc-agent/tests/fixtures/subagent/subagent-dsh-sdk/`) where the child is a REAL second harness runtime with its own cordis.yml; asserts the parent tool result and the child's own persisted transcript both carry the parent session's cwd. The child launch resolves through `resolveExampleLaunch`, so src/lib modes both hold.
- **Keyless snapshot** — `examples/jsonrpc-agent/tests/sdk.snapshot.ts` is the jsonrpc example's first snapshot suite: the real `dsh-jsonrpc-agent` runtime driven through the real `dsh-sdk-client`, replaying recorded fixtures via `llm-replay` behind the new `cordis.snapshot.yml` overlay (passed explicitly through `DSH_CORDIS_CONFIG`; the jsonrpc bin performs no snapshot config swap of its own). Three scenarios — text turn, bash tool, spawn subagent — each pinning the normalized notification stream, the SDK turn result, and the persisted parent+child logs. This also closes the protocol-tier gap the single-exe note's Python-side snapshot left on the vitest side.
- **With-key e2e** — the snapshot suite's `DSH_SNAPSHOT=record` mode is the live-API path (it produced the committed fixtures); the composition e2e needs no key by design.
## Alternatives considered
**Import wire types from `dsh-jsonrpc` instead of extracting a protocol package.** Makes every SDK consumer (including `subagent-dsh-sdk`, which must not serve JSON-RPC) depend on the server plugin and its `dsh-agent`/`dsh-llm-deepseek` peer set, and leaves the notification payloads anonymous. The capability-seam rule (interface/implementation/consumer as separate packages) already names this shape; the transport is genuinely two-sided.
**Have `subagent-dsh-sdk` speak raw JSON-RPC without the client SDK.** Duplicates the request/notification pairing, subscription fan-out, timeout, and teardown logic the SDK exists to own; the user's ask was explicitly a backend that *uses* the SDK, and the layering earns its keep by making the backend ~200 lines of policy over a reusable client.
**Fold the SDK backend into `subagent-acp` with a transport switch.** The two backends share the subprocess lifecycle but nothing about the wire (ACP SDK connection vs harness JSON-RPC), the child contract (any ACP agent vs a harness runtime), or the result extraction (`agent_message_chunk` accumulation vs session-event reading). A config discriminant would bury two protocols in one package; the genuinely shared provider-side parts moved into the subagent seam's `out-of-process.ts`, and the process mechanics live in the `dsh-subprocess` seam.
**Give the TS SDK bundled-runtime resolution parity with Python.** Python's carrier resolution exists to ship wheels to users without Node. A TypeScript consumer definitionally has Node and (in-repo) the workspace; inventing a distribution story with no consumer violates the require-current-need rule. Deferred until a real npm-distribution consumer appears.
**Reuse `dsh-acp-snapshot`'s `runScenario` for the SDK snapshots.** That harness speaks ACP (`ClientSideConnection`, `InputStep` scripts). The SDK suite's whole point is to drive the *SDK client* as the entry surface; it reuses the normalize/refresh library layer (`normalizeSessionLog`, `refreshFixtureReplacements`, …) and leaves the ACP driver alone.
## Consequences
**Bought**: the SDK runtime protocol now has named, compiler-checked types shared by its server and both client SDKs; TypeScript consumers get the same subprocess-driving capability Python has, with typed errors and structured turn reasons; the subagent seam gains a harness-native out-of-process backend whose children are full peers (own config, persistence, tools) — the recursive-composition story the seam note anticipated; the jsonrpc example finally has snapshot coverage, through the SDK path itself.
**Paid**: a third package in the `sdk/` group and a fourth subagent backend to keep current; the SDK backend boots a complete plugin tree per child (heavier per-run than an ACP child; pooling remains future work, same as ACP); the wire still has no cancel method, so both the SDK's `RequestTimeoutError` and the backend's dispose settle locally while the server-side turn runs on until process teardown; fixtures for the snapshot suite were recorded against `deepseek-v4-flash` and re-record on model-behavior drift like every other recorded corpus.
@@ -0,0 +1,47 @@
# Agent Note: TypeScript SDK 客户端与 SDK subagent 后端
Status: implemented
[English](2026-07-27-typescript-sdk-and-sdk-subagent-backend.md) | 中文
## Problem
stdio JSON-RPC 服务表面(`@deepseek-ai/dsh-jsonrpc`,见[单文件可执行 Agent Note](../architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md))当时只有一个客户端:Python SDK。想要同样"把 harness 作为子进程驱动"能力的 TypeScript 消费者——仓库测试、自动化,尤其是一个其子进程是*完整 harness 运行时*(而非通用 ACP 代理)的 subagent 后端——无物可导入:请求/通知载荷形状只以匿名对象字面量存在于服务器内部,传输类也躺在服务器插件包里。
## Decision
三个包,分层与既有 Python 栈完全一致,外加一个接缝注册:
- **`@deepseek-ai/dsh-sdk-protocol`**`packages/sdk/sdk-protocol/`)—— 把线协议做成共享且具名。`JsonRpcLineTransport``dsh-jsonrpc` 原样移入(后者现在导入它),`types.ts` 为服务器所说的每个载荷命名:`InitializeParams/Result``SessionPromptParams/Result`、四个通知载荷,以及 `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` 索引。服务器的 `notify()` 调用点以这些具名载荷标注类型,服务器漂移会先破坏编译而不是破坏客户端。一处行为变化:错误响应现在以携带线上 `code`/`data``JsonRpcResponseError` 拒绝(Python 客户端本就保留这些;旧传输只抛携带消息的裸 `Error`)。
- **`@deepseek-ai/dsh-sdk-client`**`packages/sdk/sdk-client/`)—— `python/sdk` 的 TypeScript 孪生:`HarnessClient`(生成、分帧、通知扇出、有类型的错误表面、经共享处置阶梯关闭至静止)之上是 `DeepSeekHarness`/`HarnessSession`(惰性启动、记忆化 `initialize``run()` 把一个 `session/prompt` 与其 `session.finished` 配对)。基于 `subagent.started` 血缘边的会话树范围限定在客户端完成,镜像 `client.py`。与 Python 的刻意不对称:启动规格是显式 `command`/`args`(无捆绑运行时解析——那是尚无 TS 消费者的发行问题);`env` 整体替换而非合并(凭据策略归调用方;subprocess 接缝的 `scrubbedParentEnv` 一个 import 即得);`TurnResult` 携带结构化 `reason`Python 只暴露 `status`);拆除走私有的 stdin-EOF → SIGTERM → SIGKILL 阶梯直到真正退出(客户端运行在任何 harness 上下文之外,无法搭乘 `ctx.subprocess`)。
- **`@deepseek-ai/dsh-subagent-dsh-sdk`**`packages/subagent/subagent-dsh-sdk/`)—— 第二个进程外 `SubagentProvider`,以 `subagent-acp` 的同胞结构组织:同样的全 false 能力与 `inheritsParentContext: false`,同样的握手后发布所有权事务,同样的经 `onError` 汇把结果压平为绝不拒绝,同样的父命名空间 run id。子答案从流式 `session.event` 读取——最后一条完整 `assistant/message`,否则累积的 `text-delta` 块,部分答案在取消时得以保留。停止原因由子进程的结构化 `TurnEndReason` 映射(`completed`/`max-tokens`/`aborted` 直通;其余一切、包括未跑回合就尘埃落定的子进程,都是 `error`)。其 `provider`/`model` 配置喂给子进程的 `initialize``env` 是部署传入子进程自有密钥与 `DSH_CORDIS_CONFIG` 的地方。
- **subagent 接缝增长出 `out-of-process.ts`**:两个进程外后端共享的 provider 侧词汇——`NO_START_CAPABILITIES`、时限校验、子进程 cwd 解析(配置覆盖、否则发起委托的父会话工作区)、绝不拒绝的 `settleRunResult`、以及 `subprocessRunHandle` 发布。进程机制(spawn、环境擦除、进程树拆除)属于 `dsh-subprocess` 接缝;`subagent-acp``ctx.subprocess` 生成子进程,本后端则经 SDK 客户端生成(subprocess README 记载的 SDK 托管传输例外)并自行应用接缝的 `scrubbedParentEnv()`
`dsh-jsonrpc` 的服务不变(线上字节完全一致);`dsh-jsonrpc-agent-pkg`Python 运行时闭包)增加 `dsh-sdk-protocol` 一行依赖。
## Testing
四层,依[测试政策](../../../../docs/testing.md)
- **免密钥单元** —— `sdk-client` 通过真实 stdio 驱动脚本化伪运行时(`tests/fake-runtime.ts`,环境变量脚本化、纯协议——即 Python `test_client.py` 的模式);`subagent-dsh-sdk` 经真实 provider 驱动同一伪运行时。三个包全部 100% 逐文件覆盖。
- **免密钥 Loader 组合** —— `subagent-dsh-sdk/tests/loader-composition.e2e.ts` 启动仅测试用 cordis.yml`examples/jsonrpc-agent/tests/fixtures/subagent/subagent-dsh-sdk/`),其中子进程是真实的第二个 harness 运行时、带自己的 cordis.yml;断言父工具结果与子进程自己持久化的转录都携带父会话 cwd。子启动经 `resolveExampleLaunch` 解析,src/lib 两种模式都成立。
- **免密钥快照** —— `examples/jsonrpc-agent/tests/sdk.snapshot.ts` 是 jsonrpc 示例的第一个快照套件:真实 `dsh-jsonrpc-agent` 运行时经真实 `dsh-sdk-client` 驱动,在新的 `cordis.snapshot.yml` 覆盖层后经 `llm-replay` 回放已录制夹具(经 `DSH_CORDIS_CONFIG` 显式传入;jsonrpc bin 自身不做快照配置切换)。三个场景——文本回合、bash 工具、spawn 子代理——各自钉住规范化通知流、SDK 回合结果与持久化的父+子日志。这也补上了单文件可执行 Note 的 Python 侧快照在 vitest 侧留下的协议层缺口。
- **带密钥 e2e** —— 快照套件的 `DSH_SNAPSHOT=record` 模式即真实 API 路径(已提交夹具由它产出);组合 e2e 设计上无需密钥。
## Alternatives considered
**从 `dsh-jsonrpc` 导入线类型而不是提取协议包。** 会让每个 SDK 消费者(包括绝不能提供 JSON-RPC 服务的 `subagent-dsh-sdk`)依赖服务器插件及其 `dsh-agent`/`dsh-llm-deepseek` peer 集合,且通知载荷仍然匿名。能力接缝规则(接口/实现/消费者三包分立)已经点名了这种形态;这个传输是货真价实的双边物。
**让 `subagent-dsh-sdk` 直说裸 JSON-RPC、绕开客户端 SDK。** 会复制 SDK 存在意义所在的请求/通知配对、订阅扇出、超时与拆除逻辑;用户的要求明确是一个*使用* SDK 的后端,分层的回报是后端成为可复用客户端之上约 200 行的纯策略。
**把 SDK 后端折进 `subagent-acp`、用传输开关区分。** 两个后端共享子进程生命周期,但线协议(ACP SDK 连接 vs harness JSON-RPC)、子进程契约(任意 ACP 代理 vs harness 运行时)、结果提取(`agent_message_chunk` 累积 vs 会话事件读取)毫无共享。配置判别子会把两个协议埋进一个包;真正共享的 provider 侧部分移入 subagent 接缝的 `out-of-process.ts`,进程机制则住在 `dsh-subprocess` 接缝。
**给 TS SDK 与 Python 对等的捆绑运行时解析。** Python 的载体解析是为了给没有 Node 的用户发 wheel。TypeScript 消费者定义上就有 Node 且(仓库内)有工作区;为不存在的消费者发明发行故事违反"要求当前需求"规则。推迟到真实 npm 发行消费者出现。
**复用 `dsh-acp-snapshot` 的 `runScenario` 做 SDK 快照。** 那个 harness 说 ACP`ClientSideConnection``InputStep` 脚本)。SDK 套件的全部意义就是以 *SDK 客户端*为入口表面;它复用 normalize/refresh 库层(`normalizeSessionLog``refreshFixtureReplacements`……),不动 ACP 驱动器。
## Consequences
**买到**:SDK 运行时协议现在拥有服务器与两个客户端 SDK 共享的、编译器校验的具名类型;TypeScript 消费者获得与 Python 相同的子进程驱动能力,且带类型化错误与结构化回合原因;subagent 接缝获得一个 harness 原生的进程外后端,其子进程是完整对等体(自有配置、持久化、工具)——正是接缝 Note 预期的递归组合故事;jsonrpc 示例终于有了快照覆盖,而且走的就是 SDK 路径本身。
**付出**`sdk/` 组多了第三个包、subagent 多了第四个要保持最新的后端;SDK 后端每个子进程启动完整插件树(单次成本高于 ACP 子进程;池化与 ACP 一样留作未来工作);线上仍无取消方法,SDK 的 `RequestTimeoutError` 与后端的 dispose 都只在本地定格、服务器侧回合继续跑到进程拆除为止;快照夹具录制于 `deepseek-v4-flash`,与其他录制语料一样随模型行为漂移而重录。

Some files were not shown because too many files have changed in this diff Show More