The web YAML editor is gone. agentPreset.write (arbitrary composition
text) became agentPreset.copy { from, agentPreset, name? }: a host-side
whole-directory copy of ids the host resolves itself — symlinks
dereferenced, modes re-tightened to owner-only with owner-execute kept,
metadata rewritten to keep the source's description but never its name or
roster order. No composition text or path crosses the wire in either
authoring direction, and the entryListSchema/!!js concern dissolves with
assertComposition itself.
The settings section becomes: a read-only viewer over shipped
compositions, a copy dialog (id + optional display name) as the only
create entry, delete for custom rows, and a location action leading into
the preset's own files — agentPreset.openDocument { agentPreset } resolves
the directory host-side and opens it natively, or answers
{ opened: false, path } for the row to show as text where the deployment
has no desktop. agentPreset.list reports hasDocument beside authorable;
the gateway's nativeOpen config pins the capability where
canOpenNativePath platform detection would mislead. The privileged set is
now read/copy/openDocument/remove.
With files as the only composition editor, standing mounts grew
stamp-keyed generations: ensureStanding compares the composition file's
mtime+size and starts the next generation for later sessions, while every
joined session keeps the generation it runs on.
New keyless web lane (agent-preset-authoring, overlay pins
nativeOpen: false so goldens render one branch on every platform) drives
view/copy/reveal/delete end to end; the real-composition CLI e2e switches
to copy semantics.
4.2 KiB
Agent Note: 仅复制的 preset 创作,与通往 preset 文件的入口
Status: implemented
English | 中文
Problem
agent-preset 设置页带着一个网页 YAML 编辑器:agentPreset.write 接收任意组装文本,页面是一个没有补全、高亮或 diff 的文本域,形状检查依赖 Loader 自己的 entryListSchema——其方言含 !!js,所以「过了形状检查的文本」在下一次挂载时仍是任意代码。作为编辑器很弱,作为能力很宽,还是该分区不得不防御的「编辑器 vs 名单」竞态的来源。
Decision
创作改为宿主端复制,文件就是编辑器。agentPreset.write 变为 agentPreset.copy { from, agentPreset, name? }:两个由宿主对照自身根目录解析的 id 加一个可选显示名,整目录 cp(符号链接解引用,权限收紧为仅属主并保留属主执行位),元数据重写为保留来源描述、但绝不保留其名称与 order。页面变为:随附组装的只读查看器、作为唯一创建入口的复制对话框(不再有空白「新建预设」——从零手写 YAML 不是人会做的事)、自定义行的删除,以及通向文件的位置操作——agentPreset.openDocument { agentPreset } 在宿主端解析目录并原生打开,部署没有桌面时回答 { opened: false, path } 供卡片以文本展示(list 上的 hasDocument;在 canOpenNativePath 平台探测会失真处由网关的 nativeOpen 配置钉死,例如 e2e 与容器)。
Consequences
- 创作两个方向都不再有组装文本或路径跨越浏览器传输层;
entryListSchema/!!js的顾虑随assertComposition本身(已删除)一并消解。特权集现为read/copy/openDocument/remove——没有一个接收文件系统目标。 - 编辑器移除后,手改
agent.cordis.yml成为唯一的组装编辑方式,因此常驻挂载层增加了以 stamp 为键的代际:ensureStanding比对文件的 mtime+大小,为后续会话开启下一代际(常驻挂载 note,已就地更新)。没有它,改过的文件要等进程重启才生效。 - 副本是完整快照,会随随附来源升级而漂移——接受;preset 层没有 patch 语义(那是 bundle 层
cordis.patch.yml的能力),随附集合自己也为「一个文件读完整份组装」付了同样的代价(cordis/code就是standard的完整副本)。 read去掉了writable(没有编辑器可门控),内置目录绝不被打开(openDocument与remove一样拒绝非user信任):安装目录会被升级覆盖,把编辑器指向它等于招揽会被升级悄悄丢弃的编辑。
Load-bearing details
- 复制目标的拒绝刻意分两道检查。 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。
Alternatives considered
保留 write 换个更好的编辑器(CodeMirror 等):传输层上仍是任意能力,仍是竞态来源,而且仍不如用户自己的编辑器。带 patch 语义的副本(「standard 加这点 diff」):bundle 面之下没有这样的层,仓库自己的随附 preset 也刻意选了完整副本。浏览器端拿返回路径调 host.openPath:路径一旦成为请求参数,就打破了 README 的「不可选中任意目标」不变量。