@xia-sc/dsh-cc-studio 0.11.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,8 +14,8 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
14
14
 
15
15
  | 项目 | 值 |
16
16
  | --- | --- |
17
- | 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.11.3` |
18
- | 兼容 | DSH `>=0.1.5-rc.1`(含**桌面版**)、Node `>=22`、**零运行依赖**;逐版本实测见 [兼容与权限](#兼容与权限) |
17
+ | 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.12.0` |
18
+ | 兼容 | DSH `>=0.2.0-rc.2`(含**桌面版**)、Node `>=22`、**零运行依赖**;逐版本实测见 [兼容与权限](#兼容与权限) |
19
19
  | 落盘位置 | `<DSH_HOME>/cc-library/`(角色卡)、`<DSH_HOME>/cc-drafts/`(会话草稿;LLM 创作锁 `.lock.json` 与覆盖前备份 `backups/` 也在这里);`DSH_HOME` 默认 `~/.dsh`,旧位置仍读得到 |
20
20
  | 文档 | 用户文档=本文件;改代码前必读 [AGENTS.md](./AGENTS.md);完整历史 [CHANGELOG.md](./CHANGELOG.md) |
21
21
 
@@ -42,10 +42,10 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
42
42
  dsh --profile <profile> --dump-config
43
43
  期望在 `# == @xia-sc/dsh-cc-studio` 那一层下看到两行:`id: dsh-cc-studio`(宿主行)与 `id: preset-cc`(CC 预设行)。
44
44
  4. 告诉用户重启:宿主行只在启动时组合 —— CLI 重启 `dsh web`,桌面版退出并重新打开应用。
45
- 5. 重启后可选自检:在「CC 模式」会话里调任意一个 cc_* 工具,返回里带 "build": "0.11.3" 即说明新代码已加载
45
+ 5. 重启后可选自检:在「CC 模式」会话里调任意一个 cc_* 工具,返回里带 "build": "0.12.0" 即说明新代码已加载
46
46
  (这也是判断「改了但没重启」的唯一可靠依据)。
47
47
  6. 不要做的事:
48
- - 不要手工把 presets/ 拷进 ~/.dsh/.agent-presets/(dsh >= 0.1.7-alpha.1 已不读该目录,拷了也不生效);
48
+ - 不要手工往 ~/.dsh/.agent-presets/ 里拷预设——受支持的 dsh 都不读那个目录(插件用随包声明层投递,拷了也不生效);
49
49
  - 不要手改 ~/.dsh/profiles/*/package.json(dsh plugin 会自己维护 dsh.profile.bundles);
50
50
  - 不要为了「验证」反复重装 —— 装不上先看第 3 步的 dump-config 输出。
51
51
 
@@ -58,9 +58,9 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
58
58
  ```bash
59
59
  # 从 npm 安装(推荐)
60
60
  dsh plugin --profile web add @xia-sc/dsh-cc-studio
61
- dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.11.3 # 锁定版本
61
+ dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.12.0 # 锁定版本
62
62
 
63
- # 从 GitHub 源安装(可加 #v0.11.3 / #master 指定 tag / 分支)
63
+ # 从 GitHub 源安装(可加 #v0.12.0 / #master 指定 tag / 分支)
64
64
  dsh plugin --profile web add github:xia-sc/dsh-cc-studio
65
65
 
66
66
  # 从本地仓库安装(开发 / 调试):装出来是 link:,改 lib/*.js 重启即生效
@@ -71,7 +71,7 @@ dsh plugin --profile web add .
71
71
  `dsh plugin` 只是把参数转发给 profile 目录里的 pnpm,装完会把包名自动追加进该 profile 的 `dsh.profile.bundles`,不用手改 `package.json`。
72
72
 
73
73
  - **桌面版**:应用内左侧「插件」面板搜索 `@xia-sc/dsh-cc-studio` 安装,或用 `dsh plugin --profile desktop add @xia-sc/dsh-cc-studio`;生效方式是**退出并重新打开应用**(窗口里的刷新不足以加载宿主改动)。
74
- - **CC 预设无需手工操作**:插件会自己把 `CC 模式` 带到 roster 里(≥ `0.1.7-alpha.1` 走随包补丁层 `presets/cc.patch.yml` 的声明行;≤ `0.1.6-alpha.2` 由插件挂载时装到 `<DSH_HOME>/.agent-presets/cc`)。升级 dsh 后若 `CC 模式` 从列表里消失,见下方常见问题。
74
+ - **CC 预设无需手工操作**:`CC 模式` 由随包补丁层 `presets/cc.patch.yml` 的声明行投递(与 dsh 内置 `standard` / `ptc` 同一种机制),插件装进哪个 profile 就出现在哪个 profile 的 roster 里;插件不往用户目录写任何预设文件。升级 dsh 后若 `CC 模式` 从列表里消失,见下方常见问题。
75
75
  - **包名迁移**:0.3.2 起由 `@dsh-plugins/dsh-cc-studio` 改为 `@xia-sc/dsh-cc-studio`(旧名不在本项目的 npm scope 下)。装过旧名的先 `remove` 再 `add`;`cc-library/`、`cc-drafts/` 与插件设置都不受影响。
76
76
 
77
77
  ### 更新与卸载
@@ -167,12 +167,12 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
167
167
 
168
168
  | 项 | 值 |
169
169
  | --- | --- |
170
- | DSH 兼容范围 | `>=0.1.5-rc.1`;逐版本矩阵里 `0.1.5-rc.1` / `0.1.5-rc.2` / `0.1.6-alpha.1` / `0.1.6-alpha.2` / `0.1.7-alpha.1` / `0.1.7-rc.1` / `0.2.0-rc.2` / `0.2.1-rc.2` 为 `compatible`。**没实测过的版本不写**(`0.1.7-alpha.2` / `0.1.7-rc.2` / `0.2.0-rc.1` / `0.2.1-alpha.1` 保持未声明) |
170
+ | DSH 兼容范围 | `>=0.2.0-rc.2`;逐版本矩阵里 `0.2.0-rc.2` / `0.2.1-rc.2` / `0.2.1-rc.3` / `0.2.1-rc.5` 为 `compatible`。**`0.1.x` 整条线自 0.12.0 起不再支持**;**没实测过的版本不写**(`0.2.0-rc.1` / `0.2.1-alpha.1` / `0.2.1-rc.4` 保持未声明) |
171
171
  | 宿主形态 | `dsh web`(CLI)与 **DSH 桌面版**同一套宿主 + profile bundles 机制,**均支持** |
172
172
  | 客户端平台 | `dsh.client.platform: "web"` 是**浏览器半**的平台标识,与 profile 名(`web` / `desktop`)不是一回事;两者加载同一份客户端 bundle |
173
173
  | 运行依赖 / 生命周期脚本 | **都没有**。`lib/*.js` 只 `import` `node:` 内建与相对路径,随包补丁与预设是静态 YAML |
174
- | 权限 · 文件 | 只读写 `<DSH_HOME>`(含旧根 `~/.dsh`)下自己的五个目录:`cc-drafts/`、`cc-library/`、`cc-groups/`、`cc-books/`、`.agent-presets/cc/`(最后一个只在 dsh ≤ `0.1.6-alpha.2` 上写),另读自身 `package.json` |
175
- | 权限 · 网络 / 命令 / 凭据 | 网络:**只有**设置页「检查更新」点按钮时的一次出站 HTTPS GET(`https://registry.npmjs.org/@xia-sc%2Fdsh-cc-studio/latest`,8s 超时,只带 `accept` 与 `user-agent: 包名/版本`,不传任何用户数据;设置页打开时的探测 `{network:false}` **不联网**)。不起子进程、不读也不存任何 token 或 cookie。只读两个环境变量:`DSH_HOME`、`DSH_CC_STUDIO_PRESET_INSTALL`。HTTP 路由复用宿主自己的 cookie 围栏(`connection.requestRejection`),插件不解析凭据 |
174
+ | 权限 · 文件 | 只读写 `<DSH_HOME>`(含旧根 `~/.dsh`)下自己的四个目录:`cc-drafts/`、`cc-library/`、`cc-groups/`、`cc-books/`,另读自身 `package.json`。**不再写 `.agent-presets/`**(旧目录投递已随 0.2.0 以下兼容删除;残留在那里的文件不会被读写,可手动删除) |
175
+ | 权限 · 网络 / 命令 / 凭据 | 网络:**只有**设置页「检查更新」点按钮时的一次出站 HTTPS GET(`https://registry.npmjs.org/@xia-sc%2Fdsh-cc-studio/latest`,8s 超时,只带 `accept` 与 `user-agent: 包名/版本`,不传任何用户数据;设置页打开时的探测 `{network:false}` **不联网**)。不起子进程、不读也不存任何 token 或 cookie。只读一个环境变量:`DSH_HOME`。HTTP 路由复用宿主自己的 cookie 围栏(`connection.requestRejection`),插件不解析凭据 |
176
176
 
177
177
  > **为什么商城不会给它「自动通过」**:STORE 的自动低风险通道要求运行时代码完全不出现文件 / 网络 / 命令 / 凭据四类信号,而本插件的核心功能就是草稿与角色卡落盘(文件信号)、读 `DSH_HOME`(扫描器把任何 `process.env` 读取都算凭据信号),再加上 0.11.3 起「检查更新」的一次出站请求(网络信号)。这些都不会为了迎合扫描器而消除 —— 对这类合法插件,商城的正确档位是 `user-reviewed`。隔离环境下的安装 / 启动 / 卸载证据见 [CHANGELOG.md](./CHANGELOG.md) 的 `0.3.7` 小节。
178
178
 
@@ -181,21 +181,27 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
181
181
  ## 开发与测试
182
182
 
183
183
  ```bash
184
- npm test # 25 个文件、1041 项断言,全绿才算过
184
+ npm test # 25 个文件、1008 项断言,全绿才算过
185
185
  node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(125 项)
186
186
  node tests/group-layer-guard.test.mjs # 只跑卡组层的耦合守卫(16 项)
187
187
  ```
188
188
 
189
189
  - 改 `lib/client.js`:刷新页面即可;改 `lib/index.js` / 预设:**重启 dsh**。
190
- - 架构与约定(三半分工、RPC 线协议、预设两代投递、落盘根、测试隔离要求、常见坑)都在 [AGENTS.md](./AGENTS.md) —— 动代码前先读它。
190
+ - 架构与约定(三半分工、RPC 线协议、预设的单层声明投递、落盘根、测试隔离要求、常见坑)都在 [AGENTS.md](./AGENTS.md) —— 动代码前先读它。
191
191
 
192
192
  ## 更新日志
193
193
 
194
194
  完整历史见 [CHANGELOG.md](./CHANGELOG.md)。最近几版大多是「另一个 LLM 以工具使用者身份跑回归」挖出来的问题:
195
195
 
196
+ - `0.12.0` **破坏性变更:兼容下限抬到 dsh `0.2.0-rc.2`**,`0.1.x` 整条线不再支持。预设投递收敛成唯一一份文件 `presets/cc.patch.yml`(声明行,与内置 `standard` / `ptc` 同一种机制):删掉旧的目录形态模板和「挂载时安装到 `<DSH_HOME>/.agent-presets/cc`」那套代码(`installCcPreset` / `planPresetInstall` / `supportsDeclaredPresets` 与安装记录侧车),`apply()` 不再写盘,`DSH_CC_STUDIO_PRESET_INSTALL` 环境变量与「两份清单必须逐字一致」的维护成本一起消失;权限面从 5 个目录 / 2 个环境变量降到 4 个目录 / 1 个。行为变化:`0.1.x` 上装新版后 `CC 模式` 会从 roster 消失(那代 dsh 不读补丁层声明);已存在的 `~/.dsh/.agent-presets/cc` 不再被读写,可手动删除。`dshReleases` 只保留实测过的 `0.2.0-rc.2` / `0.2.1-rc.2` / `0.2.1-rc.3` / `0.2.1-rc.5`。测试从 1041 项收敛到 **1008 项**。**重启 dsh(桌面版重启应用)生效**。
197
+
198
+ - `0.11.5` **dsh `0.2.1-rc.5` 兼容性实测:无需改动**。契约包(`dsh-host-webserver` / `dsh-client-connection` / `dsh-tools` / `dsh-system-prompt` / `dsh-agent-preset` / `dsh-client-modules` / `dsh-app-boot` 等)与 rc.3 **逐字节相同**,内置 `standard` 预设、`cordis.patch.yml` 组合与三个挂载点定义也都逐字节相同;rc.5 的真实改动只有 4 处(chat 的 `reasoningText()`、conversation 的 View tooltip、客户端 slot 目录文档、client-modules 的 commit 常量),都在插件不消费面。隔离实例 E2E **72/72**(34 个 RPC 端点、草稿锁 active/stale/force、JSON/PNG/CHARX 导出回导、roster `cc` 无 broken、客户端 bundle 200/rev),客户端 DOM 实测胶囊/工坊/设置页与 i18n EN↔ZH 全通过、console 0 error;同一 rc.5 运行时的真实 GUI 三个挂载点 occupant 全 active。`dshReleases` 补 `0.2.1-rc.5`。**运行时代码与预设一个字没改**。
199
+
200
+ - `0.11.4` **dsh `0.2.1-rc.3` 兼容性实测:无需改动**。契约包(`dsh-host-webserver` / `dsh-client-connection` / `dsh-tools` / `dsh-system-prompt` / `dsh-agent-preset` / `dsh-client-modules` / `dsh-app-boot` 等)与 rc.2 **逐字节相同**,内置 `standard` 预设也逐字节相同;rc.3 的真实改动只有 4 处且都在插件不消费面。隔离实例 E2E **72/72** 项通过(34 个 RPC 端点、草稿锁 active/stale/force、JSON/PNG/CHARX 导出回导、roster 里 `cc` 无 broken、客户端 bundle 200/rev 校验),同一 rc.3 运行时的真实 GUI 里三个挂载点 occupant 全部 active。`dshReleases` 补 `0.2.1-rc.3`,README 矩阵同步。**运行时代码与预设一个字没改** —— 升级 dsh 后照常用,无需重装或改预设。
201
+
196
202
  - `0.11.3` **设置页新增「检查更新」**:一键查 npm 上的最新版本,可更新时给出可复制的 `@xia-sc/dsh-cc-studio@版本`(直接拿去 `dsh plugin --profile <配置名> add` 更新)。查询走宿主的新 RPC 端点 `cc_checkUpdate`:`{network:false}` 只回本地版本(设置页一打开就显示「当前版本」,不打网络),默认查 registry 的 `/latest` 并带 8s 超时;客户端不直连 npm。版本比较按 SemVer 第 11 条(预发布版小于同号正式版;预发布数字标识符按数值,`rc.2 < rc.10`),脏版本号不会被误报成「有更新」。测试 +53 项(新增 `tests/check-update.test.mjs`)→ **1041 项**。**重启 dsh(桌面端重启应用)+ 刷新页面生效**。
197
203
 
198
- - `0.11.2` **修正中文 README 两处文档错误**:安装引导第 1 步被写成 `4.`;一批 dsh 版本号被写坏成 `0.4.x`(真实范围 `>=0.1.5-rc.1`),兼容范围 / 预设分界 / 实测矩阵已纠正,矩阵补上 `0.2.1-rc.2`。纯文档,无需重启。
204
+ - `0.11.2` **修正中文 README 两处文档错误**:安装引导第 1 步被写成 `4.`;一批 dsh 版本号被写坏成 `0.4.x`(当时的真实范围是 0.1.x 的 rc 版,现已抬到 `>=0.2.0-rc.2`),兼容范围 / 预设分界 / 实测矩阵已纠正,矩阵补上 `0.2.1-rc.2`。纯文档,无需重启。
199
205
 
200
206
  - `0.11.1` **CC 预设对齐 dsh `0.2.1-rc.2` 内置 `standard`**:修掉 `tool-ralph` 漏抄 `disabled`(CC 模式一直多挂一个实验性长循环工具)、`skill-filesystem` / `tool-skill` 被误关(模型没有技能目录与加载器)、以及 `backgroundMode` 等已下线的配置键;补 `time-context`、`tool-web` 的 `fetch` 改回 `true`。`dshReleases` 补 `0.2.1-rc.2`(npm `dsh web` 隔离环境实测)。测试补 CHARX 导入全链路与「按 id 改世界书条目」的回归 → **987 项**。**重启 dsh(桌面端重启应用)生效**。
201
207
 
@@ -205,7 +211,7 @@ node tests/group-layer-guard.test.mjs # 只跑卡组层的耦合守卫(1
205
211
 
206
212
  - `0.10.2` **模型偶发一个 JSON 尾逗号,整轮就没了**:报错是 `本轮运行失败 DeepSeek Messages stream: tool input is invalid JSON`。会话日志解帧取证:模型调 `cc_patch_character` 时生成的 arguments 末尾是 `..."tags":[...],}`(尾逗号)。根因在 provider:`@deepseek-ai/dsh-llm-deepseek` 在流结束时对 tool input 严格 `JSON.parse`,失败即 `MALFORMED_RESPONSE`,而默认 `retryableCodes` 不含它 → 不自动重试;工具从未执行、卡片数据没坏。插件能施加影响的只有模型可见文本,所以给 persona 加「工具参数必须是严格合法 JSON(禁尾逗号/注释/单引号/未转义换行),一次只传 1-3 个字段、长字段分多次写」,五个写类工具的 description 同步这句规则,并用测试钉住(preset-install + tools-contract 各 +1 项)。用户侧可选兜底:在 profile `cordis.patch.yml` 给 `llm-deepseek-account` 配 `retryPolicy.retryableCodes` 含 `MALFORMED_RESPONSE`(详见 CHANGELOG 0.10.2)。测试 856 → **858 项**。**重启 dsh(桌面版重启应用)生效**。
207
213
 
208
- - `0.10.1` **dsh 0.2.1-rc.2 上 CC 模式每轮「本轮运行失败」**:报错 `unknown prompt variable "{{cwd}}" in section "deployment:persona-prefix"; registered variables: provider, model`。0.2.1 的 `dsh-system-prompt` 只注册 `provider` / `model` 两个 prompt 变量(工作目录改由 working-directory 服务以 runtime context 提供),而段文本插值对未知引用**严格抛错**、发生在任何模型请求之前 —— 预设本身挂载正常、roster 里也正常可选,唯独每一轮对话失败。修法:两份预设清单(`presets/cc.patch.yml` + `presets/cc/agent.cordis.yml`)的 persona prefix 删掉「,工作目录 {{cwd}}」、保留 `{{model}}`;把 prompt 变量白名单(只允许 `provider` / `model`、禁止 `{{cwd}}`、两份集合一致、`{{model}}` 仍在)钉进 `tests/preset-install.test.mjs`。测试 852 → **856 项**。**重启 dsh(桌面版重启应用)生效**,无需重装或刷新页面。
214
+ - `0.10.1` **dsh 0.2.1-rc.2 上 CC 模式每轮「本轮运行失败」**:报错 `unknown prompt variable "{{cwd}}" in section "deployment:persona-prefix"; registered variables: provider, model`。0.2.1 的 `dsh-system-prompt` 只注册 `provider` / `model` 两个 prompt 变量(工作目录改由 working-directory 服务以 runtime context 提供),而段文本插值对未知引用**严格抛错**、发生在任何模型请求之前 —— 预设本身挂载正常、roster 里也正常可选,唯独每一轮对话失败。修法:预设清单(当时有两份载体,0.12.0 起已收敛为一份)的 persona prefix 删掉「,工作目录 {{cwd}}」、保留 `{{model}}`;把 prompt 变量白名单(只允许 `provider` / `model`、禁止 `{{cwd}}`、变量集合一致、`{{model}}` 仍在)钉进 `tests/preset-install.test.mjs`。测试 852 → **856 项**。**重启 dsh(桌面版重启应用)生效**,无需重装或刷新页面。
209
215
 
210
216
  - `0.10.0` **卡组不再是一堆工具的组合,而是一层**:0.9.x 的两轮补丁都打在同一个地方 —— 组级共享世界上线后,`simulate` / `lint` / `group_check` / `preview_export` / `validate` 五个「读数」工具**各自**补了一遍「把共享书算进来」(「改了四个、漏了第五个」)。根因不是手滑,而是同一个语义存在多份实现:卡组数据读取两半各一份、取作用域每个工具各写一遍、卡组级统计与世界书统计平行两套、界面再自己算一遍。修法是立三层并**让两半共用同一个文件** `lib/group-layer.js`(纯函数、零 IO):**数据层** `readLibrarySnapshot()` + `groupScope()`(来源卡 → 组 → 成员 → 书,唯一入口,支持 `include` / `maxCards` / 传快照避免重复读盘)、**口径层** `groupMetrics()`(所有「多少条 / 多少 token」只在这里产生,五段 + 派生量;`lorebookMetrics` 变薄包装、旧输出逐字不变)、**检查层** `runGroupChecks()` + `GROUP_CHECKS` 注册表(一份清单、一套 issue 形状、一个 `byCheck` 计数)。`checkGroup` 只剩 6 行薄包装(以前 48 行 if 序列),宿主端点、工具与既有测试一行未改。**第五个功能作为验收用例落地**:新增 **cross-card** 类别 —— 跨卡触发词冲突(只报涉及 ≥2 个来源的)、卡内条目与共享书逐字重复、组内五维漂移、成员卡全 auto,外加 **lore-quality**(共享书自身条目质量);这五项只改了注册表,五个既有工具的口径代码**一行未动**(`tests/group-layer-guard.test.mjs` 钉住)。入口仍是 `cc_group_check` / `cc_groupCheck`(新增 `checks` 参数与 `byCheck`),界面把体检结果从「一行拼接」改成按 error / warn 分组的清单。顺带:组体检读盘 `1+2N` → `1+N`;记录一个已知不可达检查(`type-unknown` —— `normalizeGroup` 早把未知 type 归一到 `other`)。测试 735 → **852 项(22 个文件)**。**需重启 dsh(桌面版重启应用)→ 刷新页面**。
211
217
 
package/lib/agent.js CHANGED
@@ -23,7 +23,7 @@ const name = "dsh-cc-studio-agent";
23
23
  // 由来:第五轮实测里测试者第一遍跑出「四项全无变化」,其实只是**没重启 dsh** —— LLM 侧完全无法
24
24
  // 分辨「代码没改」和「改了但没加载」。有了这个字段,任何一次调用都能先确认自己测的是哪一版。
25
25
  // 发版时与 package.json 的 version 同步。
26
- const BUILD = "0.11.3";
26
+ const BUILD = "0.12.0";
27
27
  const inject = ["tools", "agents"];
28
28
 
29
29
  function nowSec(){ return Math.floor(Date.now()/1000); }
package/lib/index.js CHANGED
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import { mkdir, readdir, readFile, writeFile, unlink } from "node:fs/promises";
6
6
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
7
- import { createHash } from "node:crypto";
8
7
  import { fileURLToPath } from "node:url";
9
8
  import { join } from "node:path";
10
9
  import { homedir } from "node:os";
@@ -465,7 +464,7 @@ function listDraftSlotsOnDisk(){
465
464
  return out;
466
465
  }
467
466
  /**
468
- * 草稿槽迁移的纯决策函数(便于单测,与 planPresetInstall 同一套路子)。
467
+ * 草稿槽迁移的纯决策函数(便于单测)。
469
468
  * @returns {ok:true, draft, summary} 或 {ok:false, code, message, details}
470
469
  */
471
470
  function migrateDraftDecision(source, target, overwrite){
@@ -1504,56 +1503,6 @@ function sendEnvelope(res, status, payload){
1504
1503
  function rpcFull(rpcId, result){ return { type:"server-response", rpcId, result }; }
1505
1504
  function rpcError(rpcId, code, message, details){ return rpcFull(rpcId, { ok:false, error:{ code, message, details } }); }
1506
1505
 
1507
- // ========== CC 预设安装 ==========
1508
- // dsh >= 0.1.7-alpha.1:预设机制换代了。预设不再是「<DSH_HOME>/.agent-presets/<id>/ 目录」,
1509
- // 而是**组合里的一个声明行**(内置 standard/ptc/minimal/cordis 就是这么发的),目录形态在 0.1.7
1510
- // 已经没有任何代码读取 —— 这就是「升级到 0.1.7-alpha.1 后 CC 模式从 roster 里消失」的原因:
1511
- // 文件还在,只是没人再看它。本插件因此把声明写进随包的补丁层 presets/cc.patch.yml
1512
- // (package.json 的 dsh.bundle.patch 第二层):插件装进哪个 profile,CC 模式就出现在哪个
1513
- // profile 的 roster 里,不再依赖「挂载时往用户目录写文件」。
1514
- //
1515
- // 下面这套目录安装**只为更老的 dsh(<= 0.1.6-alpha.2)保留**:那条路径上预设发现只扫三个根
1516
- // ——shipped 根(dsh-agent-presets 包内)+ 部署 config.roots + 用户根 <DSH_HOME>/.agent-presets
1517
- // (见 dsh-agent-presets lib/index.js:1300-1310);插件包内的 presets/ 不在其中,
1518
- // 「随包分发」≠「已注册」,所以必须挂载时把模板装进用户根,「装完插件即有 CC 模式」才成立。
1519
- //
1520
- // 安全策略(绝不静默覆盖用户改动):
1521
- // · 目标文件不存在 → 写入模板
1522
- // · 目标与模板一致 → 不动(只补记安装记录)
1523
- // · 目标与我们上次写入的记录一致 → 是我们装的且用户没改 → 安全更新
1524
- // · 其余(用户改过 / 无记录的历史手工拷贝)→ 跳过并告警,交由用户决定
1525
- // 安装记录是目标目录里的 JSON 侧车文件,含每个受管文件写入时的 sha256,
1526
- // 因此不依赖版本号比较也能正确判定「这是不是我们写的、用户有没有动过」。
1527
- // config.presetInstall: "auto"(默认)| "off" | "force"(连用户改动一起覆盖)。
1528
- // 新版 dsh 上这份目录安装自动让位(status: "native"),开关也无意义 —— 想关 CC 模式就在
1529
- // profile 补丁里按 id 关掉 preset-cc 那一行(presets/cc.patch.yml 顶部有现成的写法)。
1530
- const PRESET_ID = "cc";
1531
- const PRESET_MANAGED_FILES = ["preset.yml", "agent.cordis.yml"];
1532
- const PRESET_MARKER_FILE = ".dsh-cc-studio-preset.json";
1533
- /**
1534
- * 当前 dsh 是否已经用「声明行」发预设(>= 0.1.7-alpha.1)。
1535
- * 只用来决定「要不要往那份已经没人读的目录里写字」,判错的代价并不对称:
1536
- * · 误判 false(在老版上)→ 照旧写一次目录,无害;
1537
- * · 误判 true(在真需要目录的老版上)→ 才是真的少一个预设。
1538
- * 所以要求两个方法**同时**存在,且**不许简化成其中任何一个**:
1539
- * 0.1.6 的 `dsh-agent-presets` 也有 `compositionInventory`(`async compositionInventory()`),
1540
- * 单看它会把老版误判成新版;`register(definition)` 才是 0.1.7 新注册表独有的(老服务里
1541
- * `register` 只出现在 `settings.register` / `sessionProjections.register` 上)。
1542
- */
1543
- function supportsDeclaredPresets(ctx){
1544
- try{
1545
- const presets = ctx && typeof ctx.get === "function" ? ctx.get("agentPresets") : null;
1546
- return !!(presets && typeof presets.register === "function" && typeof presets.compositionInventory === "function");
1547
- }catch{ return false; }
1548
- }
1549
-
1550
- // `dshHomeDir()` / `legacyDshHomeDir()` / `dataRoots()` 定义在文件顶部(草稿那一节之前)——
1551
- // 这里**不再重复定义**:两个同名 function 声明会「后者悄悄赢」,是漂移的温床。
1552
- function presetSourceRoot(){
1553
- try{ return fileURLToPath(new URL("../presets/"+PRESET_ID+"/", import.meta.url)); }catch{ return null; }
1554
- }
1555
- function presetTargetRoot(){ return join(dshHomeDir(), ".agent-presets", PRESET_ID); }
1556
- function sha256(text){ return createHash("sha256").update(String(text), "utf8").digest("hex"); }
1557
1506
  function pluginMeta(){
1558
1507
  try{
1559
1508
  const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
@@ -1631,123 +1580,8 @@ async function fetchLatestFromNpm(timeoutMs){
1631
1580
  return { version: v, distTag: "latest" };
1632
1581
  }finally{ clearTimeout(timer); }
1633
1582
  }
1634
- /**
1635
- * 纯决策函数(便于单测):给定目标现状、模板、上次安装记录,决定每个受管文件该写还是该跳过。
1636
- * @param targets 目标文件内容表(缺失的文件用 null)
1637
- * @param templates 模板内容表
1638
- * @param marker 上次安装记录(可缺省)
1639
- * @param force 为 true 时连用户改动一起覆盖
1640
- */
1641
- function planPresetInstall(targets, templates, marker, force){
1642
- const recorded = (marker && marker.files && typeof marker.files === "object") ? marker.files : {};
1643
- const write = [], skip = [], ok = [];
1644
- for(const name of Object.keys(templates)){
1645
- const tpl = templates[name];
1646
- const cur = Object.prototype.hasOwnProperty.call(targets, name) ? targets[name] : null;
1647
- if(cur === null || cur === undefined){ write.push(name); continue; } // 缺文件 → 补齐
1648
- if(cur === tpl){ ok.push(name); continue; } // 已是当前模板
1649
- if(force){ write.push(name); continue; }
1650
- if(recorded[name] && recorded[name] === sha256(cur)){ write.push(name); continue; } // 我们写的、用户没改
1651
- skip.push(name); // 用户改动 / 历史手工拷贝
1652
- }
1653
- return { write, skip, ok };
1654
- }
1655
- /**
1656
- * 把 presets/cc 安装到 <DSH_HOME>/.agent-presets/cc(**只对 < 0.1.7-alpha.1 有意义**)。
1657
- * 同步执行(只有两个小文件),幂等。
1658
- * 注意:卸载插件**不会**删除已安装的预设(与 README 的既有说明一致),因此这里不注册删除型 disposer。
1659
- */
1660
- function installCcPreset(ctx, config){
1661
- // 开关优先级:插件行 config.presetInstall > 环境变量 DSH_CC_STUDIO_PRESET_INSTALL > auto。
1662
- // 环境变量是给用户的轻量逃生口(不必写 overlay patch)。
1663
- const fromCfg = (config && typeof config.presetInstall === "string") ? config.presetInstall.trim() : "";
1664
- const fromEnv = (typeof process.env.DSH_CC_STUDIO_PRESET_INSTALL === "string") ? process.env.DSH_CC_STUDIO_PRESET_INSTALL.trim() : "";
1665
- const raw = fromCfg || fromEnv || "auto";
1666
- const mode = (raw === "off" || raw === "force") ? raw : "auto";
1667
- const out = { mode, status: "unknown", installed: [], updated: [], skipped: [], reason: null };
1668
- if(mode === "off"){ out.status = "off"; return out; }
1669
- const log = (level, msg)=>{ try{
1670
- const l = ctx && ctx.logger;
1671
- if(l && typeof l[level] === "function") l[level]("[dsh-cc-studio] " + msg);
1672
- else if(level === "warn") console.warn("[dsh-cc-studio] " + msg);
1673
- }catch{} };
1674
- // dsh >= 0.1.7-alpha.1:预设已随包声明(presets/cc.patch.yml),目录形态被彻底忽略。
1675
- // 这里一个字都不写 —— 否则只会留下一个没人读的目录,下次有人「修预设」时还要多查一层。
1676
- if(supportsDeclaredPresets(ctx)){
1677
- out.status = "native";
1678
- out.reason = "dsh >= 0.1.7-alpha.1:CC 预设由随包补丁层 presets/cc.patch.yml 声明,目录形态已不再被读取,跳过安装";
1679
- log("info", out.reason);
1680
- try{
1681
- const dst = presetTargetRoot();
1682
- if(existsSync(dst)) log("info", "检测到旧版安装目录 " + dst + "(0.1.6 及更早的形态):0.1.7 起不再被读取,可以安全删除。");
1683
- }catch{}
1684
- return out;
1685
- }
1686
- try{
1687
- const src = presetSourceRoot();
1688
- const templates = {};
1689
- for(const name of PRESET_MANAGED_FILES){
1690
- const p = src ? join(src, name) : null;
1691
- if(p && existsSync(p)) templates[name] = readFileSync(p, "utf8");
1692
- }
1693
- if(Object.keys(templates).length === 0){
1694
- out.status = "no-template"; out.reason = "插件包内找不到 presets/" + PRESET_ID + "(打包时漏了 presets?)";
1695
- log("warn", out.reason); return out;
1696
- }
1697
- const dst = presetTargetRoot();
1698
- const targets = {};
1699
- for(const name of Object.keys(templates)){
1700
- const p = join(dst, name);
1701
- targets[name] = existsSync(p) ? readFileSync(p, "utf8") : null;
1702
- }
1703
- const markerPath = join(dst, PRESET_MARKER_FILE);
1704
- let marker = null;
1705
- if(existsSync(markerPath)){
1706
- try{ marker = JSON.parse(readFileSync(markerPath, "utf8")); }catch{ marker = null; }
1707
- }
1708
- const plan = planPresetInstall(targets, templates, marker, mode === "force");
1709
- const isFresh = !existsSync(dst) || Object.keys(targets).every((k) => targets[k] === null);
1710
- if(plan.write.length > 0){
1711
- mkdirSync(dst, { recursive: true });
1712
- for(const name of plan.write){
1713
- writeFileSync(join(dst, name), templates[name], "utf8");
1714
- (isFresh ? out.installed : out.updated).push(name);
1715
- }
1716
- }
1717
- // 刷新记录的前提是「我们至少拥有一个受管文件」(写入的或本已一致的)。
1718
- // 若全部文件都属于用户(例如纯手工拷贝),则不写任何记录——不在用户自己管理的目录里留痕迹。
1719
- const files = {};
1720
- for(const name of Object.keys(templates)){
1721
- if(plan.write.indexOf(name) >= 0 || plan.ok.indexOf(name) >= 0) files[name] = sha256(templates[name]);
1722
- }
1723
- const owned = Object.keys(files).length > 0;
1724
- const nextMarker = { version: pluginVersion(), installedAt: nowSec(), files };
1725
- const markerStale = !marker || marker.version !== nextMarker.version || JSON.stringify(marker.files||{}) !== JSON.stringify(files);
1726
- if(owned && (plan.write.length > 0 || markerStale)){
1727
- mkdirSync(dst, { recursive: true });
1728
- writeFileSync(markerPath, JSON.stringify(nextMarker, null, 2), "utf8");
1729
- }
1730
- out.skipped = plan.skip;
1731
- if(out.installed.length) { out.status = "installed"; log("info", "已自动安装 CC 预设到 " + dst + "(" + out.installed.join(", ") + "):重启后可在会话模式里选择「CC 模式」"); }
1732
- else if(out.updated.length) { out.status = "updated"; log("info", "已自动更新 CC 预设 " + out.updated.join(", ") + " → " + nextMarker.version); }
1733
- else { out.status = "current"; }
1734
- if(plan.skip.length){
1735
- out.status = out.status === "current" ? "skipped-user-edited" : out.status;
1736
- log("warn", "CC 预设的 " + plan.skip.join(", ") + " 与插件模板不一致,且不是本插件写入的(可能是你手工改过或旧版手工拷贝),已保留未覆盖。" +
1737
- "如需用插件模板刷新,请删除 " + dst + " 后重启,或在插件行配置 presetInstall: force。");
1738
- }
1739
- return out;
1740
- }catch(e){
1741
- out.status = "error"; out.reason = String(e && e.message ? e.message : e);
1742
- log("warn", "自动安装 CC 预设失败(不影响其它功能):" + out.reason);
1743
- return out;
1744
- }
1745
- }
1746
-
1747
- function apply(ctx, config){
1583
+ function apply(ctx){
1748
1584
  globalThis.__CC_CTX__ = ctx;
1749
- // 装完插件即有 CC 模式:把模板预设写进用户预设根(幂等、不覆盖用户改动)
1750
- try{ globalThis.__CC_PRESET_INSTALL__ = installCcPreset(ctx, config); }catch{}
1751
1585
  const dispatch = async (endpoint, payload, signal)=>{
1752
1586
  const args= payload && typeof payload==="object" && payload.args && typeof payload.args==="object" ? payload.args : {};
1753
1587
  try{
@@ -2632,7 +2466,7 @@ function applyBookPromotion(cards, bookEntries, signatures){
2632
2466
  savedChars: nextCards.reduce((n, c) => n + c.removedChars, 0) };
2633
2467
  }
2634
2468
 
2635
- export { apply, inject, name, planPresetInstall, installCcPreset, supportsDeclaredPresets, presetTargetRoot, presetSourceRoot,
2469
+ export { apply, inject, name,
2636
2470
  draftKeyPartsFrom, draftContentSummary, listDraftSlotsOnDisk, migrateDraftDecision,
2637
2471
  // 卡组层(0.10.0):卡库快照 + 卡组作用域 —— 卡组数据的唯一入口,两半共用 lib/group-layer.js 的纯组装
2638
2472
  readLibrarySnapshot, groupScope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xia-sc/dsh-cc-studio",
3
- "version": "0.11.3",
3
+ "version": "0.12.0",
4
4
  "description": "CCv3 角色卡工坊:融合工坊 + CC 模式 + JSON/PNG/CHARX + 中英双语(跟随全局)",
5
5
  "keywords": [
6
6
  "dsh",
@@ -57,16 +57,12 @@
57
57
  ]
58
58
  },
59
59
  "compatibility": {
60
- "dsh": ">=0.1.5-rc.1",
60
+ "dsh": ">=0.2.0-rc.2",
61
61
  "dshReleases": {
62
- "0.1.5-rc.1": "compatible",
63
- "0.1.5-rc.2": "compatible",
64
- "0.1.6-alpha.1": "compatible",
65
- "0.1.6-alpha.2": "compatible",
66
- "0.1.7-alpha.1": "compatible",
67
- "0.1.7-rc.1": "compatible",
68
62
  "0.2.0-rc.2": "compatible",
69
- "0.2.1-rc.2": "compatible"
63
+ "0.2.1-rc.2": "compatible",
64
+ "0.2.1-rc.3": "compatible",
65
+ "0.2.1-rc.5": "compatible"
70
66
  }
71
67
  }
72
68
  },
@@ -1,19 +1,14 @@
1
- # @xia-sc/dsh-cc-studio — 「CC 模式」预设声明层(dsh >= 0.1.7-alpha.1)
1
+ # @xia-sc/dsh-cc-studio — 「CC 模式」预设声明层(dsh >= 0.2.0-rc.2)
2
2
  #
3
- # 为什么单独一层:dsh 0.1.7-alpha.1 起,预设不再是「<DSH_HOME>/.agent-presets/<id>/ 目录」,
4
- # 而是**组合里的一个声明行** —— 内置的 standard/ptc/minimal/cordis 就是这么发的(见
5
- # @deepseek-ai/dsh-web-app 的 presets/*.patch.yml)。目录形态在 0.1.7 已经没有任何代码读取,
6
- # 所以必须随包带一层补丁:插件装进哪个 profile,「CC 模式」就出现在哪个 profile 的 roster 里。
3
+ # 本文件是**唯一**的预设载体:dsh 0.1.7-alpha.1 起,预设不再是「<DSH_HOME>/.agent-presets/<id>/ 目录」,
4
+ # 而是组合里的一个声明行(内置的 standard/ptc/minimal/cordis 就是这么发的)。旧目录形态从 0.1.7 起
5
+ # 就再也没有任何读者;0.12.0 起本插件不再兼容 0.2.0 以下的 dsh,因此连兼容用的目录模板
6
+ # (presets/cc/ 与挂载时安装那套代码)一并删除 —— 这里看不到「两份清单」是刻意的,不是漏了。
7
7
  #
8
8
  # package.json 的 dsh.bundle.patch 列两层:cordis.patch.yml(宿主插件行)+ 本文件(预设声明)。
9
+ # 插件装进哪个 profile,「CC 模式」就出现在哪个 profile 的 roster 里;插件不再往用户目录写预设文件。
9
10
  #
10
- # ⚠️ 本文件的 plugins 必须与 presets/cc/agent.cordis.yml **同名同序**:
11
- # · 本文件(声明行) → dsh >= 0.1.7-alpha.1
12
- # · agent.cordis.yml(目录形态,插件挂载时安装到 <DSH_HOME>/.agent-presets/cc)→ 更老的 dsh
13
- # tests/preset-install.test.mjs 第 11/12 节把「两份清单不许漂移」钉死了:改一边必须改另一边。
14
- # 两份清单的行逐字一致,只有上面那两处解释「健康检查」的历史注释按 0.1.7 的事实改写过。
15
- #
16
- # 想关掉 CC 模式(>=0.1.7):不必卸载插件,在自己 profile 的补丁层里按 id 关掉这一行即可 ——
11
+ # 想关掉 CC 模式(>=0.2.0):不必卸载插件,在自己 profile 的补丁层里按 id 关掉这一行即可 ——
17
12
  # - id: preset-cc
18
13
  # name: '@deepseek-ai/dsh-agent-preset'
19
14
  # disabled: true
@@ -55,8 +50,8 @@
55
50
  # ── identity ────────────────────────────────────────────────────────────────
56
51
 
57
52
  # The preset's own persona, shadowing the deployment default for this agent.
58
- # dsh >= 0.1.5-rc.1: the persona row takes `prefix` (required) + optional `suffix`
59
- # — the old `text` key was replaced and now fails the config schema.
53
+ # persona 行只认 `prefix`(必填)+ 可选 `suffix`:旧的 `text` 键在 0.1.5-rc.1 就被替换,
54
+ # 而下限 0.2.0-rc.2 上只有 prefix 形态。
60
55
  #
61
56
  # dsh >= 0.2.1-rc.2: only `provider` / `model` are registered prompt
62
57
  # variables. The old `cwd` variable is NOT one of them — `dsh-system-prompt`
@@ -89,9 +84,9 @@
89
84
  config:
90
85
  maxBytes: 65536
91
86
 
92
- # 0.2.1 对齐:time-context 从 host 层挪进预设行(dsh 0.2.1-rc.2 的内置 standard 有这一行)。
93
- # 包 @deepseek-ai/dsh-time-context 在 dsh 0.1.6-alpha.2 的依赖里也存在,
94
- # 且那一代 host 层没有挂它,所以旧版目录形态上补这一行不会重复注册、不会 broken。
87
+ # 0.2.1 起 time-context 从 host 层挪进预设行(0.2.1-rc.2 的内置 standard 有这一行)。
88
+ # 包 @deepseek-ai/dsh-time-context 在 0.2.0-rc.2 的依赖里也存在,而那一版的 dsh-base 与
89
+ # dsh-web-app 组合都没有挂它(已核对两层的 cordis.patch.yml),所以补这一行不会重复注册。
95
90
  - id: time-context
96
91
  name: '@deepseek-ai/dsh-time-context'
97
92
 
@@ -149,8 +144,8 @@
149
144
  - id: tool-skill
150
145
  name: '@deepseek-ai/dsh-tool-skill'
151
146
 
152
- # 0.3.7 补回:内置 `standard` 自 0.1.6-alpha.2 起就带这一行,本预设初次拷贝时整行漏掉了
153
- # (与 0.3.4 补回的 `present` 同类:不报错、不判 broken,只是少了能力)。
147
+ # 补回:内置 `standard` 一直带这一行,本预设初次拷贝时整行漏掉了 —— 不报错、不判 broken,
148
+ # 只是 CC 模式会话里没有 `/goal` 斜杠命令(与补回的 `present` 同类「静默少能力」)。
154
149
  # 它与下面 `tool-goal` 不是一回事:这行给的是**人**用的 `/goal` 斜杠命令,`tool-goal`
155
150
  # 才是给模型的工具。下面「goals」段那条注释说的「`/goal` command 留在宿主面」指的是命令
156
151
  # 背后的 goal SERVICE,不是这个声明行 —— 别据此再把它删掉。
@@ -254,7 +249,7 @@
254
249
  - id: tool-subagent-list-agents
255
250
  name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
256
251
 
257
- # 0.3.4 对齐 dsh 0.1.6-alpha.2 内置 standard:spawn 行此前漏抄 modelSelectionSettings。
252
+ # 对齐内置 standard(0.2.1 与 0.2.0 的这一行一致):spawn 行此前漏抄 modelSelectionSettings。
258
253
  # 该键在 dsh-tool-subagent 里是 `z.boolean().default(false)`,漏掉既不报错也不改 schema,
259
254
  # 只是子代理的「指定模型」入口被静默关掉 —— 最难发现的那类漂移。
260
255
  # fork 行故意不加:provider/model 必须等于父代理,继承的历史才能继续命中 KV cache。
@@ -271,12 +266,10 @@
271
266
  provider: fork
272
267
  toolName: subagent_fork
273
268
 
274
- # dsh 0.1.6-alpha.1: `dsh-workflow-worker-thread` is gone from the release;
275
- # the workflow engine is now `dsh-workflow-ptc` (sandboxed Node PTC runtime,
276
- # host row `ptc-runtime` → `dsh-ptc-runtime-node`).
277
- # dsh 0.1.7-alpha.1: 包名取不到的行由 `dsh-agent-preset-registry` 的 mountPreset →
278
- # auditRows 判死,落在 record.broken 上 —— roster 里仍看得到「CC 模式」,但带着
279
- # 诊断且组不出会话。失败形态变了,结论没变:包名必须与内置 standard 逐行对得上。
269
+ # 与 dsh 0.2.1-rc.2 内置 standard 对齐:workflow engine 是 dsh-workflow-ptc
270
+ # (宿主行 ptc-runtime → dsh-ptc-runtime-node)。包名取不到的行会被
271
+ # dsh-agent-preset-registry 的 mountPreset → auditRows 判死,落在 record.broken 上 ——
272
+ # roster 里仍看得到「CC 模式」,但带诊断且组不出会话。结论:包名必须与 standard 逐行对得上。
280
273
  - id: workflow-ptc
281
274
  name: '@deepseek-ai/dsh-workflow-ptc'
282
275
  config:
@@ -303,30 +296,29 @@
303
296
  fetch: true
304
297
  searchTimeoutMs: 60000
305
298
 
306
- # 0.3.4 补回:`present` 是 standard 一直有的行,本预设此前整行漏抄。
299
+ # 补回:`present` 是 standard 一直有的行,本预设此前整行漏抄。
307
300
  # 症状很隐蔽 —— CC 模式下模型没有「把成品登记为交付物」的工具,不报任何错,只是少了能力。
308
301
  # 它和下面那行都必须与 dsh 内置 standard 逐行对得上(升级清单见 AGENTS.md §4)。
309
302
  - id: present
310
303
  name: '@deepseek-ai/dsh-tool-present'
311
304
 
312
- # dsh 0.1.6-alpha.2 新增行:standard 里就是 disabled(默认关,需要的人自己放开)。
313
- # 抄过来是为了让「本预设 vs 内置 standard」的行差以后只剩有意为之的那几处,
314
- # 下次升级 diff 时不必再逐个判断「这行是新加的、还是我们漏抄了」。
315
- # 保留 disabled 也安全:0.1.7 的注册表对 disabled 行既不 import 也不参与
316
- # auditRows 行审计(dsh-agent-preset-registry lib/index.js:199)。
305
+ # 内置 standard 里就是 disabled(默认关,需要的人自己放开);
306
+ # 抄过来是为了让「本预设 vs 内置 standard」的行差只剩有意为之的那两处。
307
+ # 保留 disabled 也安全:注册表对 disabled 行既不 import 也不参与 auditRows 行审计。
317
308
  - id: tool-plugin-manager
318
309
  name: '@deepseek-ai/dsh-plugin-manager/tools'
319
310
  disabled: true
320
311
 
321
- # ── 与 dsh 0.2.1-rc.2 内置 standard 的**有意差异**(下次升级 diff 别当成漏抄)──
322
- # · `tool-schedule`:0.2.1 standard 有,但 `@deepseek-ai/dsh-tool-schedule` 在
323
- # dsh ≤ 0.1.6-alpha.2 的依赖里不存在,而这份清单同时喂给目录形态(旧版);
324
- # 非 disabled 行解析不到包会让**整个预设 broken**(unresolvableRows),
325
- # 所以为兼容旧版不带这一行。CC 模式也不需要定时任务工具。
326
- # · `tool-plugin-market`:0.2.1 standard 里是 disabled,但 0.1.6 没有
327
- # `@deepseek-ai/dsh-plugin-market` 这个包;按家规「不拿 disabled 遮掩
328
- # 一个已经不存在的包」,同样不带。
329
- # 其余行与 standard 逐行对齐;本预设只保留 persona 定制与末尾的 cc-agent 行。
312
+ # ── 与 dsh 0.2.1 内置 standard 的**有意差异**(下次升级 diff 别当成漏抄)──
313
+ # 本预设的下限是 0.2.0-rc.2(唯一实测过的 0.2.0),而下面两行指向的包在该版本的
314
+ # 依赖里都不存在(已对 0.2.0-rc.2 的 dsh / dsh-web-app 包树逐个核对):
315
+ # · `tool-schedule`:@deepseek-ai/dsh-tool-schedule 是 0.2.1 才有的包(0.2.0-rc.2 只有
316
+ # dsh-schedule)。非 disabled 行解析不到包会让**整个预设 broken**,所以为兼容下限不带。
317
+ # CC 模式也不需要定时任务工具。
318
+ # · `tool-plugin-market`:0.2.0-rc.2 没有 @deepseek-ai/dsh-plugin-market;按家规
319
+ # 「不拿 disabled 遮掩一个已经不存在的包」,同样不带。
320
+ # 想把它们补回来,必须先把兼容下限抬到 0.2.1-rc.2(同时改 package.json 与 README 矩阵)。
321
+ # 其余行与 0.2.1 的 standard 逐行对齐;本预设只保留 persona 定制与末尾的 cc-agent 行。
330
322
 
331
323
  # ── CC 角色卡工坊(CC 模式专用) ───────────────────────────────────
332
324
  - id: cc-agent
@@ -1,300 +0,0 @@
1
- # The `standard` agent preset: the full coding agent, mounted once per process.
2
- #
3
- # This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
4
- # standing scope; every session naming it joins by scope parentage, so the
5
- # tools and prompt sections registered here cover each joined agent while a
6
- # session's own state stays keyed per Session/Agent inside the plugins. The
7
- # host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
8
- # preset must not own: the registries themselves, the sandbox and approval
9
- # stack, persistence, and the model route.
10
- #
11
- # A service row here MUST sit inside a group carrying an `isolate` realm.
12
- # Without one it publishes into the root realm, where it is process-global —
13
- # another preset publishing the same name collides, and a host reader would
14
- # resolve one preset's instance for every session; `dsh-agent-presets` rejects
15
- # that at mount. `true` means an entry-local realm: this standing mount's own
16
- # private instance, apart from every other preset's. (A shared label does NOT
17
- # pool instances — `provide()` throws on the second registration under the
18
- # same realm symbol; labels join REALMS, and are not what this file needs.)
19
-
20
- # ── identity ────────────────────────────────────────────────────────────────
21
-
22
- # The preset's own persona, shadowing the deployment default for this agent.
23
- # dsh >= 0.1.5-rc.1: the persona row takes `prefix` (required) + optional `suffix`
24
- # — the old `text` key was replaced and now fails the config schema.
25
- #
26
- # dsh >= 0.2.1-rc.2: only `provider` / `model` are registered prompt
27
- # variables. The old `cwd` variable is NOT one of them — `dsh-system-prompt`
28
- # interpolates strictly and throws `unknown prompt variable` before the first
29
- # model request, which makes every turn in CC mode fail. The working directory
30
- # reaches the model as required runtime context instead, so never add that
31
- # variable back here.
32
- - id: persona
33
- name: '@deepseek-ai/dsh-persona'
34
- config:
35
- prefix: |
36
- 你是 CC 角色卡工坊的专属引导员(CC 模式),模型 {{model}}。
37
- 你的唯一任务是把用户的一句话点子变成可导入 SillyTavern / Risu 的完整 CCv3 卡(spec: chara_card_v3)。
38
- 必须走完强制工作流,不可偷懒跳过 5 维世界观和世界书:
39
- 1) cc_get_card — 先看草稿,告诉用户还缺什么
40
- 2) cc_patch_character — 补 name/nickname/description/personality/scenario/tags(至少 description/personality/scenario 非空,tags 同步胶囊风格标签)
41
- 3) cc_patch_world — 补五维 timeline/factions/geo/power/daily,至少填 3 项,autoLorebook=true 自动生成带 @@decorator 的基础条目;**同时给 loreKeys**(每一维自报用户真会说的实体名,如 {geo:["旧校舍","广播室","天台"]})—— 不给的话自动条目的 keys 只有维度名,真实对话永远命中不了
42
- 4) cc_add_lorebook_entries — 再补到至少 5 条,且**至少 1 条是你与用户共创的**(纯 autoLorebook 自动条目不算完成),覆盖年表/势力/地理/力量/日常各 1,至少 1 条 constant,content 头部写 @@position/@@depth/@@activate 等,支持 CBS 占位符(双花括号的 char / random / roll 形式);写完用 cc_simulate_trigger 拿一句用户会说的话验一下命中
43
- 5) cc_patch_greetings — 补 first_mes、alternate_greetings(≥2)、group_only_greetings(≥1 必填)、mes_example
44
- 6) cc_validate — 必须调且 valid=true 才算完成,否则按返回的 errors/warnings 继续补;**通过后必须立刻调用 cc_release_draft 交还草稿编辑权**(不调用的话工坊一直只读、用户无法微调),然后再提醒用户在工坊左下角「校验导出」入口打开导出页选 JSON / PNG / CHARX,或点「★ 保存当前」落盘 ~/.dsh/cc-library
45
-
46
- 辅助工具(按需,不必每步都调):cc_patch_book_meta(世界书命名 / scan_depth / token_budget)、cc_lint_lorebook(世界书静态体检)、cc_simulate_trigger(验触发词命中)、cc_preview_export(导出前干跑)、cc_new_card(重置草稿,**必须先经用户同意**再带 confirm:true)、cc_update_lorebook_entry / cc_delete_lorebook_entries(改删单条)、已存库 cc_list_library / cc_save_to_library / cc_load_from_library / cc_rename_in_library / cc_delete_from_library / cc_get_library_entry(载入后直接保存会更新原卡,不会新建重复卡)。
47
-
48
- 规则:每次对话只追问 1-2 个薄弱点,问完立刻调工具写入;用户说“随便/跳过”也要你合理推断补全,不能留空;严禁跳过 3/4 步直接做问候语;每调一次工具要简要总结“已写入 X,下一步 Y”,并让胶囊实时同步;结束前必须 cc_validate;**cc_validate 通过 = 创作完成,必须调用 cc_release_draft 交还草稿锁收口**(不调就算没做完;之后用户要改,你再写会自动重新上锁)。工具返回里只有一份 workflow.next(下一步该做什么)与 workflow.hint(进度摘要),照它走即可;用户还没回答就别抢跑。
49
-
50
- **工具参数必须是严格合法的 JSON**:最后一个字段 / 数组元素后不要写逗号,不要用 ```json 代码块包裹,不要注释、不要单引号;字符串里的换行写成 \n、双引号写成 \"。一次只传 1-3 个字段,长字段(description/personality/scenario)分多次调用写;tags / loreKeys 这类数组或对象字段单独一次调用,不要和长文本同批。参数 JSON 非法时 provider 会在调用前直接判本轮失败(用户只看到「本轮运行失败」),所以宁可分多次小调用。
51
-
52
- - id: agent-instructions
53
- name: '@deepseek-ai/dsh-agent-instructions'
54
- config:
55
- maxBytes: 65536
56
-
57
- # 0.2.1 对齐:time-context 从 host 层挪进预设行(dsh 0.2.1-rc.2 的内置 standard 有这一行)。
58
- # 包 @deepseek-ai/dsh-time-context 在 dsh 0.1.6-alpha.2 的依赖里也存在,
59
- # 且那一代 host 层没有挂它,所以旧版目录形态上补这一行不会重复注册、不会 broken。
60
- - id: time-context
61
- name: '@deepseek-ai/dsh-time-context'
62
-
63
- # ── shell ───────────────────────────────────────────────────────────────────
64
-
65
- # `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
66
- # publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
67
- # the criterion for host-plane ownership — injection resolves before any session
68
- # exists, so there is no agent to key by. Behind a preset realm those variables
69
- # never reached the model's shell at all. Both shell tools consume the host
70
- # registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
71
- # host-plane too.
72
- - id: tool-bash
73
- name: '@deepseek-ai/dsh-tool-bash'
74
- disabled: !!js process.platform === 'win32'
75
-
76
- - id: tool-pwsh
77
- name: '@deepseek-ai/dsh-tool-pwsh'
78
- disabled: !!js process.platform !== 'win32'
79
-
80
- # ── filesystem ──────────────────────────────────────────────────────────────
81
-
82
- # Both register into the host `tools` registry and provide nothing, so
83
- # they need no realm. The `fs` service and its policy stay in the host.
84
- - id: tool-fs
85
- name: '@deepseek-ai/dsh-tool-fs'
86
-
87
- - id: tool-fs-search
88
- name: '@deepseek-ai/dsh-tool-fs-search'
89
- config:
90
- sampleOverCapGlobResults: false
91
-
92
- # ── background jobs ────────────────────────────────────────────────────────
93
-
94
- # Only the model-facing controls. The task REGISTRY stays on the host plane:
95
- # its producers sit outside any realm this file could put it in — `tool-bash`
96
- # above resolves it with `ctx.get`, and an entry-local realm here is invisible
97
- # to every sibling row, so `run_in_background` would answer "background jobs
98
- # unavailable" while these controls sat in the catalog. The registry is keyed by
99
- # owning agent anyway, so one host instance serves every session. What a preset
100
- # chooses is whether its agent can collect and stop background work at all.
101
- - id: tool-jobs
102
- name: '@deepseek-ai/dsh-tool-jobs'
103
-
104
- # ── skills ──────────────────────────────────────────────────────────────────
105
-
106
- # The skill REGISTRY lives in the host composition and is layered per scope:
107
- # these rows register into THIS preset's layer of it, so they need no realm.
108
- # `skill-filesystem` contributes local-root discovery for agents on this preset, and
109
- # `tool-skill` gives them the catalog and loader; the merged catalog also
110
- # carries whatever the deployment registered globally (repository plugins).
111
- - id: skill-filesystem
112
- name: '@deepseek-ai/dsh-skill-filesystem'
113
-
114
- - id: tool-skill
115
- name: '@deepseek-ai/dsh-tool-skill'
116
-
117
- # 0.3.7 补回:内置 `standard` 自 0.1.6-alpha.2 起就带这一行,本预设初次拷贝时整行漏掉了
118
- # (与 0.3.4 补回的 `present` 同类:不报错、不判 broken,只是少了能力)。
119
- # 它与下面 `tool-goal` 不是一回事:这行给的是**人**用的 `/goal` 斜杠命令,`tool-goal`
120
- # 才是给模型的工具。下面「goals」段那条注释说的「`/goal` command 留在宿主面」指的是命令
121
- # 背后的 goal SERVICE,不是这个声明行 —— 别据此再把它删掉。
122
- - id: command-goal
123
- name: '@deepseek-ai/dsh-command-goal'
124
-
125
- # ── goals ───────────────────────────────────────────────────────────────────
126
-
127
- # Only the model-facing tool. The goal SERVICE, its session driver, and the
128
- # `/goal` command stay on the host plane: the Gateway serves the goal domain as
129
- # Remote endpoints whose receiver comes from a generated descriptor, so it
130
- # resolves `goals` on the host and an entry-local realm here would hide it. The
131
- # registry is keyed by session anyway, so one host instance serves every
132
- # session. What a preset chooses is whether its agent can call the goal tool.
133
- - id: tool-goal
134
- name: '@deepseek-ai/dsh-tool-goal'
135
-
136
- # ── plan mode ───────────────────────────────────────────────────────────────
137
-
138
- # Plan state is per-agent by nature, so an entry-local realm is not a
139
- # workaround here — it is the correct lifetime.
140
- - id: planning
141
- name: cordis:group
142
- group: true
143
- isolate:
144
- planMode: true
145
- config:
146
- - id: plan-mode
147
- name: '@deepseek-ai/dsh-plan-mode'
148
- config:
149
- section: |
150
- You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
151
-
152
- Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
153
-
154
- The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
155
-
156
- Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
157
-
158
- Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
159
-
160
- When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
161
-
162
- # ── compaction ──────────────────────────────────────────────────────────────
163
-
164
- # `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
165
- # share this realm rather than sit outside it.
166
- #
167
- # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
168
- # plane, and the rows here resolve that one instance. It takes no configuration,
169
- # keys every fold by Session, and owns the context-meter projection units the
170
- # browser reads for every session — behind a realm those units would come and go
171
- # with whichever presets happen to be mounted. What a preset chooses is whether
172
- # its agent compacts at all, which is `compaction-basic` below.
173
- - id: compaction
174
- name: cordis:group
175
- group: true
176
- isolate:
177
- compaction: true
178
- toolResultPruner: true
179
- config:
180
- - id: compaction-basic
181
- name: '@deepseek-ai/dsh-compaction-basic'
182
-
183
- - id: command-compact
184
- name: '@deepseek-ai/dsh-command-compact'
185
-
186
- - id: tool-result-pruner
187
- name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
188
- config:
189
- thresholdChars: 8192
190
- headChars: 4096
191
- tailChars: 1024
192
-
193
- # ── delegation and workflows ────────────────────────────────────────────────
194
-
195
- # The `subagents` registry and its spawn/fork backends live in the HOST
196
- # composition: the registry is a process singleton whose cross-session queries
197
- # the api-proxy serves to the browser, and a provider name may only be
198
- # registered once. This preset contributes the delegation TOOLS, which resolve
199
- # that host registry.
200
- #
201
- # `workflows` is different — nothing outside an agent reads it — so every row
202
- # that reaches it shares one entry-local realm here, and a consumer left
203
- # outside would resolve a host registry this preset does not populate.
204
- #
205
- # `tool-subagent-report` is host-plane for the same reason as the registry,
206
- # not because a preset may not want it: it registers a CONTINUABLE SETUP on
207
- # that singleton rather than a tool this agent calls, and the setup list is
208
- # not scope-aware — one copy per mounted preset means every child gets
209
- # `report` registered once per live session, which throws on the second.
210
- - id: delegation
211
- name: cordis:group
212
- group: true
213
- isolate:
214
- workflowEngine: true
215
- config:
216
- - id: tool-subagent-control
217
- name: '@deepseek-ai/dsh-tool-subagent-control'
218
-
219
- - id: tool-subagent-list-agents
220
- name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
221
-
222
- # 0.3.4 对齐 dsh 0.1.6-alpha.2 内置 standard:spawn 行此前漏抄 modelSelectionSettings。
223
- # 该键在 dsh-tool-subagent 里是 `z.boolean().default(false)`,漏掉既不报错也不改 schema,
224
- # 只是子代理的「指定模型」入口被静默关掉 —— 最难发现的那类漂移。
225
- # fork 行故意不加:provider/model 必须等于父代理,继承的历史才能继续命中 KV cache。
226
- - id: tool-subagent
227
- name: '@deepseek-ai/dsh-tool-subagent'
228
- config:
229
- provider: spawn
230
- toolName: subagent
231
- modelSelectionSettings: true
232
-
233
- - id: tool-subagent-fork
234
- name: '@deepseek-ai/dsh-tool-subagent'
235
- config:
236
- provider: fork
237
- toolName: subagent_fork
238
-
239
- # dsh 0.1.6-alpha.1: `dsh-workflow-worker-thread` is gone from the release;
240
- # the workflow engine is now `dsh-workflow-ptc` (sandboxed Node PTC runtime,
241
- # host row `ptc-runtime` → `dsh-ptc-runtime-node`). Preset health check
242
- # (`unresolvableRows`) rejects any started row whose package cannot be
243
- # resolved, so the old name marks the WHOLE preset broken — unselectable in
244
- # the roster. Keep this in sync with the shipped `standard` preset.
245
- - id: workflow-ptc
246
- name: '@deepseek-ai/dsh-workflow-ptc'
247
- config:
248
- provider: spawn
249
-
250
- - id: tool-workflow
251
- name: '@deepseek-ai/dsh-tool-workflow'
252
-
253
- # ── remaining model-facing rows ─────────────────────────────────────────────
254
-
255
- - id: tool-ask-user
256
- name: '@deepseek-ai/dsh-tool-ask-user'
257
-
258
- - id: tool-todo
259
- name: '@deepseek-ai/dsh-tool-todo'
260
- config:
261
- allowParallelInProgress: true
262
-
263
- # The `web` service and its search provider stay in the host composition; only
264
- # the model-facing tool is per-session.
265
- - id: tool-web
266
- name: '@deepseek-ai/dsh-tool-web'
267
- config:
268
- fetch: true
269
- searchTimeoutMs: 60000
270
-
271
- # 0.3.4 补回:`present` 是 standard 一直有的行,本预设此前整行漏抄。
272
- # 症状很隐蔽 —— CC 模式下模型没有「把成品登记为交付物」的工具,不报任何错,只是少了能力。
273
- # 它和下面那行都必须与 dsh 内置 standard 逐行对得上(升级清单见 AGENTS.md §4)。
274
- - id: present
275
- name: '@deepseek-ai/dsh-tool-present'
276
-
277
- # dsh 0.1.6-alpha.2 新增行:standard 里就是 disabled(默认关,需要的人自己放开)。
278
- # 抄过来是为了让「本预设 vs 内置 standard」的行差以后只剩有意为之的那几处,
279
- # 下次升级 diff 时不必再逐个判断「这行是新加的、还是我们漏抄了」。
280
- # 保留 disabled 也安全:健康检查 unresolvableRows 会跳过 disabled 行
281
- # (dsh-agent-presets lib/index.js:292 `if (Boolean(row.disabled)) continue;`)。
282
- - id: tool-plugin-manager
283
- name: '@deepseek-ai/dsh-plugin-manager/tools'
284
- disabled: true
285
-
286
- # ── 与 dsh 0.2.1-rc.2 内置 standard 的**有意差异**(下次升级 diff 别当成漏抄)──
287
- # · `tool-schedule`:0.2.1 standard 有,但 `@deepseek-ai/dsh-tool-schedule` 在
288
- # dsh ≤ 0.1.6-alpha.2 的依赖里不存在,而这份清单同时喂给目录形态(旧版);
289
- # 非 disabled 行解析不到包会让**整个预设 broken**(unresolvableRows),
290
- # 所以为兼容旧版不带这一行。CC 模式也不需要定时任务工具。
291
- # · `tool-plugin-market`:0.2.1 standard 里是 disabled,但 0.1.6 没有
292
- # `@deepseek-ai/dsh-plugin-market` 这个包;按家规「不拿 disabled 遮掩
293
- # 一个已经不存在的包」,同样不带。
294
- # 其余行与 standard 逐行对齐;本预设只保留 persona 定制与末尾的 cc-agent 行。
295
-
296
- # ── CC 角色卡工坊(CC 模式专用) ───────────────────────────────────
297
- - id: cc-agent
298
- name: '@xia-sc/dsh-cc-studio/agent'
299
-
300
-
@@ -1,2 +0,0 @@
1
- name: CC 模式
2
- description: CCv3 角色卡工坊 — 用对话把点子补成完整的角色卡(chara_card_v3):模型先问再填,胶囊实时同步,工坊全屏编辑,导出 JSON / PNG / CHARX。