Files
deepseek-harness/.agents/notes/archived/simplification/2026-08-08-copy-only-preset-authoring.zh.md
T
Turtle d1e22a7e24 feat(preset): declare Agent compositions in profile YAML (#4569)
* feat(preset): declare Agent compositions in profile YAML

* fix(preset): share scope binding cleanup and refresh module graph

* fix(preset): use host syntax colors in the YAML editor

* fix(preset): write new profile declarations as block YAML

* fix(preset): align eager activation with browser and worker surfaces

* fix(ui-agent-preset): retain the existing preset card layout

* fix(preset): keep rows waiting for a Host service mounted and re-audit after settlement

A preset row whose Host provider was still activating at declaration time
was reported as waiting for a missing service, and the registry recorded the
definition as broken for the rest of the process. Split the mount audit into
failed and pending rows: failures and root-realm leaks still reject the
mount, while pending rows stay mounted and every read and binding re-audits
them after the Host Loader tree settles.

Remove the accidental self-dependency of @deepseek-ai/dsh-web-app and its
lockfile self-link. Archive the copy-only preset authoring note, which the
declarative-preset note fully supersedes, and update the ralph note's
restore recipe to the profile-patch mechanism.

* chore(lint): re-key and prune the unknown-cast baseline after the master merge

* refactor(web-app): ship each Agent preset as its own bundle patch file

`dsh.bundle.patch` accepts an ordered file list, so the four shipped preset
declarations move out of the 1000-line web patch into
`presets/<id>.patch.yml`, one insert per file, applied after the host patch.
The host patch keeps the preset registry row. Gates and test scaffolds that
read the web layer resolve every patch file through `bundlePatchPaths`
instead of one hardcoded path.

* refactor(preset): drop the save notice injected into live Agents

The notice duplicated the save result the Web editor already shows, and
after the producer-owned message sources it would have needed its own
persisted source kind. Saving now only persists and reports the activation
diagnostic; the persistence catalog is unchanged from master.

* docs: regenerate the module graph after dropping the registry's llm dependency

* test(ui-agent-preset): cover the guide dialog's keyboard handling on the declarative section

* fix(preset): address the remaining review comments

Apply the lowercase id rule only when creating a declaration, so presets
declared with other ids stay editable. Export `isBuiltInPreset` from the
display fold and key the client card groups on it. Remove the dead `error`
and `retry` locale keys, the JSDoc orphaned by the `trust` removal, the
`trust` fields in the plugin-inventory test fixtures, and the stale
`dsh-agent-presets` package name in comments and READMEs.

* refactor(preset): make the Web preset page view-and-select only

Remove the child-plugin YAML editor, the `agent_preset` tool and the
registry's read/save Remote methods. Presets are declared and changed only by
bundle patches; the settings page keeps the roster, the new-task default and
the mode help, and its question icon explains that and offers to start a
Creator-mode task. The `editing-cordis-compositions` skill and the Creator
persona describe the bundle-based path, and the authored skill-load Session
and Web snapshots are refreshed for it.

* docs(skill): make the composition skill self-sufficient for preset authoring

State where the shipped declarations live, list every declaration field,
inline a complete minimal bundle and an override example, and name the
verification calls, so Creator mode no longer reads DSH package sources to
infer the format. Refresh the authored skill-load Session for the new text.

* test(tools): drop the removed agent_preset tool from the catalog expectation
2026-09-21 08:24:18 +00:00

4.3 KiB
Raw Blame History

Agent Note: 仅复制的 preset 创作,与通往 preset 文件的入口

Status: implemented Archived: 2026-09-20

English | 中文

问题

agent-preset 设置页带着一个网页 YAML 编辑器:agentPreset.write 接收任意组装文本,页面是一个没有补全、高亮或 diff 的文本域,形状检查依赖 Loader 自己的 entryListSchema——其方言含 !!js,所以「过了形状检查的文本」在下一次挂载时仍是任意代码。作为编辑器很弱,作为能力很宽,还是该分区不得不防御的「编辑器 vs 名单」竞态的来源。

决策

创作改为宿主端复制,文件就是编辑器。agentPreset.write 变为 agentPreset.copy { from, agentPreset, name? }:两个由宿主对照自身根目录解析的 id 加一个可选显示名,整目录 cp(符号链接解引用,权限收紧为仅属主并保留属主执行位),元数据重写为保留来源描述、但绝不保留其名称或 order。页面包含随附组装的只读查看器、作为唯一创建入口的复制对话框(不提供空白「新建预设」)、自定义行的删除,以及通向文件的位置操作。settings/openAgentPresetDirectory { agentPreset } 在 Host 侧解析目录并原生打开,部署没有桌面时回答 { opened: false, path } 供该行以文本形式展示;settings/canOpenAgentPresetDirectory 控制该行是否显示,Settings Controller 的 nativeOpen 则在平台探测可能误判时固定服务端行为。

后果

  • 创作两个方向都没有组装文本或路径跨越浏览器传输层;entryListSchema/!!js 的顾虑随 assertComposition 本身(已删除)一并消解。创作操作是 read/copy/openDocument/remove——没有一个接收文件系统目标,且 Connection 用完整 Host API 的同一会话认证它们。
  • 编辑器移除后,手改 agent.cordis.yml 成为唯一的组装编辑方式,因此常驻挂载层增加了以 stamp 为键的代际:ensureStanding 比对文件的 mtime+大小,为后续会话开启下一代际(常驻挂载 note,已就地更新)。没有它,改过的文件要等进程重启才生效。
  • 副本是完整快照,会随随附来源升级而漂移——接受;preset 层没有 patch 语义(那是 bundle 层 cordis.patch.yml 的能力),随附集合自己也为「一个文件读完整份组装」付了同样的代价(cordis/code 就是 standard 的完整副本)。
  • read 去掉了 writable(没有编辑器可门控),内置目录绝不被打开(openDocument 与 remove 一样拒绝非 user 信任):安装目录会被升级覆盖,把编辑器指向它等于招揽会被升级悄悄丢弃的编辑。

关键实现细节

  • 复制目标的拒绝刻意分两道检查。 roster 检查拒绝任一根目录提供的 id——与随附 preset 同名的用户目录会被遮蔽,「创建」只会落下一个永远不被列出的文件;磁盘检查(cp 之前的 PresetExistsError,errorOnExist 作竞态兜底)拒绝占着名字却不是 preset 的目录,那是 discovery 看不见的。
  • 展示的路径是响应方向的披露,且经过浏览器认证。「没有任何浏览器载荷能选中任意文件系统目标」这条不变量说的是请求方向;把解析出的目录展示给已认证浏览器正是方案要求的降级。它绝不搭乘 list。
  • e2e lane 钉死 nativeOpen: false(agent-preset-authoring.overlay.yml)——既让 golden 在 macOS 开发机与无头 Linux CI 上渲染同一分支,也让测试运行永不弹出真实文件管理器。揭示的目录由 lane 自己 token 化为 {{presetRoot}},因为 normalizeAria 只认识 workspace cwd。

考虑过的替代方案

保留 write 换个更好的编辑器(CodeMirror 等):传输层上仍是任意能力,仍是竞态来源,而且仍不如用户自己的编辑器。带 patch 语义的副本(「standard 加这点 diff」):bundle 面之下没有这样的层,仓库自己的随附 preset 也刻意选了完整副本。浏览器端拿返回路径调 session/openWorkspacePath:路径一旦成为请求参数,就打破了 README 的「不可选中任意目标」不变量。