@xia-sc/dsh-cc-studio 0.9.1 → 0.10.3
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 +17 -6
- package/lib/agent.js +290 -350
- package/lib/client.js +32 -7
- package/lib/group-layer.js +624 -0
- package/lib/index.js +124 -152
- package/package.json +2 -2
- package/presets/cc/agent.cordis.yml +10 -2
- package/presets/cc.patch.yml +10 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
|
|
|
14
14
|
|
|
15
15
|
| 项目 | 值 |
|
|
16
16
|
| --- | --- |
|
|
17
|
-
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.
|
|
17
|
+
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.10.3` |
|
|
18
18
|
| 兼容 | DSH `>=0.4.5-rc.4`(含**桌面版**)、Node `>=22`、**零运行依赖**;逐版本实测见 [兼容与权限](#兼容与权限) |
|
|
19
19
|
| 落盘位置 | `<DSH_HOME>/cc-library/`(角色卡)、`<DSH_HOME>/cc-drafts/`(会话草稿);`DSH_HOME` 默认 `~/.dsh`,旧位置仍读得到 |
|
|
20
20
|
| 文档 | 用户文档=本文件;改代码前必读 [AGENTS.md](./AGENTS.md);完整历史 [CHANGELOG.md](./CHANGELOG.md) |
|
|
@@ -42,7 +42,7 @@ 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.
|
|
45
|
+
5. 重启后可选自检:在「CC 模式」会话里调任意一个 cc_* 工具,返回里带 "build": "0.10.3" 即说明新代码已加载
|
|
46
46
|
(这也是判断「改了但没重启」的唯一可靠依据)。
|
|
47
47
|
6. 不要做的事:
|
|
48
48
|
- 不要手工把 presets/ 拷进 ~/.dsh/.agent-presets/(dsh >= 0.4.7-alpha.4 已不读该目录,拷了也不生效);
|
|
@@ -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.
|
|
61
|
+
dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.10.3 # 锁定版本
|
|
62
62
|
|
|
63
|
-
# 从 GitHub 源安装(可加 #v0.
|
|
63
|
+
# 从 GitHub 源安装(可加 #v0.10.3 / #master 指定 tag / 分支)
|
|
64
64
|
dsh plugin --profile web add github:xia-sc/dsh-cc-studio
|
|
65
65
|
|
|
66
66
|
# 从本地仓库安装(开发 / 调试):装出来是 link:,改 lib/*.js 重启即生效
|
|
@@ -92,6 +92,8 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
|
|
|
92
92
|
| 重启后浏览器报 `Failed to load plugins` / `web boot: 4 entry did not activate` | 包名迁移后没按新名重装(profile 里的安装身份还是旧名)。看一眼 `~/.dsh/profiles/web/node_modules/` 下的目录名是否等于 `package.json` 的 `name`;修法是按新名重装 → 重启 → **硬刷新**(`Ctrl+Shift+R`) |
|
|
93
93
|
| 工坊顶部橙条「草稿已新建」 | 该会话没有历史草稿(首次使用或草稿目录被清)。已存档会话恢复时是蓝条并显示 `creation_date`,下次写入后提示消失 |
|
|
94
94
|
| 改完代码没变化 | 分三层看:改 `lib/client.js` → 刷新页面;改 `lib/index.js`(宿主)或预设 → **重启 dsh(桌面版重启应用)**;先看一眼工具返回里的 `build` 是不是你以为的版本 |
|
|
95
|
+
| 偶发「本轮运行失败 `tool input is invalid JSON`」 | 模型生成的工具参数 JSON 有语法错误(最常见是尾逗号),DSH provider 严格 `JSON.parse` 后判 `MALFORMED_RESPONSE`,且默认不重试。0.10.2 已在 persona 与五个写类工具描述里要求「严格 JSON + 分小步写(数组字段单独一批)」,**重启 dsh 让新文本生效**;仍偶发就重发同一句话(模型会重新生成),或在 profile 的 `cordis.patch.yml` 给 `llm-deepseek-account` 加 `retryPolicy`(`retryableCodes` 要**写全默认 5 码 + `MALFORMED_RESPONSE`** —— 补丁层对 config 是整条替换、不做深合并),完整片段见 CHANGELOG 0.10.2 |
|
|
96
|
+
| 卡里的换行显示成字面 `\n`(如「说完这句话。\n\n下一段」) | 模型在 tool call 里把换行写成了字面 `\n`(JSON 多转义一层),旧版工具原样落盘。0.10.3 起写类工具统一解码字面 `\n` / `\r` / `\t`(含多层转义),**重启 dsh 生效**。⚠️ 已存卡里已经被写坏的字段不会自动修复 —— 重新调一次对应工具写入那几个字段,或在工坊里手动改回真换行 |
|
|
95
97
|
|
|
96
98
|
---
|
|
97
99
|
|
|
@@ -174,8 +176,9 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
|
|
|
174
176
|
## 开发与测试
|
|
175
177
|
|
|
176
178
|
```bash
|
|
177
|
-
npm test #
|
|
178
|
-
node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(
|
|
179
|
+
npm test # 22 个文件、864 项断言,全绿才算过
|
|
180
|
+
node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(104 项)
|
|
181
|
+
node tests/group-layer-guard.test.mjs # 只跑卡组层的耦合守卫(16 项)
|
|
179
182
|
```
|
|
180
183
|
|
|
181
184
|
- 改 `lib/client.js`:刷新页面即可;改 `lib/index.js` / 预设:**重启 dsh**。
|
|
@@ -185,6 +188,14 @@ node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(97 项
|
|
|
185
188
|
|
|
186
189
|
完整历史见 [CHANGELOG.md](./CHANGELOG.md)。最近几版大多是「另一个 LLM 以工具使用者身份跑回归」挖出来的问题:
|
|
187
190
|
|
|
191
|
+
- `0.10.3` **多行文本被写成字面 `\n`**:用户实机报告(六步全链路 `valid: true`)发现经 `cc_patch_character` / `cc_patch_greetings` / `cc_patch_world` 写入的字符串,换行全部落盘成字面 `\` + `n`,导进 SillyTavern 显示成「说完这句话。\n\n下一段」。取证推翻了「工具多转义」的猜测:工具层往返测试证明真换行原样保留;会话日志里模型 18 次调用有 6 次在 JSON 里把换行写成 `\\n`(多转义一层),而 `alternate_greetings` / lorebook 却写对了 —— 是**模型输出不一致**。修法:写类工具统一 `textArg()` 解码字面 `\n` / `\r` / `\t`(多层转义最多 4 轮收敛),覆盖角色 / 问候语 / 世界书条目 / 书级元信息 / 共享书;工坊保存与导入路径不动。顺带修报告 P2(卡内 `token_budget` 默认 1200 → **2400**)与 P4(autoLorebook 的「力量体系」条目裸 `@@activate` → `@@activate 100`)。测试 858 → **864 项**。**重启 dsh 生效**;已存卡里已写坏的字段需重写。
|
|
192
|
+
|
|
193
|
+
- `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(桌面版重启应用)生效**。
|
|
194
|
+
|
|
195
|
+
- `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(桌面版重启应用)生效**,无需重装或刷新页面。
|
|
196
|
+
|
|
197
|
+
- `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(桌面版重启应用)→ 刷新页面**。
|
|
198
|
+
|
|
188
199
|
- `0.9.1` **共享世界书导不进 SillyTavern**:把 CCv3 独立 lorebook(`{spec:"lorebook_v3", data}`)拖进「世界信息 → 导入」只会得到 `Error importing world info: … Bad Request`。根因有两层(对着本地 ST 1.19.0 源码 + 实测复现):服务端 `/api/worldinfo/import` 拿到文件第一句就是 `if (!('entries' in worldContent)) throw` —— 顶层没有 `entries` 一律 **400**;而 ST 客户端 `importWorldInfo()` 只转 Novel / Agnai / Risu,整份 `world-info.js` 里搜不到 `lorebook_v3`(**它不是「换个后缀」能解决的**)。另外 ST 的 `parseDecorators()` 只认 `@@activate` / `@@dont_activate`,其余 `@@` 行会**连同正文一起被丢掉**。修法是多一种形态而非改掉原来那种:共享书页与导出页各多一枚「⬇ SillyTavern 世界书」,导出 `{entries:{"<uid>":{…}}}`,字段照抄 ST 自己的 `convertCharacterBook()`,`@@position` / `@@depth` 翻译成 ST 的 `position` / `depth` 并从正文删掉(`@@activate` 原样留给 ST),文件名 = 书名(ST 用上传文件名当世界名);**CCv3 那份一个字节没动**。实测:同一本真书导出的 ST 文件导入返回 **200 + `{"name":…}`**、ST 落盘、条目 uid 与源 `id` 一致、「年表」落在 `position:1`。测试 647 → 683 项(16 个文件)。**同一版还补齐了 SillyTavern 兼容层**:PNG 现在同时写 `ccv3`(纯 CCv3)与 `chara`(诚实的 V2 投影,按规范摘掉装饰器)两个块,读取器改成**按载荷里的 spec 判并优先 `ccv3`**(此前是「取第一个命中的块」,而 ST 导出的 PNG 是 `chara` 在前,于是把 ST 导出的卡拖进工坊会报「spec 需为 chara_card_v3」);导出 PNG / CHARX / JSON 时若该卡所属分组有共享书,会**只在导出副本上**写 ST 的世界书绑定(`data.extensions.world = 书名`)—— ST 里的顺序是先导世界书、再导卡,绑定就自动接上(卡内私有条目还要点一次 Import Card Lore)。规范同样明文禁止往卡片里加应用私有字段,所以卡片顶层一个 ST 私有键都不加。实测:我们导出的双块 PNG 进 ST 返回 **200**,ST 回读 `data.extensions.world`、卡内 3 条书条目与 V3 独有字段俱在且世界名可解析;ST 落盘的「V2 在前」PNG 我们也能读了。测试 683 → **718 项(17 个文件)**。**同一版还有第三步:V2 卡现在能进工坊了** —— SillyTavern 里绝大多数存量卡是 V2(ST 界面新建的就是 V2 形状,只是 `spec` 被改写成 v3 标签),此前拖 PNG/JSON 进来只会得到「spec 需为 chara_card_v3」。现在 `upgradeCardToV3()` 一份实现管 PNG / CHARX / JSON 三条导入路径(规则照 ST 的 `readFromV2()`:顶层 V1 平铺字段只在 `data` 缺值时补、`creatorcomment`→`creator_notes`、`talkativeness`/`fav`→`data.extensions`、纯 V1 整份包成 `data`),导入返回带 `upgradedFrom`、界面明说「V2 卡已升级为 CCv3」(不静默变换);JSON 导入也收口到宿主的新端点 `cc_importFromJson`(客户端不再自己 parse 卡 JSON)。`cc_setDraft` 故意不升级(它是工坊自己草稿的镜像)。测试 718 → **735 项(17 个文件)**。**需重启 dsh(桌面版重启应用)→ 刷新页面**。
|
|
189
200
|
|
|
190
201
|
- `0.9.0` **「共享世界书」的收口版**:0.8.2 把世界设定从「每张卡一份」挪到了「组上一本」,但**所有读数、判定与质量检查还按「卡里」算** —— 五轮 LLM 侧回归把它逐个补平:四个读数工具(`simulate` / `lint` / `group_check` / `preview_export`)全部拆成「**卡内 / 共享书 / 合计**」三段(`simulate` 此前会把「会命中」报成「不会命中」,实测低估约 3 倍);**提升去重后自动同步草稿**(否则下一次保存会把重复原样写回卡里);`cc_validate` 的「世界书 ≥5 条」改走合计口径(按最佳实践去重过的卡此前**必然判不合格**);重复检测补上「**卡 ↔ 共享书**」一类(`bookDuplicates` / `wouldClean`);`constant` 与 `@@decorator` 的存在性检查两端统一;**共享书自己的逐条质量检查**补上(此前「共享书质量走 `cc_get_shared_book`」只是一句没实现的说明 —— 那条线上只有重复检测)。口径与质量各收口成一个纯函数(`lorebookMetrics` / `auditLoreEntries`),以后改口径只动一处。测试 604 → 647 项(15 个文件)。
|