@xia-sc/dsh-cc-studio 0.11.2 → 0.11.5
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 +16 -9
- package/lib/agent.js +1 -1
- package/lib/client.js +160 -2
- package/lib/index.js +105 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ DSH(DeepSeek Harness)插件:输入框上方的胶囊 → 全屏融合工
|
|
|
14
14
|
|
|
15
15
|
| 项目 | 值 |
|
|
16
16
|
| --- | --- |
|
|
17
|
-
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.11.
|
|
17
|
+
| 包名 / 版本 | `@xia-sc/dsh-cc-studio` `0.11.5` |
|
|
18
18
|
| 兼容 | DSH `>=0.1.5-rc.1`(含**桌面版**)、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) |
|
|
@@ -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.11.
|
|
45
|
+
5. 重启后可选自检:在「CC 模式」会话里调任意一个 cc_* 工具,返回里带 "build": "0.11.5" 即说明新代码已加载
|
|
46
46
|
(这也是判断「改了但没重启」的唯一可靠依据)。
|
|
47
47
|
6. 不要做的事:
|
|
48
48
|
- 不要手工把 presets/ 拷进 ~/.dsh/.agent-presets/(dsh >= 0.1.7-alpha.1 已不读该目录,拷了也不生效);
|
|
@@ -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.
|
|
61
|
+
dsh plugin --profile web add @xia-sc/dsh-cc-studio@0.11.5 # 锁定版本
|
|
62
62
|
|
|
63
|
-
# 从 GitHub 源安装(可加 #v0.11.
|
|
63
|
+
# 从 GitHub 源安装(可加 #v0.11.5 / #master 指定 tag / 分支)
|
|
64
64
|
dsh plugin --profile web add github:xia-sc/dsh-cc-studio
|
|
65
65
|
|
|
66
66
|
# 从本地仓库安装(开发 / 调试):装出来是 link:,改 lib/*.js 重启即生效
|
|
@@ -113,13 +113,14 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
|
|
|
113
113
|
### 融合工坊(客户端)
|
|
114
114
|
|
|
115
115
|
- **布局**:胶囊(CC 模式下常驻)→ `shell.overlay` 全屏工坊;左侧 3 页导航(**5 维世界观 / 角色细化 / 世界书**)+ 左下角「**校验导出**」,中部自适应阅读区,右侧 268px 可折叠「已存角色」(收起成 40px 竖条)。主区底部两块默认收起:实时 `card.json` 预览与 CBS 速查。
|
|
116
|
-
- **导出入口只有两处**(0.4.
|
|
116
|
+
- **导出入口只有两处**(0.4.1 归一):左下角「校验导出」与卡片上的「导出」,打开的是同一个导出页(`JSON / PNG / 写入PNG / CHARX / 校验`)。导出页导出的**永远是当前工坊里的卡**(标题后带卡名),要导某张已存卡请先「载入」——卡片按钮点下去会先变成「已切到 ✓」,若那张卡尚未载入,页内会给出醒目提示。
|
|
117
117
|
- **文档式阅读排版**:字段是无边框 textarea、按内容自动撑高,点进去即可改;长文点「⛶」开 720px 大框(带打开时快照,「取消」真回滚)。写作是主线,手动编辑是次要入口。
|
|
118
118
|
- **5 维世界观**:年表 / 势力 / 地理 / 力量体系 / 日常 → `autoLorebook` 自动生成带 `@@position / @@depth / @@activate` 的条目(至少 4 条 `constant`;再次调用覆盖旧自动条目、保留手工条目)。
|
|
119
119
|
- **ID 化 CRUD**:`★ 保存当前`(已载入时原地覆盖)/ `+ 另存为新` / `✎ 重命名` / `+ 新建` / 载入 / 删除,搜索、高亮当前卡,落盘 `<DSH_HOME>/cc-library/<id>.json`。
|
|
120
120
|
- **LLM 创作保护(草稿锁,0.11.0)**:LLM 每次写草稿都会上锁(`<DSH_HOME>/cc-drafts/<槽>.lock.json`),锁 active 期间工坊只读、载入/导入只进**只读预览**(草稿与来源 id 都不写),退出预览即回到 LLM 的草稿;LLM 调 `cc_release_draft` 交还、或锁 30 分钟无写入自动失效后恢复编辑。设置页可关掉该保护(关闭 = 旧行为)。任何覆盖型动作(载入/导入/迁移/force 覆盖)前都会把当前草稿备份到 `<DSH_HOME>/cc-drafts/backups/`(同槽最多 20 份,空壳与同内容不重复备份)
|
|
121
121
|
- **导入导出**:`⬆ 导入 JSON/PNG/CHARX` 自动识别容器;`⬇ JSON`、`⬇ PNG`(4×4 占位图)、`⬆ 写入 PNG`(写入你上传的任意 PNG,自动剥离旧 `ccv3/chara` 块并重算 CRC32)、`⬇ CHARX`(打包 `card.json`)。
|
|
122
122
|
- **中英双语 + 深浅色**:完整 `zh / en` 词表,跟随 `设置 → 通用 → 语言` 实时切换;配色走 `var(--dsw-alias-*)`,主操作固定品牌紫 `#7c5cff`。
|
|
123
|
+
- **检查更新(0.11.3)**:设置 →「角色卡工坊」→ 插件更新,点一下查 npm 上的最新版本。可更新时列出 `v最新(当前 v本地)`,并给出可一键复制的 `@xia-sc/dsh-cc-studio@版本`(另附 `dsh plugin --profile <配置名> add …` 命令提示);已是最新、本机是开发版本、检查失败各有对应文案。查询在宿主做(浏览器不直连 npm,省掉 CORS 与代理差异),断网 / registry 异常会如实报错并保留「当前版本」。
|
|
123
124
|
|
|
124
125
|
### CC 模式(模型侧 33 个 Tool)
|
|
125
126
|
|
|
@@ -166,21 +167,21 @@ dsh plugin --profile web remove @xia-sc/dsh-cc-studio # 卸载(同时移
|
|
|
166
167
|
|
|
167
168
|
| 项 | 值 |
|
|
168
169
|
| --- | --- |
|
|
169
|
-
| 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.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` / `0.2.1-rc.3` / `0.2.1-rc.5` 为 `compatible`。**没实测过的版本不写**(`0.1.7-alpha.2` / `0.1.7-rc.2` / `0.2.0-rc.1` / `0.2.1-alpha.1` / `0.2.1-rc.4` 保持未声明) |
|
|
170
171
|
| 宿主形态 | `dsh web`(CLI)与 **DSH 桌面版**同一套宿主 + profile bundles 机制,**均支持** |
|
|
171
172
|
| 客户端平台 | `dsh.client.platform: "web"` 是**浏览器半**的平台标识,与 profile 名(`web` / `desktop`)不是一回事;两者加载同一份客户端 bundle |
|
|
172
173
|
| 运行依赖 / 生命周期脚本 | **都没有**。`lib/*.js` 只 `import` `node:` 内建与相对路径,随包补丁与预设是静态 YAML |
|
|
173
174
|
| 权限 · 文件 | 只读写 `<DSH_HOME>`(含旧根 `~/.dsh`)下自己的五个目录:`cc-drafts/`、`cc-library/`、`cc-groups/`、`cc-books/`、`.agent-presets/cc/`(最后一个只在 dsh ≤ `0.1.6-alpha.2` 上写),另读自身 `package.json` |
|
|
174
|
-
| 权限 · 网络 / 命令 / 凭据 |
|
|
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`),插件不解析凭据 |
|
|
175
176
|
|
|
176
|
-
> **为什么商城不会给它「自动通过」**:STORE 的自动低风险通道要求运行时代码完全不出现文件 / 网络 / 命令 /
|
|
177
|
+
> **为什么商城不会给它「自动通过」**:STORE 的自动低风险通道要求运行时代码完全不出现文件 / 网络 / 命令 / 凭据四类信号,而本插件的核心功能就是草稿与角色卡落盘(文件信号)、读 `DSH_HOME`(扫描器把任何 `process.env` 读取都算凭据信号),再加上 0.11.3 起「检查更新」的一次出站请求(网络信号)。这些都不会为了迎合扫描器而消除 —— 对这类合法插件,商城的正确档位是 `user-reviewed`。隔离环境下的安装 / 启动 / 卸载证据见 [CHANGELOG.md](./CHANGELOG.md) 的 `0.3.7` 小节。
|
|
177
178
|
|
|
178
179
|
---
|
|
179
180
|
|
|
180
181
|
## 开发与测试
|
|
181
182
|
|
|
182
183
|
```bash
|
|
183
|
-
npm test #
|
|
184
|
+
npm test # 25 个文件、1041 项断言,全绿才算过
|
|
184
185
|
node tests/tools-contract.test.mjs # 只跑 CC 工具链契约(125 项)
|
|
185
186
|
node tests/group-layer-guard.test.mjs # 只跑卡组层的耦合守卫(16 项)
|
|
186
187
|
```
|
|
@@ -192,6 +193,12 @@ node tests/group-layer-guard.test.mjs # 只跑卡组层的耦合守卫(1
|
|
|
192
193
|
|
|
193
194
|
完整历史见 [CHANGELOG.md](./CHANGELOG.md)。最近几版大多是「另一个 LLM 以工具使用者身份跑回归」挖出来的问题:
|
|
194
195
|
|
|
196
|
+
- `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`。**运行时代码与预设一个字没改**。
|
|
197
|
+
|
|
198
|
+
- `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 后照常用,无需重装或改预设。
|
|
199
|
+
|
|
200
|
+
- `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(桌面端重启应用)+ 刷新页面生效**。
|
|
201
|
+
|
|
195
202
|
- `0.11.2` **修正中文 README 两处文档错误**:安装引导第 1 步被写成 `4.`;一批 dsh 版本号被写坏成 `0.4.x`(真实范围 `>=0.1.5-rc.1`),兼容范围 / 预设分界 / 实测矩阵已纠正,矩阵补上 `0.2.1-rc.2`。纯文档,无需重启。
|
|
196
203
|
|
|
197
204
|
- `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(桌面端重启应用)生效**。
|
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.
|
|
26
|
+
const BUILD = "0.11.5";
|
|
27
27
|
const inject = ["tools", "agents"];
|
|
28
28
|
|
|
29
29
|
function nowSec(){ return Math.floor(Date.now()/1000); }
|
package/lib/client.js
CHANGED
|
@@ -141,6 +141,21 @@ window.__ModuleLoader__.load({
|
|
|
141
141
|
"settings.behavior.autoSave":"导入后自动 ★ 保存",
|
|
142
142
|
"settings.behavior.autoSaveDesc":"导入后自动落盘到 ~/.dsh/cc-library(新建 ID),省去再点保存。",
|
|
143
143
|
"settings.behavior.storageHint":"存储键 localStorage:dsh-cc-studio-settings,切预设/会话间共享。",
|
|
144
|
+
"settings.update.title":"插件更新",
|
|
145
|
+
"settings.update.desc":"检查 npm 上的最新版本;有更新时给出可复制的「包名@版本」,到终端用 dsh 更新。",
|
|
146
|
+
"settings.update.check":"检查更新",
|
|
147
|
+
"settings.update.checking":"检查中…",
|
|
148
|
+
"settings.update.recheck":"重新检查",
|
|
149
|
+
"settings.update.current":"当前版本 v{v}",
|
|
150
|
+
"settings.update.available":"发现新版本 v{latest}(当前 v{current})",
|
|
151
|
+
"settings.update.upToDate":"已是最新版本(v{current})",
|
|
152
|
+
"settings.update.localNewer":"本机 v{current} 比 npm 上的 v{latest} 更新(开发版本)",
|
|
153
|
+
"settings.update.copy":"复制",
|
|
154
|
+
"settings.update.copied":"已复制",
|
|
155
|
+
"settings.update.copyFailed":"复制失败:请手动选中上面的文本复制",
|
|
156
|
+
"settings.update.commandHint":"在终端执行:dsh plugin --profile <配置名> add {spec}",
|
|
157
|
+
"settings.update.error":"检查失败:{msg}",
|
|
158
|
+
"settings.update.unknownError":"未知错误",
|
|
144
159
|
// —— 0.11.0 LLM 草稿锁(创作保护 / 只读 / 只读预览)——
|
|
145
160
|
"lock.bar.active":"🔒 LLM 正在创作「{name}」· 工坊只读 · 最后写入 {n} 分钟前",
|
|
146
161
|
"lock.bar.activeHint":"创作完成后让 LLM 调用 cc_release_draft 释放草稿锁;或到设置里关闭「LLM 创作保护(草稿锁)」。",
|
|
@@ -472,6 +487,21 @@ window.__ModuleLoader__.load({
|
|
|
472
487
|
"settings.behavior.autoSave":"Auto ★ save on import",
|
|
473
488
|
"settings.behavior.autoSaveDesc":"Auto save imported card to ~/.dsh/cc-library (new ID), skip manual save.",
|
|
474
489
|
"settings.behavior.storageHint":"Key: localStorage:dsh-cc-studio-settings, shared across presets/sessions.",
|
|
490
|
+
"settings.update.title":"Plugin updates",
|
|
491
|
+
"settings.update.desc":"Check the latest version on npm. When an update exists you get a copyable package@version string for the dsh CLI.",
|
|
492
|
+
"settings.update.check":"Check for updates",
|
|
493
|
+
"settings.update.checking":"Checking…",
|
|
494
|
+
"settings.update.recheck":"Check again",
|
|
495
|
+
"settings.update.current":"Installed v{v}",
|
|
496
|
+
"settings.update.available":"v{latest} is available (installed v{current})",
|
|
497
|
+
"settings.update.upToDate":"You are on the latest version (v{current})",
|
|
498
|
+
"settings.update.localNewer":"Local v{current} is newer than npm's v{latest} (dev build)",
|
|
499
|
+
"settings.update.copy":"Copy",
|
|
500
|
+
"settings.update.copied":"Copied",
|
|
501
|
+
"settings.update.copyFailed":"Copy failed: select the text above and copy manually",
|
|
502
|
+
"settings.update.commandHint":"Run: dsh plugin --profile <profile> add {spec}",
|
|
503
|
+
"settings.update.error":"Check failed: {msg}",
|
|
504
|
+
"settings.update.unknownError":"Unknown error",
|
|
475
505
|
// —— 0.11.0 LLM draft lock (writing protection / read-only / preview) ——
|
|
476
506
|
"lock.bar.active":"🔒 LLM is writing “{name}” · workshop is read-only · last write {n} min ago",
|
|
477
507
|
"lock.bar.activeHint":"When the draft is done, ask the LLM to call cc_release_draft (or turn off “LLM draft protection (draft lock)” in Settings).",
|
|
@@ -692,6 +722,38 @@ window.__ModuleLoader__.load({
|
|
|
692
722
|
};
|
|
693
723
|
function h(type,props){ var c=Array.prototype.slice.call(arguments,2); return React.createElement.apply(React,[type,props].concat(c)); }
|
|
694
724
|
|
|
725
|
+
// 设置页「检查更新」用:把 `@xia-sc/dsh-cc-studio@x.y.z` 放进剪贴板。
|
|
726
|
+
// 优先 async clipboard API;失败退回老式 textarea + execCommand —— 桌面端 WebView 里
|
|
727
|
+
// 两种都可能被权限策略挡掉,所以两条路都留着,仍失败就交给调用方显示「复制失败」。
|
|
728
|
+
function copyText(text){
|
|
729
|
+
var s = String(text == null ? "" : text);
|
|
730
|
+
try{
|
|
731
|
+
if(typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function"){
|
|
732
|
+
return navigator.clipboard.writeText(s).catch(function(){ return legacyCopyText(s); });
|
|
733
|
+
}
|
|
734
|
+
}catch(e){}
|
|
735
|
+
return legacyCopyText(s);
|
|
736
|
+
}
|
|
737
|
+
function legacyCopyText(s){
|
|
738
|
+
return new Promise(function(resolve, reject){
|
|
739
|
+
try{
|
|
740
|
+
if(typeof document === "undefined" || !document.body){ reject(new Error("clipboard unavailable")); return; }
|
|
741
|
+
var ta = document.createElement("textarea");
|
|
742
|
+
ta.value = s;
|
|
743
|
+
ta.setAttribute("readonly", "");
|
|
744
|
+
ta.style.position = "fixed";
|
|
745
|
+
ta.style.top = "-1000px";
|
|
746
|
+
ta.style.opacity = "0";
|
|
747
|
+
document.body.appendChild(ta);
|
|
748
|
+
ta.select();
|
|
749
|
+
var done = false;
|
|
750
|
+
try{ done = !!(document.execCommand && document.execCommand("copy")); }catch(e){ done = false; }
|
|
751
|
+
document.body.removeChild(ta);
|
|
752
|
+
if(done) resolve(); else reject(new Error("copy command rejected"));
|
|
753
|
+
}catch(e){ reject(e); }
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
695
757
|
function nowSec(){ return Math.floor(Date.now()/1000); }
|
|
696
758
|
function makeDraft(){
|
|
697
759
|
return {
|
|
@@ -741,7 +803,7 @@ window.__ModuleLoader__.load({
|
|
|
741
803
|
var _saved = loadSettings();
|
|
742
804
|
// 兼容旧开关:poll=false 视为 0 秒
|
|
743
805
|
if(_saved.poll===false) _saved.pollInterval = 0;
|
|
744
|
-
var state={ draft: draft, draftStatus: null, cur:0, panelOpen:false, sideExpanded:false, isCcMode:false, currentSessionId:null, slotWarn:null, alternateSlots:null, world:{ timeline:"", factions:"", geo:"", power:"", daily:"" }, busy:null, valid:{valid:true,errors:[],warnings:[]}, lastMsg:null, library:[], libraryQuery:"", libraryCollapsed:false, libraryBusy:null, groups:[], currentGroupId:"", relations:null, relBusy:null, relIssues:null, relByCheck:null, currentLibraryId:null, book:null, bookBusy:null, bookMerge:false, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport, draftLock:_saved.draftLock, lock:null, preview:null, llmDraft:null, llmWorld:null, llmDraftStatus:null };
|
|
806
|
+
var state={ draft: draft, draftStatus: null, cur:0, panelOpen:false, sideExpanded:false, isCcMode:false, currentSessionId:null, slotWarn:null, alternateSlots:null, world:{ timeline:"", factions:"", geo:"", power:"", daily:"" }, busy:null, valid:{valid:true,errors:[],warnings:[]}, lastMsg:null, library:[], libraryQuery:"", libraryCollapsed:false, libraryBusy:null, groups:[], currentGroupId:"", relations:null, relBusy:null, relIssues:null, relByCheck:null, currentLibraryId:null, book:null, bookBusy:null, bookMerge:false, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport, draftLock:_saved.draftLock, lock:null, preview:null, llmDraft:null, llmWorld:null, llmDraftStatus:null, update:null };
|
|
745
807
|
var listeners=new Set();
|
|
746
808
|
function emit(){ listeners.forEach(function(fn){ try{fn();}catch(e){console.error(e);} }); }
|
|
747
809
|
function getSnapshot(){ return state; }
|
|
@@ -876,6 +938,43 @@ window.__ModuleLoader__.load({
|
|
|
876
938
|
if(!on) set({lastMsg: t("lock.msg.draftLockOff")});
|
|
877
939
|
saveSettings();
|
|
878
940
|
}
|
|
941
|
+
// ———————————————— 0.11.3 检查更新 ————————————————
|
|
942
|
+
// 版本号与 npm 查询都在宿主做(package.json 是唯一真源,客户端不硬编码版本)。
|
|
943
|
+
// probe = 只取本地版本(设置页一打开就显示「当前版本」),不打网络;失败静默 ——
|
|
944
|
+
// 老宿主没有 cc_checkUpdate 端点时,设置页不该因此报错。
|
|
945
|
+
async function checkUpdate(opts){
|
|
946
|
+
var probe = !!(opts && opts.probe);
|
|
947
|
+
var cur = state.update || {};
|
|
948
|
+
if(cur.status === "checking") return;
|
|
949
|
+
set({ update: { status:"checking", source:cur.source||null, current:cur.current||null, latest:cur.latest||null,
|
|
950
|
+
updateAvailable:!!cur.updateAvailable, localNewer:!!cur.localNewer, installSpec:cur.installSpec||"",
|
|
951
|
+
registry:cur.registry||null, checkedAt:cur.checkedAt||null, error:null, copied:null } });
|
|
952
|
+
try{
|
|
953
|
+
var res = await rpc("cc_checkUpdate", probe ? { network:false } : {});
|
|
954
|
+
if(!res || !res.ok){
|
|
955
|
+
if(probe){ set({ update: null }); return; }
|
|
956
|
+
var details = (res && res.error && res.error.details) || {};
|
|
957
|
+
set({ update: { status:"error", source:null, current:details.current||cur.current||null, latest:null,
|
|
958
|
+
updateAvailable:false, localNewer:false, installSpec:"", registry:details.registry||null, checkedAt:null,
|
|
959
|
+
error:(res && res.error && res.error.message) ? String(res.error.message) : t("settings.update.unknownError"), copied:null } });
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
var v = res.value || {};
|
|
963
|
+
set({ update: { status:"done", source:v.source||null, current:v.current||null, latest:v.latest||null,
|
|
964
|
+
updateAvailable:!!v.updateAvailable, localNewer:!!v.localNewer, installSpec:v.installSpec||"",
|
|
965
|
+
registry:v.registry||null, checkedAt:v.checkedAt||null, error:null, copied:null } });
|
|
966
|
+
}catch(e){
|
|
967
|
+
if(probe){ set({ update: null }); return; }
|
|
968
|
+
set({ update: { status:"error", source:null, current:cur.current||null, latest:null, updateAvailable:false,
|
|
969
|
+
localNewer:false, installSpec:"", registry:cur.registry||null, checkedAt:null,
|
|
970
|
+
error:String(e && e.message ? e.message : e), copied:null } });
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
/** 复制反馈:null = 未复制,"ok" = 已复制,"failed" = 剪贴板不可用(界面提示手动选)。 */
|
|
974
|
+
function markUpdateCopied(v){
|
|
975
|
+
if(!state.update) return;
|
|
976
|
+
set({ update: Object.assign({}, state.update, { copied: (v==null ? null : String(v)) }) });
|
|
977
|
+
}
|
|
879
978
|
function pushDraftToHost(){
|
|
880
979
|
// 第二道闸:UI 已经只读(输入框 readOnly / 按钮 disabled),这里防的是「锁在两次轮询之间
|
|
881
980
|
// 突然出现」与程序化调用 —— 锁定时把本地草稿推上去,等于替 LLM 覆盖它正在写的内容。
|
|
@@ -1794,7 +1893,7 @@ window.__ModuleLoader__.load({
|
|
|
1794
1893
|
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
1795
1894
|
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
1796
1895
|
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
1797
|
-
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, setLock:setLock, setLlmDraft:setLlmDraft, enterPreview:enterPreview, exitPreview:exitPreview, isLockActive:isLockActive, isReadOnly:isReadOnly, setDraftLock:setDraftLock, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, setCurrentLibraryId:setCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard, openRelations:openRelations, openRelationsFor:openRelationsFor, loadBook:loadBook, patchBook:patchBook, promoteBook:promoteBook, applyBook:applyBook, exportBookFile:exportBookFile, exportBookStFile:exportBookStFile, setBookMerge:setBookMerge };
|
|
1896
|
+
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, setLock:setLock, setLlmDraft:setLlmDraft, enterPreview:enterPreview, exitPreview:exitPreview, isLockActive:isLockActive, isReadOnly:isReadOnly, setDraftLock:setDraftLock, checkUpdate:checkUpdate, markUpdateCopied:markUpdateCopied, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, setCurrentLibraryId:setCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard, openRelations:openRelations, openRelationsFor:openRelationsFor, loadBook:loadBook, patchBook:patchBook, promoteBook:promoteBook, applyBook:applyBook, exportBookFile:exportBookFile, exportBookStFile:exportBookStFile, setBookMerge:setBookMerge };
|
|
1798
1897
|
}
|
|
1799
1898
|
|
|
1800
1899
|
var S={
|
|
@@ -2944,6 +3043,12 @@ window.__ModuleLoader__.load({
|
|
|
2944
3043
|
var snapRef=React.useState(function(){ return store ? store.getSnapshot() : null; });
|
|
2945
3044
|
var snap=snapRef[0], setSnap=snapRef[1];
|
|
2946
3045
|
React.useEffect(function(){ if(!store) return; return store.subscribe(function(){ setSnap(store.getSnapshot()); }); }, [store]);
|
|
3046
|
+
// 0.11.3:一打开设置页问一次本地版本(network:false,不打网络);store 里已有结果就不重复问。
|
|
3047
|
+
// 老宿主没有 cc_checkUpdate 端点时 store 会静默回 idle —— 设置页不因此报错。
|
|
3048
|
+
React.useEffect(function(){
|
|
3049
|
+
if(!store || !store.checkUpdate) return;
|
|
3050
|
+
if(!store.getSnapshot().update) store.checkUpdate({probe:true});
|
|
3051
|
+
}, [store]);
|
|
2947
3052
|
function tr(key, vars){
|
|
2948
3053
|
var s=t(key);
|
|
2949
3054
|
if(vars && typeof vars==="object"){ for(var k in vars){ s=s.split("{"+k+"}").join(String(vars[k])); } }
|
|
@@ -2965,6 +3070,53 @@ window.__ModuleLoader__.load({
|
|
|
2965
3070
|
)
|
|
2966
3071
|
);
|
|
2967
3072
|
}
|
|
3073
|
+
// 检查更新区块(0.11.3):状态文案全部走词表;可更新时给出可复制的 `包名@版本`。
|
|
3074
|
+
function renderUpdateBlock(){
|
|
3075
|
+
var u = snap ? snap.update : null;
|
|
3076
|
+
var status = (u && u.status) ? u.status : "idle";
|
|
3077
|
+
var checking = status === "checking";
|
|
3078
|
+
var label = checking ? t("settings.update.checking")
|
|
3079
|
+
: ((status === "done" || status === "error") ? t("settings.update.recheck") : t("settings.update.check"));
|
|
3080
|
+
var kids = [
|
|
3081
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:10, marginTop:10, flexWrap:"wrap"}},
|
|
3082
|
+
h("button",{style:Object.assign({}, S.btn, {padding:"6px 12px", fontSize:12, opacity: checking?0.55:1, cursor: checking?"default":"pointer"}),
|
|
3083
|
+
disabled: checking,
|
|
3084
|
+
onClick:function(){ if(!checking && store && store.checkUpdate) store.checkUpdate(); }}, label),
|
|
3085
|
+
(u && u.current) ? h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, tr("settings.update.current",{v:u.current})) : null
|
|
3086
|
+
)
|
|
3087
|
+
];
|
|
3088
|
+
if(status === "error"){
|
|
3089
|
+
kids.push(h("div",{style:{fontSize:11, color:"#e05555", marginTop:8, lineHeight:1.5, wordBreak:"break-all"}},
|
|
3090
|
+
tr("settings.update.error",{msg:(u && u.error) ? u.error : t("settings.update.unknownError")})));
|
|
3091
|
+
}
|
|
3092
|
+
if(status === "done" && u && u.latest){
|
|
3093
|
+
if(u.updateAvailable){
|
|
3094
|
+
kids.push(h("div",{style:{fontSize:12, fontWeight:600, color:"var(--dsw-alias-label-primary)", marginTop:10, lineHeight:1.5}},
|
|
3095
|
+
tr("settings.update.available",{latest:u.latest, current:u.current})));
|
|
3096
|
+
kids.push(h("div",{style:{display:"flex", alignItems:"center", gap:8, marginTop:8, flexWrap:"wrap"}},
|
|
3097
|
+
h("code",{style:{fontSize:11, padding:"5px 8px", borderRadius:6, background:"var(--dsw-alias-bg-layer-2)", border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-primary)", userSelect:"all"}}, u.installSpec),
|
|
3098
|
+
h("button",{style:Object.assign({}, S.btnGhost,{border:"1px solid var(--dsw-alias-border-l1)", padding:"4px 10px", fontSize:11}), onClick:function(){ copyUpdateSpec(); }},
|
|
3099
|
+
u.copied === "ok" ? t("settings.update.copied") : t("settings.update.copy"))
|
|
3100
|
+
));
|
|
3101
|
+
if(u.copied === "failed") kids.push(h("div",{style:{fontSize:11, color:"#e05555", marginTop:6, lineHeight:1.5}}, t("settings.update.copyFailed")));
|
|
3102
|
+
kids.push(h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:6, lineHeight:1.5}},
|
|
3103
|
+
tr("settings.update.commandHint",{spec:u.installSpec})));
|
|
3104
|
+
} else {
|
|
3105
|
+
kids.push(h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", marginTop:10, lineHeight:1.5}},
|
|
3106
|
+
u.localNewer ? tr("settings.update.localNewer",{current:u.current, latest:u.latest}) : tr("settings.update.upToDate",{current:u.current})));
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
return kids;
|
|
3110
|
+
}
|
|
3111
|
+
function copyUpdateSpec(){
|
|
3112
|
+
var u = store ? store.getSnapshot().update : null;
|
|
3113
|
+
if(!u || !u.installSpec) return;
|
|
3114
|
+
copyText(u.installSpec).then(function(){
|
|
3115
|
+
store.markUpdateCopied("ok");
|
|
3116
|
+
// 2s 后把「已复制」退回「复制」,免得用户以为状态卡住了
|
|
3117
|
+
try{ setTimeout(function(){ if(store && store.markUpdateCopied) store.markUpdateCopied(null); }, 2000); }catch(e){}
|
|
3118
|
+
}).catch(function(){ store.markUpdateCopied("failed"); });
|
|
3119
|
+
}
|
|
2968
3120
|
return h("div",{style:{padding:"16px 20px", maxWidth:720}},
|
|
2969
3121
|
h("h2",{style:{margin:"0 0 8px", fontSize:18}}, "🎭 "+t("settings.title")),
|
|
2970
3122
|
h("div",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:13, lineHeight:1.6}}, t("settings.desc")),
|
|
@@ -3005,6 +3157,12 @@ window.__ModuleLoader__.load({
|
|
|
3005
3157
|
ToggleRow(t("settings.behavior.draftLock"), t("settings.behavior.draftLockDesc"), draftLock, function(v){ store.setDraftLock(v); }),
|
|
3006
3158
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:8}}, t("settings.behavior.storageHint"))
|
|
3007
3159
|
),
|
|
3160
|
+
// 0.11.3:检查更新(宿主查 npm latest → 可复制 `包名@版本` 去 dsh 更新)
|
|
3161
|
+
h("div",{style:{marginTop:12, background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:12, padding:12}},
|
|
3162
|
+
h("h3",{style:{margin:"0 0 8px", fontSize:13}}, t("settings.update.title")),
|
|
3163
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", lineHeight:1.5}}, t("settings.update.desc")),
|
|
3164
|
+
renderUpdateBlock()
|
|
3165
|
+
),
|
|
3008
3166
|
h("div",{style:{marginTop:12, background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:12, padding:12}},
|
|
3009
3167
|
h("h3",{style:{margin:"0 0 8px", fontSize:13}}, t("settings.tips.title")),
|
|
3010
3168
|
h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.6}}, t("settings.tips.body"))
|
package/lib/index.js
CHANGED
|
@@ -1554,9 +1554,82 @@ function presetSourceRoot(){
|
|
|
1554
1554
|
}
|
|
1555
1555
|
function presetTargetRoot(){ return join(dshHomeDir(), ".agent-presets", PRESET_ID); }
|
|
1556
1556
|
function sha256(text){ return createHash("sha256").update(String(text), "utf8").digest("hex"); }
|
|
1557
|
-
function
|
|
1558
|
-
try{
|
|
1559
|
-
|
|
1557
|
+
function pluginMeta(){
|
|
1558
|
+
try{
|
|
1559
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1560
|
+
return {
|
|
1561
|
+
name: (typeof pkg.name === "string" && pkg.name) ? pkg.name : PACKAGE_NAME_FALLBACK,
|
|
1562
|
+
version: (typeof pkg.version === "string" && pkg.version) ? pkg.version : "unknown"
|
|
1563
|
+
};
|
|
1564
|
+
}catch{ return { name: PACKAGE_NAME_FALLBACK, version: "unknown" }; }
|
|
1565
|
+
}
|
|
1566
|
+
function pluginVersion(){ return pluginMeta().version; }
|
|
1567
|
+
|
|
1568
|
+
// ———————————————— 检查更新(0.11.3) ————————————————
|
|
1569
|
+
// 设置页「角色卡工坊」底部的「检查更新」按钮:宿主去 npm registry 取 latest,和本机
|
|
1570
|
+
// package.json 的 version 比一下。**浏览器不直连 registry**(省掉 CORS 与代理差异),
|
|
1571
|
+
// 只走本插件自己的 RPC 通道 —— 与其它端点同一条鉴权围栏。
|
|
1572
|
+
// 只查 `/latest`(一个版本对象,几百字节);不拉整份 packument(几十个版本的全量元数据)。
|
|
1573
|
+
const PACKAGE_NAME_FALLBACK = "@xia-sc/dsh-cc-studio";
|
|
1574
|
+
const NPM_LATEST_URL = "https://registry.npmjs.org/@xia-sc%2Fdsh-cc-studio/latest";
|
|
1575
|
+
const UPDATE_CHECK_TIMEOUT_MS = 8000;
|
|
1576
|
+
const UPDATE_CHECK_TIMEOUT_MAX = 20000;
|
|
1577
|
+
/** 够用的 SemVer 解析:数字三段 + 可选预发布段。解析不了返回 null(不当成「有更新」)。 */
|
|
1578
|
+
function parseSemver(v){
|
|
1579
|
+
const raw = String(v == null ? "" : v).trim().replace(/^v/i, "");
|
|
1580
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(raw);
|
|
1581
|
+
if(!m) return null;
|
|
1582
|
+
return { numbers: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ? m[4].split(".") : [] };
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* SemVer 优先级比较(够用版):返回 -1 / 0 / 1。
|
|
1586
|
+
* 只用于回答「npm 上的 latest 是不是比本机新」,所以按 SemVer 第 11 条的常见规则来:
|
|
1587
|
+
* 数字段按数值比;有预发布段的**小于**同版本号无预发布段的(0.12.0-rc.1 < 0.12.0);
|
|
1588
|
+
* 预发布标识符里数字段的按数值、其余按 ASCII 字典序,前缀相同则短的更小。
|
|
1589
|
+
* 任一侧解析不了 → 0:宁可「看起来已是最新」,也不要因为脏数据把「有更新」误报成常态。
|
|
1590
|
+
*/
|
|
1591
|
+
function compareVersions(a, b){
|
|
1592
|
+
const x = parseSemver(a), y = parseSemver(b);
|
|
1593
|
+
if(!x || !y) return 0;
|
|
1594
|
+
for(let i = 0; i < 3; i++){
|
|
1595
|
+
if(x.numbers[i] !== y.numbers[i]) return x.numbers[i] > y.numbers[i] ? 1 : -1;
|
|
1596
|
+
}
|
|
1597
|
+
if(!x.pre.length && !y.pre.length) return 0;
|
|
1598
|
+
if(!x.pre.length) return 1; // 正式版 > 预发布版
|
|
1599
|
+
if(!y.pre.length) return -1;
|
|
1600
|
+
const n = Math.max(x.pre.length, y.pre.length);
|
|
1601
|
+
for(let i = 0; i < n; i++){
|
|
1602
|
+
const xa = x.pre[i], yb = y.pre[i];
|
|
1603
|
+
if(xa === undefined) return -1;
|
|
1604
|
+
if(yb === undefined) return 1;
|
|
1605
|
+
const na = /^\d+$/.test(xa), nb = /^\d+$/.test(yb);
|
|
1606
|
+
if(na && nb){ if(Number(xa) !== Number(yb)) return Number(xa) > Number(yb) ? 1 : -1; continue; }
|
|
1607
|
+
if(na !== nb) return na ? -1 : 1; // 数字标识符总小于字母数字标识符
|
|
1608
|
+
if(xa !== yb) return xa > yb ? 1 : -1; // ASCII 字典序
|
|
1609
|
+
}
|
|
1610
|
+
return 0;
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* 取 npm 上这个包的 latest 版本。带超时(默认 8s)—— 断网/被墙时不能把一次点击变成永久挂起。
|
|
1614
|
+
* 抛出的错误统一由端点包成 fail("check-update-failed"),客户端原样显示 message。
|
|
1615
|
+
*/
|
|
1616
|
+
async function fetchLatestFromNpm(timeoutMs){
|
|
1617
|
+
if(typeof fetch !== "function") throw new Error("当前运行时没有全局 fetch(需要 Node >= 22)");
|
|
1618
|
+
const ms = Math.max(1000, Math.min(UPDATE_CHECK_TIMEOUT_MAX, Number(timeoutMs) || UPDATE_CHECK_TIMEOUT_MS));
|
|
1619
|
+
const meta = pluginMeta();
|
|
1620
|
+
const ctrl = new AbortController();
|
|
1621
|
+
const timer = setTimeout(function(){ try{ ctrl.abort(); }catch{} }, ms);
|
|
1622
|
+
try{
|
|
1623
|
+
const res = await fetch(NPM_LATEST_URL, {
|
|
1624
|
+
headers: { accept: "application/json", "user-agent": meta.name + "/" + meta.version + " (dsh-cc-studio update-check)" },
|
|
1625
|
+
signal: ctrl.signal
|
|
1626
|
+
});
|
|
1627
|
+
if(!res || !res.ok) throw new Error("npm registry 返回 HTTP " + (res ? res.status : "?"));
|
|
1628
|
+
const j = await res.json();
|
|
1629
|
+
const v = (j && typeof j.version === "string") ? j.version.trim() : "";
|
|
1630
|
+
if(!parseSemver(v)) throw new Error("npm registry 返回的版本号无法解析:" + JSON.stringify(j && j.version));
|
|
1631
|
+
return { version: v, distTag: "latest" };
|
|
1632
|
+
}finally{ clearTimeout(timer); }
|
|
1560
1633
|
}
|
|
1561
1634
|
/**
|
|
1562
1635
|
* 纯决策函数(便于单测):给定目标现状、模板、上次安装记录,决定每个受管文件该写还是该跳过。
|
|
@@ -2286,6 +2359,33 @@ function apply(ctx, config){
|
|
|
2286
2359
|
return ok({ b64: Buffer.from(zipBytes).toString("base64"), filename: (draft.data.name||"card")+".charx", draft });
|
|
2287
2360
|
}catch(e){ return fail("build-failed", String(e.message)); }
|
|
2288
2361
|
}
|
|
2362
|
+
// 检查更新(0.11.3):设置页按钮 → 查 npm 上的 latest。
|
|
2363
|
+
// args.network === false = 只回本地版本(设置页一打开就要显示「当前版本」,
|
|
2364
|
+
// 不该为了这一行字去打一次网络);args.timeoutMs 可调,但会被夹到 [1s, 20s]。
|
|
2365
|
+
case "cc_checkUpdate":{
|
|
2366
|
+
const meta = pluginMeta();
|
|
2367
|
+
const current = meta.version;
|
|
2368
|
+
const base = { package: meta.name, current, registry: NPM_LATEST_URL };
|
|
2369
|
+
if(args.network === false){
|
|
2370
|
+
return ok({ ...base, latest: null, updateAvailable: false, localNewer: false, installSpec: meta.name + "@" + current, checkedAt: null, source: "local" });
|
|
2371
|
+
}
|
|
2372
|
+
try{
|
|
2373
|
+
const info = await fetchLatestFromNpm(args.timeoutMs);
|
|
2374
|
+
const cmp = compareVersions(info.version, current);
|
|
2375
|
+
return ok({
|
|
2376
|
+
...base,
|
|
2377
|
+
latest: info.version,
|
|
2378
|
+
// latest 比本机大 → 可更新;比本机小 → 本机是更早/未发布的开发版本
|
|
2379
|
+
updateAvailable: cmp > 0,
|
|
2380
|
+
localNewer: cmp < 0,
|
|
2381
|
+
installSpec: meta.name + "@" + info.version,
|
|
2382
|
+
checkedAt: nowSec(),
|
|
2383
|
+
source: "registry"
|
|
2384
|
+
});
|
|
2385
|
+
}catch(e){
|
|
2386
|
+
return fail("check-update-failed", String(e && e.message ? e.message : e), base);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2289
2389
|
default:
|
|
2290
2390
|
return fail("unknown-endpoint", `unknown endpoint ${JSON.stringify(endpoint)}`);
|
|
2291
2391
|
}
|
|
@@ -2558,6 +2658,8 @@ export { apply, inject, name, planPresetInstall, installCcPreset, supportsDeclar
|
|
|
2558
2658
|
stV2Projection, stripDecorators, withStWorld, pngCardChunks, buildTEXtChunk, buildPngWithCard, embedCardIntoPngBytes, extractCardFromPngBytes,
|
|
2559
2659
|
// 0.9.1 B2:V2/V1 卡升级成 CCv3(PNG/CHARX/JSON 三条导入路径共用这一份实现)
|
|
2560
2660
|
upgradeCardToV3, normTags,
|
|
2661
|
+
// 0.11.3 检查更新:版本比较与 npm 查询是纯/单点逻辑,供回归测试直接断言
|
|
2662
|
+
pluginMeta, parseSemver, compareVersions, fetchLatestFromNpm, NPM_LATEST_URL,
|
|
2561
2663
|
REL_TYPES, REL_TYPE_LABELS, REL_BEGIN, REL_END };
|
|
2562
2664
|
|
|
2563
2665
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xia-sc/dsh-cc-studio",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"description": "CCv3 角色卡工坊:融合工坊 + CC 模式 + JSON/PNG/CHARX + 中英双语(跟随全局)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"presets"
|
|
39
39
|
],
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node tests/rpc-channel.test.mjs && node tests/preset-install.test.mjs && node tests/cc-detection.test.mjs && node tests/client-greetings.test.mjs && node tests/draft-slot-sync.test.mjs && node tests/draft-lock-client.test.mjs && node tests/nav-highlight.test.mjs && node tests/clear-card.test.mjs && node tests/export-entry.test.mjs && node tests/close-button.test.mjs && node tests/tools-contract.test.mjs && node tests/draft-lock.test.mjs && node tests/relations.test.mjs && node tests/relations-view.test.mjs && node tests/shared-book.test.mjs && node tests/shared-book-st-export.test.mjs && node tests/st-compat.test.mjs && node tests/promote-draft-sync.test.mjs && node tests/group-scope.test.mjs && node tests/group-metrics.test.mjs && node tests/group-checks.test.mjs && node tests/group-consistency.test.mjs && node tests/group-layer-guard.test.mjs && node tests/data-root.test.mjs",
|
|
41
|
+
"test": "node tests/rpc-channel.test.mjs && node tests/preset-install.test.mjs && node tests/cc-detection.test.mjs && node tests/client-greetings.test.mjs && node tests/draft-slot-sync.test.mjs && node tests/draft-lock-client.test.mjs && node tests/nav-highlight.test.mjs && node tests/clear-card.test.mjs && node tests/export-entry.test.mjs && node tests/close-button.test.mjs && node tests/tools-contract.test.mjs && node tests/draft-lock.test.mjs && node tests/relations.test.mjs && node tests/relations-view.test.mjs && node tests/shared-book.test.mjs && node tests/shared-book-st-export.test.mjs && node tests/st-compat.test.mjs && node tests/promote-draft-sync.test.mjs && node tests/group-scope.test.mjs && node tests/group-metrics.test.mjs && node tests/group-checks.test.mjs && node tests/group-consistency.test.mjs && node tests/group-layer-guard.test.mjs && node tests/data-root.test.mjs && node tests/check-update.test.mjs",
|
|
42
42
|
"prepublishOnly": "npm test"
|
|
43
43
|
},
|
|
44
44
|
"dsh": {
|
|
@@ -66,7 +66,9 @@
|
|
|
66
66
|
"0.1.7-alpha.1": "compatible",
|
|
67
67
|
"0.1.7-rc.1": "compatible",
|
|
68
68
|
"0.2.0-rc.2": "compatible",
|
|
69
|
-
"0.2.1-rc.2": "compatible"
|
|
69
|
+
"0.2.1-rc.2": "compatible",
|
|
70
|
+
"0.2.1-rc.3": "compatible",
|
|
71
|
+
"0.2.1-rc.5": "compatible"
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
},
|