dsh-composer-ux 0.2.0 → 0.3.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/CHANGELOG.md +32 -0
- package/README.md +16 -3
- package/lib/client.js +621 -151
- package/lib/client.js.map +4 -4
- package/lib/index.js +321 -6
- package/lib/index.js.map +4 -4
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.3.0] — 2026-09-15
|
|
6
|
+
|
|
7
|
+
> 尚未发布:先在用户机器上实测,通过后再发 GitHub / npm。
|
|
8
|
+
|
|
9
|
+
### 变更(含一处破坏性调整)
|
|
10
|
+
|
|
11
|
+
- **快捷指令的存储搬出设置文档**,改存 `$DSH_HOME/quick-prompts.json`。三个原因:设置文档对**数组**是整份替换、且受 schema 长度上限约束(旧 4000 字会**静默截断**长提示词,而长提示词恰恰是常态);快捷指令是「用户内容」而不是「插件配置」,单独一个文件便于备份、搬迁与手工编辑;插件被禁用或卸载也不牵连它。
|
|
12
|
+
- **自动迁移**:首次读取时把设置文档里 0.2.x 的那份平铺列表搬成「默认」分类。**旧值保留、不清空**——万一回滚到 0.2.x,用户还能看到自己那几条。
|
|
13
|
+
- 单条上限 4000 字 → **20 万字**(只做防呆,不再截断正常提示词)。送去「优化」的原文仍限 8000 字:那是给模型的输入,不能跟着涨(原先它由 `QUICK_TEXT_MAX * 2` 推导,现在独立成 `OPTIMIZE_TEXT_MAX`,**值不变**)。
|
|
14
|
+
- **分类两级结构**:`{ version: 2, categories: [{ id, name, prompts: [...] }] }`。面板顶部是分类标签(点它切换、`+` 新增),分类的改名 / 排序 / 删除与条目的增删改移都在设置页。结构参考 [lnyuqian/dsh-quick-prompts](https://github.com/lnyuqian/dsh-quick-prompts),文件字段名与它**逐字对齐**(`categories` / `name` / `title` / `text` / `autoSend` / `order`),于是两边读得懂同一个文件;代价是**不能同时装两个插件**——一个文件两个写者会互相覆盖,而对方是非原子写。
|
|
15
|
+
- **「默认插入」跨分类生效**:勾选按条目 id 找,不再只看当前分类;发送时附加的那份列表由 `alwaysQuickPrompts(book)` 跨所有分类聚合。
|
|
16
|
+
- 新增宿主路由 `GET/POST /composer-ux/prompts`(读整本 / 整本写回),沿用原有的 `webServer.register({ kind: 'exact' })` 接缝。
|
|
17
|
+
|
|
18
|
+
### 存储层为什么比参考实现更严
|
|
19
|
+
|
|
20
|
+
参考实现的存储有两处真实的丢数据路径,本插件都避开了:
|
|
21
|
+
|
|
22
|
+
1. 它用 `writeFile(file, JSON.stringify(...))` **直接覆盖**——中途失败会留下截断的 JSON。本插件用「临时文件 → `handle.sync()` → `rename`」原子替换,并在 `<file>.lock` 里串行化写入(`wx` 创建;超过 30 秒的遗留锁**只报错、不抢占**——锁的新旧无法区分「崩溃的所有者」与「只是被暂停的写者」,处置权交给操作者)。
|
|
23
|
+
2. 它读盘失败时 `catch { return [] }`——UI 显示空列表,用户一保存就把**空列表写回去**,真数据被覆盖。本插件解析失败先**改名隔离**(`quick-prompts.json.bad-<时间戳>`)再如实报错,**绝不返回空本、绝不写入**。
|
|
24
|
+
|
|
25
|
+
### 开发中发现并修掉的问题
|
|
26
|
+
|
|
27
|
+
1. **迁移判断在锁外,并发首个请求会互相覆盖。** `ensureQuickBook` 原本「读到文件不存在就就地初始化」:多个并发请求同时看到缺失时,后写的那个会把先写的用户数据覆盖成默认本。✅ 正解:在写锁内**复核一次**再初始化。
|
|
28
|
+
2. **我写的一条护栏是「永不报警的死护栏」。** 检查「读不懂时不许返回空本」的正则精确匹配了代码形状,但我多写了一个收尾括号,于是它永远不命中任何代码。✅ 正解:改成语义写法(`catch` 之后 200 字内不许出现 `kind: 'ok'`)。**这个问题是靠变异测试发现的**——把 bug 放回去时那条护栏没有变红。
|
|
29
|
+
3. **越界换位凭空造新对象。** `withPromptMoved` 走到 `mapCategory` 才判边界,越界时仍返回内容相同的新对象,会引发一次无意义的写盘。✅ 正解:先算边界,越界直接返回传入的那个对象。
|
|
30
|
+
|
|
31
|
+
### 测试
|
|
32
|
+
|
|
33
|
+
- 新增 `test/quick-store.mjs`(77 条)覆盖:原子写(不留 `.tmp`)、坏文件隔离且内容原样保留、认不出的结构不写入、空文件当损坏、v1 平铺与裸数组升级、**5000 字提示词不再被截断**、并发四写不互相截断且最终是一个完整本、遗留锁报错且不删不抢占、字段映射往返、客户端编辑函数的不可变性与跨分类聚合。
|
|
34
|
+
- 全量 **239 条,0 失败**。
|
|
35
|
+
- 对三处关键护栏做了**变异测试**(把 bug 放回去看会不会红):读不懂当空本 / 文件字段名改回内部名 / `always` 聚合退化成只看第一个分类——均成功变红。
|
|
36
|
+
|
|
5
37
|
## [0.2.0] — 2026-09-14
|
|
6
38
|
|
|
7
39
|
> 已在用户机器上实测(并据实测修掉 5 处问题,见下「开发中修掉的问题」),随后发布到 GitHub 与 npm。
|
package/README.md
CHANGED
|
@@ -77,10 +77,21 @@ dsh plugin --profile web add <你克隆或解压出来的目录>
|
|
|
77
77
|
|
|
78
78
|
输入框工具行里、「展开」按钮的左侧有一个同款胶囊按钮「快捷指令」(槽位 `conversation.input.right`,order 89 < 官方「展开」的 90)。点开展开面板:
|
|
79
79
|
|
|
80
|
-
-
|
|
81
|
-
- **「默认插入」**:条目右侧的勾选框。勾上后,在你**点发送时**(Enter
|
|
80
|
+
- **快捷指令清单(分类两级结构)**:点条目把内容插入输入框(原有内容保留、另起一行)。面板顶部是分类标签(点它切换,`+` 新增一个分类);分类的改名 / 排序 / 删除,以及条目的增删改、上下移、恢复内置 9 条,都在 设置 → 输入体验 → 快捷指令清单 里。
|
|
81
|
+
- **「默认插入」**:条目右侧的勾选框。勾上后,在你**点发送时**(Enter 或官方发送按钮)勾选的提示词会自动拼到消息**末尾**一起发出,输入框里不提前显示;多条按列表顺序拼接、条目间空一行。**跨分类生效**(勾选按条目 id 找,不限于当前分类)。原文为空时不附加(交还官方原语义)。
|
|
82
82
|
- **优化提示词**:把输入框里的话交给**另一个 AI** 整理成一条能直接发给工作 AI 的清晰指令,结果**直接写回输入框**(Ctrl+Z 可还原)。三档强度:普通 / 高级(默认)/ 极端。
|
|
83
83
|
|
|
84
|
+
### 数据存在哪
|
|
85
|
+
|
|
86
|
+
快捷指令(分类 + 条目 + 「默认插入」标记)存在 **`$DSH_HOME/quick-prompts.json`**(默认 `~/.dsh/quick-prompts.json`):
|
|
87
|
+
|
|
88
|
+
- **与会话、项目无关**:换仓库、换会话都在;可以直接备份、搬迁、手工编辑(改完在设置页点「重新读取」)。
|
|
89
|
+
- 宿主半用「临时文件 → `fsync` → `rename`」**原子替换**,并用 `<file>.lock` 串行化写入;写到一半断电不会留下半截 JSON,并发保存也不会互相截断。
|
|
90
|
+
- 文件读不动(不是合法 JSON / 结构认不出 / 空文件)时,**先把坏文件改名隔离**成 `quick-prompts.json.bad-<时间戳>` 再如实报错,**绝不静默返回空列表**。这一条是刻意的:若回退成「读不懂就当空列表」,用户下一次保存就会把空列表写回去,**真数据被覆盖**。
|
|
91
|
+
- 文件字段名与 [lnyuqian/dsh-quick-prompts](https://github.com/lnyuqian/dsh-quick-prompts) 对齐(`categories` / `name` / `title` / `text` / `autoSend` / `order`),同一个文件两边都读得懂。⚠️ 但**不要同时装两个插件**——同一个文件两个写者会互相覆盖(对方还是非原子写)。
|
|
92
|
+
- 0.2.x 存在设置文档里的那份旧列表会在**首次读取时自动迁移**成「默认」分类;旧值**保留在设置文档里不清空**,万一回滚到 0.2.x 还能看到自己那几条。
|
|
93
|
+
- 单条提示词上限从 4000 字提到 **20 万字**(只做防呆,不再截断正常提示词);送去「优化」的原文仍限 8000 字(那是给模型的输入,不能跟着涨)。
|
|
94
|
+
|
|
84
95
|
### 为什么优化要走宿主的 HTTP 接口
|
|
85
96
|
|
|
86
97
|
出网请求由宿主的模型适配器发出,浏览器侧碰不到模型路由;宿主半与客户端半之间也没有别的受支持通道。所以「优化提示词」是一次往返:浏览器 `POST /composer-ux/optimize` → 宿主用 `ctx.get('llm').stream(...)` 独立跑一次模型调用 → 把优化后的正文回给浏览器填进输入框。这条路径与 [WestFox-AwA/dsh-prompt-optimizer](https://github.com/WestFox-AwA/dsh-prompt-optimizer) 同构,系统提示词也逐字提取自它(BSD-3-Clause,作者「啃轮胎的西狐」),落在 `src/optimizer-prompt.ts` 并保留署名。
|
|
@@ -173,7 +184,9 @@ pnpm dsh web --patch <本仓库的绝对路径>/cordis.dev.patch.yml
|
|
|
173
184
|
|
|
174
185
|
## 设置持久化
|
|
175
186
|
|
|
176
|
-
|
|
187
|
+
除快捷指令之外的设置存在 Host 用户设置文档(默认 `$DSH_HOME/settings.yaml`)的 `composer-ux` 分区,随工作区/机器生效;删除该分区即恢复这些设置的默认值。
|
|
188
|
+
|
|
189
|
+
快捷指令(分类 + 条目 + 「默认插入」)**不在**设置文档里,它存 `$DSH_HOME/quick-prompts.json`(见上「数据存在哪」)。设置文档里那份 `quickPrompts` 只是 0.2.x 的**迁移种子**,故意保留不清空。
|
|
177
190
|
|
|
178
191
|
## 发行包与「装前体检」
|
|
179
192
|
|