The `cordis` preset's `editing-cordis-compositions` skill is the only guidance an agent has when it authors a preset, and four of its statements were false. `tool-bash` was named as the worked example of a row that hides a service; it provides nothing and injects `bashEnv` from the host's own `bash-env` row, so following that advice strands the row behind its realm and the preset fails to mount. The `isolate` example composed `tasks-local` with `tool-tasks`, which the shipped compositions' own comments say breaks `run_in_background`. A string realm label was described as pooling one instance; labels join realms and `provide()` still throws on the second registration. Rows were to be checked against a package README, which no harness package publishes. Verification is now the agent's own: `standingKeyFor(id)` runs the same mount a session start performs and rejects an unresolvable package, an invalid config, a service in the root realm, and a row that never activated. The skill states that `list()`'s `broken` field is a shape check that every one of those passes, ships the `cordis_mount` plugin that reaches the roster service, and names `copy()` as the authoring write. The prohibition on touching the shipped install is promoted to its own section and extended to the host composition. Fixes #2266
5.1 KiB
Agent Note:损坏的 preset 是名单行,不是空缺
Status: implemented
English | 中文
问题
文件成为唯一的组装编辑器之后,手动编辑造成的损坏有两种形态,且都要拖到最糟的时刻才暴露。agent.cordis.yml 解析不了的 preset 在名单上是一张完全正常的行——可选择、可复制、可设为默认——直到下一个会话尝试挂载才失败;一旦被设为默认,所有新会话都无法启动。组装文件被整个删掉的目录则从名单上消失,却仍在磁盘上占着它的 id:copy 以「先删除既有 preset」拒绝这个名字,remove 却回答「找不到」——两条互相矛盾的错误,除了手动删目录别无出路。
决定
发现过程负责健康,受损目录是携带 broken 原因的名单行,绝不是空缺。scanRoot 把名字是可用 preset id 的每个目录都当作一个 preset 槽位:组装缺失 → broken(「仍占着该 id;删除目录或恢复文件」),组装不可读/解析失败/不是具名行列表 → broken 并携带解析器的首行。形状检查用加载器自己的 entryListSchema(含 !!js 的方言)解析,因此健康检查绝不会把加载器接受的组装叫作损坏;名字不符合 PRESET_ID 的目录直接跳过,因为复制永远不可能与之相撞。broken 依次落在 AgentPreset、agentPreset.list 的线上条目和 UI 行上。挂载路径(mount/recompose/standingKeyFor)经 resolveMountable 用发现时记下的原因在前置拒绝;resolve 照样应答(删除/读取/上报都需要这一行),而 copy 的名单检查现在看得见幽灵,让「已存在」的拒绝变得可操作——要删的损坏卡片就在同一页上。
界面按职责分开:管理区把损坏行渲染为标记卡片(红边、「已损坏」徽记、原样展示原因、卡片主体与复制禁用,自定义行保留位置与删除——文件正是修复处,删除正是幽灵的出路;损坏的内置行连查看器也不给),而两个选择器(通用设置行、新会话 chip)经 presetOptions 完全不列损坏的 preset——它们选的是下一个会话的组装,端出无法组装的选项只会推迟失败。
后果
- 幽灵死路端到端消除:目录以损坏行列出,删除即清掉,释放的 id 立刻可用(单测、组件测试与 e2e 各自覆盖)。
- 事后才损坏的默认值仍会在会话启动处大声失败——选择器隐藏损坏行,但没有任何东西改写已存的默认;
resolveMountable的前置拒绝让每种不可加载形态得到同一条消息,而不是依赖加载器内部的报错。 - 健康检查随每次
list()运行:每次读名单对每个 preset 一次读取加解析,接受的理由与不做缓存的发现相同——名单很小,新鲜是契约。 - 复制损坏 preset 只在 UI 层拒绝(按钮禁用并给出原因);宿主的
copy保持形状无关。损坏来源产出同样损坏、同样可见的副本——没有能力增益,而宿主侧拒绝需要为一条被禁用按钮挡住的路径专门发明错误词汇。
关键细节
PRESET_ID移到types.ts,让发现与创作共享同一份包含边界词汇;authoring 原样转发导出。- 原因只留一行。 js-yaml 会附上多行代码框摘录;名单卡片不是终端,
compositionProblem只保留首行。 - mount.spec 的两个竞态用例特意不动:
ensureStanding仍可能拿到删除前一刻解析出的 preset(私有路径测试),其 stamp/unstampable 语义不变——健康检查发生在此之前的公开路径上。 - 创造模式的引导随同一 PR 落地:
cordispreset 的 persona 禁止编辑随附安装(损坏cordis会禁用这一模式本身),并把创作指向${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/;其技能教了preset.yml元信息、先复制再改的流程与一次升级的沙箱现实(preset 根目录在会话工作区之外)。已实测:被要求直接改随附cordis组装时,组装出的 agent 援引两条规则拒绝并给出复制路径;被要求真正创建 preset 时,它落在$DSH_HOME下并把写入合并为一次升级。该引导中关于验证的那一半——agent 无法自己启动会话,因而设置页的红色标记是用户的检查项——已由创作 preset 的 agent 自行挂载校验其组装取代:下文的结构检查不是校验,而standingKeyFor才给了 agent 真正的校验手段。本篇的健康检查决策不变。
曾考虑的替代方案
隐藏损坏 preset 但在复制时用更好的报错拒绝该 id:幽灵仍然无法从任何界面清除。深度校验(读名单时解析每一行的模块):挂载已经拥有这一失败并带回滚,每次读名单逐行 import 既不便宜也不更可操作。阻止 settings 写入指向损坏默认值:settings 领域是通用的,而名单是活目录——此刻缺失或损坏的名字到下一个会话可能已经有效,挂载的响亮失败才是拥有那一刻的强制点。