# 删除一个预设(干净删除 · 操作指引) > **适用**:DSH **0.1.7-alpha.1 及以后**(预设=声明式)+ 本插件 **2.1.0 及以后**生成的预设。 > **本文只讲"删",不讲"重建"。** > **路径一律用占位符**:`<工作区>`/``(宿主数据目录,默认 `~/.dsh`)/``(profile 名, > 一般 `web`)/`<预设 id>`/`<预设显示名>`。**判据一律放在文件内容里,不靠目录名猜。** > 相关:[迁移与兼容](迁移与兼容.md)(老版本目录式预设怎么办)|[反复测试:删除流程](反复测试-清理流程.md)(清测试痕迹) --- ## 0. 先记住三句话 1. **新版没有"删除预设"这个按钮。** 预设不是一个文件,而是**某个可安装 bundle 里的一行声明**。 设置页里只能**查看 / 选择 / 设为默认**;能真正让预设消失的,是**卸载它所在的 bundle**, 或**把那一行声明从它所在的包补丁里删掉**。 2. **"删了半个"是最常见的状态**:依赖摘了、工作区里的目录还在;或目录删了、依赖还挂着。 两者都会让宿主重启时报"跳过该 bundle",并让**用过该预设的会话被拒绝恢复**。 3. **真正的不可逆点只有两个**:**(a) 改了 `config.id`**(会话记的就是它);**(b) 没有备份且忘了原内容**。 其余情况都有救 —— 见 §5。 --- ## 1. 你面对的是哪种形态(先分清,再动手) | 形态 | 长什么样 | 删除方式 | |---|---|---| | **A. 本插件生成、独立成包**(2.1.0+ 默认) | `<工作区>/.dsh/preset-bundles/<预设 id>/`,里面**恰好两件**:`package.json` + `cordis.patch.yml`;patch 里有生成标记(本插件包名) | ✅ **四步法**(§2,另加两件收尾:清痕、重启验收)—— 推荐路径 | | **B. 与别的预设共用一个组合包** | 某个包的一份 `cordis.patch.yml` 里有**多条** `- id: preset-*` 声明(一份 patch 可以声明很多个预设) | ⚠️ **不能卸包**(会带走同包所有预设)⇒ **改声明段**(§3) | | **C. 老版本留下的目录式预设**(≤0.1.6) | `/.agent-presets//`(`preset.yml` + `agent.cordis.yml`) | 自 0.1.7-alpha.1 起**已无人读取** ⇒ 删它只是"清痕"(§4) | **先做一次只读体检**(本插件自带,不加 `--yes` 时**只列清单、不删任何东西**): ```sh node scripts/clean-test-state.mjs --scan --workspace <工作区> ``` 它会告诉你三件事:①哪些预设是**本插件生成**的(判据=patch 里的生成标记,不是目录名); ②预设根里有没有**疑似测试残留**;③工作区状态里有没有**死引用**(`known.json` 记着、预设根却没有)。 > ⚠️ 别用**裸 `--yes`** 去删单个预设 —— 那个组合会删掉该预设根下**所有**本插件生成的预设。 > 只想删一个 ⇒ 用 `--only <预设 id>`(只删目录),并**先做完 §2 第 2 步**(见下)。 --- ## 2. 形态 A:四步法(+两件收尾:清痕、验收) ### 第 1 步(可选 · 可逆)· 先把这一行"关掉" 目的只是**确认就是它**、且随时能反悔: - 界面:主侧栏 **「插件」** → 打开包含该预设的包(形态 A 下就是 `@local/dsh-learn-preset-<预设 id>`)→ 关掉 `preset-<预设 id>` **这一行**; - 或让 Agent 调 `plugin_manager`(`action: set_plugin`,`target`=该行的 entry id,`enabled: false`)。 > 📌 **要点:行开关 ≠ 删除。** 它只是往 profile 用户补丁写一条 `disabled` 覆盖 > (`/profiles//cordis.patch.yml`),**包本体一个字都不动**; > 名册里它会消失(新会话选不到),但文件里那行还在 ⇒ **第 4 步要清掉这条覆盖**。 > ⚠️ **只点这一行的开关**;**不要**点同页的**整包开关 / 卸载**(形态 A 下这个小包里只有一个预设, > 但还是养成习惯:先看清这个包里声明了几个)。 ### 第 2 步 · 卸载 bundle 选一条: ```sh # ① 终端一行(最省事) dsh plugin --profile remove @local/dsh-learn-preset-<预设 id> # ② 界面:主侧栏「插件」→ 该包 → 卸载(会弹二次确认) # ③ 「创造模式」会话里让 Agent 调 plugin_manager # action=remove_bundle, target=@local/dsh-learn-preset-<预设 id>(会弹审批卡) ``` - **判据**:`plugin_manager` 的 `list_plugins` 里那行 `preset-<预设 id>` **消失**; - 目标 profile 开着 HMR 时卸载立即生效;没有 HMR 的 profile 会要求你先停服务再卸; - **别点错层级**:卸载的是 `@local/dsh-learn-preset-<预设 id>` 这个**小包**, 不是别人那个"一份文件里塞了十几个预设"的大包。 ### 第 3 步 · 删掉工作区里的 bundle 目录 ```sh # 目录:<工作区>/.dsh/preset-bundles/<预设 id>/ node scripts/clean-test-state.mjs --only <预设 id> --workspace <工作区> --yes # 或直接删目录(手动删也行,删之前先确认第 2 步已完成) ``` - **顺序必须在第 2 步之后**:先删目录、依赖还挂着 ⇒ 宿主会把该 bundle 判为"跳过", 并且**用过该预设的会话在重启后被拒绝恢复**; - 反过来"只卸依赖、目录留着"是无害的(多占一点磁盘),但**不算干净**。 ### 第 4 步 · 清掉本插件的占用记录(**很容易漏,且会咬人**) 编辑 `<工作区>/.dsh/learn-skills/known.json`,把这两个数组里的对应项删掉: ```json { "presetNames": ["<预设显示名>"], "presetIds": ["<预设 id>"] } ``` > **为什么必须清**:本插件判"这个 id 被占用了"的判据=**`known.json` 的 `presetIds` ∪ 宿主名册**。 > 留着它 ⇒ 你以后用**同名**重建时,插件会判定重名,给出**新 id(形如 `<原名>-2`)**; > id 变了 ⇒ 老会话即便重建也恢复不了。清掉 ⇒ 同名重建能拿回**原来的 id**。 ### 第 5 步 · 清其余痕迹(可选,但"干净"就该做) | 痕迹 | 位置 | 怎么清 | |---|---|---| | 行开关写的覆盖行 | `/profiles//cordis.patch.yml` | 删掉 `- id: preset-<预设 id>` 与其 `disabled:`(若点"启用"复原过,可能是 `disabled: false`,**同属残留**) | | 默认预设指向它 | 同上文件里的 `agent-preset-registry` → `selectedDefault` | 若等于被删的 id,改成仍在的预设 id(**只改这一个字段**) | | 老版本落点残留 | `/.agent-presets//` | 目录删掉即可(见 §4) | | 备份文件 | 各 bundle 目录旁 | 别把备份留在原目录里叫 `… - 副本.yml`(下次容易"对着副本改,改了不生效")⇒ 挪到 `/backups/` | ### 第 6 步 · 重启 → 验收 **预设声明与 profile 覆盖都在装载期结算**(名册"一进程只挂载一次")⇒ **必须重启宿主**。 1. 新建会话 → 预设选择器:**没有它**,其余预设在; 2. 随手选一个仍在的预设进会话 ⇒ 能正常对话(证明整体健康); 3. 主侧栏「插件」里那个小包已不在,或行数比之前少 1; 4. 再跑一次 `node scripts/clean-test-state.mjs --scan --workspace <工作区>`:**死引用 0 个**。 --- ## 3. 形态 B:预设住在共用组合包里(**不能卸包**) 有些部署(或你自己)会把**十几个预设塞进同一个包**的一份 `cordis.patch.yml` 里。 这时**卸载整包=同包所有预设一起消失**,所以只能"从那一行动手": 1. **备份**该文件(DSH **0.1.7-rc.1 起配置热更新取消了事务回滚** —— 改错可能"部分生效"): 复制一份到 `/backups/`,并记下行数与哈希; 2. 用编辑器打开该包的 `cordis.patch.yml`; 3. **按锚点删,不按行号删**:从 ` - id: preset-<预设 id>` 起,删到**下一个** ` - id: preset-` **之前**; - 保留文件头注释、保留 `- insert:` 行、段后留**一个**空行; - **编码存 UTF-8**(显示名里有中文,存错编码会乱码); - **别改 `config.id`**,也别顺手动别的段; 4. 校验:**若该包自带校验脚本**(很多迁移包里有 `validate.mjs`),跑一遍: ```sh node <该包目录>/validate.mjs <该包目录>/cordis.patch.yml # 期望:YAML 语法 OK|preset 行数 = 原来 − 1|结构校验: OK(0 问题) ``` 另外用文本搜索确认该 id 在文件里 **0 命中**、且 `preset` 行数少 1; 5. **重启宿主**(见下面的"为什么")→ 按 §2 第 6 步验收。 > ⚠️ **为什么改完必须重启**:热更新只监听 **profile 用户补丁**与 `/cordis.patch.yml`, > **不监听各 bundle 自己的 patch 文件**(源码:`packages/boot/hmr/src/index.ts` 里 > `patchFiles = [profile.patchPath, join(profile.home, PROFILE_PATCH_FILENAME)]`)。 > 也就是说:**改 bundle 的 patch,宿主不会自动感知**。 > 💡 **顺带的完整性自检(很好用)**:改完后,**位于被删段之后的所有预设行,行号应当一律上移同一个数字**, > 且该数字=你删掉的行数(段行数 + 段后空行)。逐段核对一遍,就能证明"**只删了这一段,别的段一字没动**"。 --- ## 4. 形态 C:老版本(≤0.1.6)的目录式预设 - 位置:`/.agent-presets//`(`preset.yml` + `agent.cordis.yml`,可能还有 `AGENTS.md`); - **自 0.1.7-alpha.1 起,没有任何代码读这个目录** ⇒ 删它、改它都**不影响运行**, 改了也不生效(最典型的"假动作":以为删了,其实选择器里还在,因为真身在 bundle 里); - 所以它只有两个意义:**① 清痕**(盘点时不至于看花眼)**② 回退保底**(若你还没把内容迁成 bundle)。 想继续用它 ⇒ 先迁移:见 [迁移与兼容](迁移与兼容.md); - **只删你要删的那一个目录**,同级的其它目录是别人的/别的预设的保底。 --- ## 5. 删错了怎么办(两个窗口) | 时点 | 情况 | 处置 | |---|---|---| | **重启之前(黄金窗口)** | 刚卸了或刚改了声明,还没重启 | **先别重启**。声明退役只影响"新会话能不能选到";**已经在跑的会话持旧代际继续跑**。把声明补回去 ⇒ **零损失** | | 重启之后 · 有备份 | — | 用备份覆盖回原文件 ⇒ 重启 | | 重启之后 · 无备份 | 记得**显示名** | 同名重建(id 由名字推导,同名的 id 与原来一致)+ 清 `known.json` 占用(§2 第 4 步) | | 重启之后 · 无备份 | 连显示名也不记得 | 只能重建一个新预设;**老会话救不回**(会话记的是旧 `config.id`) | | 有会话打不开 | 提示"定义缺失/无法恢复" | 这是**设计内行为**(重启时按保存的 id 在当前配置里解析,缺失即拒绝恢复)。补回**同 id** 的声明后即可恢复 | --- ## 6. 四条禁忌 1. **不要为了删一个预设去卸整包** —— 先数一下那个包里声明了几个预设(一份文件可以有很多个)。 2. **不要先删目录、后卸依赖** —— 顺序反了:宿主启动会提示"跳过该 bundle",相关会话被拒绝恢复。 3. **不要把 `config.id` 当可改字段** —— 显示名、`order` 都能改,`config.id` 不能:会话记的是它。 4. **不要"只删目录"就以为完事** —— 名册里那一行还在,选择器里照样看得见它。 --- ## 7. 一页速查 | 你想做的事 | 一条命令 / 一步 | |---|---| | 先看清有什么(只读) | `node scripts/clean-test-state.mjs --scan --workspace <工作区>` | | 确认是不是这一行(可逆) | 主侧栏「插件」→ 该包 → 关掉 `preset-<预设 id>` 行 | | 摘掉依赖(形态 A) | `dsh plugin --profile remove @local/dsh-learn-preset-<预设 id>` | | 删工作区里的 bundle 目录 | `node scripts/clean-test-state.mjs --only <预设 id> --workspace <工作区> --yes` | | 清占用记录 | `<工作区>/.dsh/learn-skills/known.json` 的 `presetNames` / `presetIds` | | 清覆盖残留 | `/profiles//cordis.patch.yml` 里 `preset-<预设 id>` 那两行 | | 让它生效 | **重启宿主** → 新建会话看选择器 | | 复核是否干净 | 再跑一次 `--scan`:**死引用 0 个** + 该 id 在各文件 0 命中 | | 删错了 | **先别重启** → 把声明补回去(零损失窗口) | **一句话版**: > **先体检 →(可选)关行确认 → 卸对应那个小包 → 删工作区里的 bundle 目录 → 清 `known.json` 占用 > →(可选)清覆盖行与老落点 → 重启 → 再体检。** --- ## 附:本插件在"删除"这件事上做什么、不做什么 - **做**:生成预设 bundle(两件套)、出**安装/卸载指引**、给出**只读体检工具**与判据 (`clean-test-state.mjs`:生成标记、死引用、孤儿登记); - **不做**:**不代替你安装、也不代替你删除** —— 安装/卸载会在 **Host 进程**执行代码或改 profile 依赖, 属于"替你点头"的动作。插件的立场与官方一致:**生成与判据我出,动手由你确认** (官方 `plugin_manager` 对安装/卸载**强制审批**,源码:`packages/boot/plugin-manager/src/tools.ts`)。