dsh-composer-ux 0.27.0 → 0.28.1

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 CHANGED
@@ -2,6 +2,65 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.28.1] — 2026-10-29(兼容「往这一组里放按钮」的其它插件)
6
+
7
+ 用户 2026-10-29(截图圈住侧边栏那一行图标):「如果有其他插件在这里放按钮会怎么样,需要兼容所有往这里放按钮的插件」。
8
+
9
+ **以前会怎样(两个真问题)**
10
+
11
+ 1. **收成一条时把整只 `<nav>` 藏了**(`display:none`)—— 别的插件往这一组里塞的按钮会**跟着消失**。
12
+ 用户看不到自己的按钮,只会觉得"装了新插件没反应"。
13
+ 2. **只留图标时的选择器写的是 `nav > button`** —— 别人用包装层(div 包按钮)注入的节点压缩不到,
14
+ 而别人**裸按钮**的文字又会被 `font-size:0` 一起压掉(不是我们该管的东西)。
15
+
16
+ **现在怎么做**
17
+
18
+ - 只处理**认得出的面板行**:`nav` 的直接子节点里,**button + 非空 aria-label + 内含 aria-hidden 图标**。
19
+ 宿主自己的 `PanelRow` 与「更多」都长这样;**别的插件通过官方 panel 注册表注册的面板**由宿主渲染成
20
+ 同样的结构,所以天然跟着一起折叠 —— 它们本来就是这组里的入口。
21
+ - 其余节点(别的插件直接注入的按钮、包装层、分隔线……)**一个字都不改**:不加标记、不改宽度/字号/文字;
22
+ 收成一条时它们**原地留着、照样能点**(这时整只 nav 不隐藏,只藏面板行)。
23
+ - 关掉折叠时把标记与行内样式**全部还原**。
24
+
25
+ **测试**:`test/sidebar-nav.mjs` 新增第 6 节(9 条:分类、bar 模式保留别人的节点、icons 模式不碰别人的节点、
26
+ 关掉后摘干净);变异新增 SX / SY 两条(把别人的节点当面板行、有别人的节点仍整体隐藏)。
27
+
28
+ ## [0.28.0] — 2026-10-28(删掉四栏:微信通知 / 工作区目录选择 / 右键菜单 / OpenCode 请求头)
29
+
30
+ 用户 2026-10-28:「我的插件去掉微信通知,去掉工作区目录选择,去掉右键菜单,去掉 open code 请求头」。
31
+ 追问两次定下:**彻底删掉(不是藏起来)**,并且**清掉他机器上的旧配置、清之前先把凭据备份成 JSON**。
32
+
33
+ **破坏性变更(0.27.0 → 0.28.0)**
34
+
35
+ 这四栏从插件里整体移除:设置页卡片、宿主 schema 字段、净化与默认值、路由、宿主半逻辑、
36
+ 独立模块、测试套件、变异用例、文档章节全部删掉,不是"隐藏入口"。
37
+
38
+ | 删掉的东西 | 连带删掉的代码 |
39
+ |---|---|
40
+ | 微信通知 | `notify.ts` / `notify-send.ts` / `notify-hooks.ts` / `notify-route.ts` / `notify-events.ts`、`/composer-ux/notify/test` 路由、四个事件钩子 |
41
+ | 工作区目录选择 | `client/directory-flow.ts` / `client/DirectoryFlowEntry.tsx`、`directoryPickEnabled` 字段 |
42
+ | 右键菜单 | `client/ContextMenuHost.tsx`、`interceptors.ts` 里的 contextmenu 分支与 `runMenuAction`、七个条目开关与三档 `menuMode` |
43
+ | OpenCode 请求头 | `opencode-header.ts`、`migrations.ts`(唯一一条迁移就是它)、`internal/config` 运行期覆盖、`header*` 七个字段 |
44
+
45
+ **升级后会怎样**
46
+
47
+ - **右键菜单**:回到 DSH / 浏览器原生行为(本插件不再拦 `contextmenu`)。之前选「自定义菜单」的人
48
+ 会立刻感觉到差别 —— 这是删这一栏的必然结果,不是 bug。
49
+ - **OpenCode 路由**:不受影响。宿主自 pi-ai 1.0.2 起就自带 `opencode-headers.js`,按**每段对话的真实
50
+ session id** 发 `x-opencode-session`,比本插件那个固定 UUID 更准(0.25.0 起本插件本来就只在旧宿主上才注入)。
51
+ - **旧配置**:profile 里残留的 `notify*` / `menu*` / `header*` / `directoryPickEnabled` 键会被静默忽略
52
+ (schemastery 对未声明键原样放行)。这次顺手清掉了本机 9 个 profile 里的死键,清之前备份在
53
+ `~/.dsh/dsh-composer-ux-遗留配置备份-<日期>.json`。
54
+
55
+ **代码体量**
56
+
57
+ `src/` 少 9 个文件、约 1300 行(`host.ts` 净减 456 行);测试套件 35 → **29**,
58
+ 用例 2889 → **2603**;变异用例 233 → **87**(删掉的全是指向已删代码的那些)。
59
+
60
+ **顺手修的**
61
+
62
+ - 客户端入口的注释与「恢复默认」的字段清单同步瘦身(`MENU_FIELDS` 一类死引用清零)。
63
+
5
64
  ## [0.27.0] — 2026-10-28(左侧「全局面板」默认折叠:收成一条 / 只留图标,设置里选)
6
65
 
7
66
  用户 2026-10-28(截图圈住侧边栏那 5 个入口):「给我的插件添加一个打开后默认折叠这个区域的功能」,
package/README.en.md CHANGED
@@ -17,11 +17,10 @@ opt-in through two layers of switches, and nothing here touches your conversatio
17
17
  item it cannot trace. Results land in a **result box** (0.12.0): items stream in one by one as they pass
18
18
  verification, the finished prompt is **editable**, and an explicit **“Insert into input”** button decides
19
19
  when — and whether — it replaces what you typed.
20
- 5. **`x-opencode-session` header injection** so OpenCode (Go) routes work inside DSH.
21
- 6. **Default terminal switch (Windows)** — replaces the model's PowerShell tool with **Git Bash**.
22
- 7. **Official persistent terminal tools** (`terminal_open/read/send/signal/close/list`).
23
- 8. **Stats line upgrade** — renders DSH's built-in cache-hit percentage with **three decimals**.
24
- 9. **Cost capsule** — per-session cost from DeepSeek's list price, with per-model peak/off-peak prices you can
20
+ 5. **Default terminal switch (Windows)** — replaces the model's PowerShell tool with **Git Bash**.
21
+ 6. **Official persistent terminal tools** (`terminal_open/read/send/signal/close/list`).
22
+ 7. **Stats line upgrade** — renders DSH's built-in cache-hit percentage with **three decimals**.
23
+ 8. **Cost capsule** — per-session cost from DeepSeek's list price, with per-model peak/off-peak prices you can
25
24
  override, **Chinese public holidays fetched automatically**, an account-balance reader, and one-click price
26
25
  sync (official page + models.dev).
27
26
  10. **One-click “restart DSH”** in the settings-card header.
@@ -149,13 +148,9 @@ edited in settings; the JSON output contract is always appended and cannot be ov
149
148
 
150
149
  ### Everything else
151
150
 
152
- - **Context menu**: three modes — official (do not interfere), browser default, or the plugin's own menu with
153
- seven items; clipboard read permission is explained in the card.
154
151
  - **Quick commands**: categories, cross-category moves, per-item insert mode, and a JSON file you can back up
155
152
  or edit by hand (`$DSH_HOME/quick-prompts.json`, atomic writes with a lock file; a corrupt file is quarantined
156
153
  rather than silently replaced by an empty list).
157
- - **OpenCode header**: writes `providers.<route>.headers` in the `llm-pi-ai` profile — the only supported
158
- outbound-header entry point in DSH — so it takes effect on the next request without a restart.
159
154
  - **Default terminal (Windows)**: git-anchored discovery, WSL excluded, per-session tool-surface trim, with the
160
155
  official sandbox/approval/timeout semantics preserved.
161
156
  - **Restart DSH**: two-step confirmation, detached helper process, waits for the port to be released, hidden
package/README.md CHANGED
@@ -26,23 +26,20 @@ DeepSeek Harness Web 输入体验增强插件:
26
26
 
27
27
  ![设置页「输入体验」:顶部总开关 + 六张折叠卡,每张卡的标题行右端各有一个卡级开关](https://raw.githubusercontent.com/fangwen9527/dsh-composer-ux/v0.5.0/docs/settings-panel-0.5.0.png)
28
28
 
29
- > 上图即 0.5.0 的设置页(六栏都开着)。注意标题行**只有卡级开关与从属控件** —— 右键菜单那 7 个条目开关已按实测反馈搬进卡内的「自定义」档(见第 0 条)。
29
+ > 上图是 0.5.0 的设置页(当时还有右键菜单与 OpenCode 请求头两栏,0.28.0 已删除)。
30
30
 
31
- 0. **两层开关:总开关 + 每栏开关**。设置页顶部「启用输入增强」是**总开关(默认开)**,它是一道总闸;八张折叠卡里**前七张**的标题行右端各有一个**卡级开关**,决定「这一栏要不要生效」(第 8 张「金额」没有开关 —— 金额胶囊常驻显示)。两处都满足才生效(`enabled && 该栏开关`)。前六栏默认**关**;第 7 栏「统计行」默认**开**(它是唯一例外,理由见下条)。
31
+ 0. **两层开关:总开关 + 每栏开关**。设置页顶部「启用输入增强」是**总开关(默认开)**,它是一道总闸;折叠卡的标题行右端是**卡级开关**,决定「这一栏要不要生效」(「金额」与「自检」没有开关 —— 一个是常驻胶囊,一个是纯只读检查)。两处都满足才生效(`enabled && 该栏开关`)。0.28.0 起只剩 **6 张卡**:键位 / 快捷指令 / 设置面板 / 默认终端 / 统计行 / 金额,外加自检与自启动。
32
32
  - **兼容性**:同一个产物同时支持 **DSH 0.1.6 与 0.1.7**。两代的设置服务形状不同(`settingsScope.bind` → `configForms.get`、宿主 `settings.register` 被删除、事件名换代),代码按**能力探测**分支,不维护两份产物。
33
33
  - ⚠️ **升级 DSH 到 0.1.7 之前先备份 `~/.dsh/settings.yaml`**:0.1.7 的官方升级会把它改名成 `settings.yaml.imported` 并逐节导入 profile patch(一次性、不可逆),而本插件这一段在旧版上**导不进去**(那时还没有 Config 字段表),旧值只会留在那个 `.imported` 文件里。
34
- - **老用户不会被升级弄坏**:卡级开关的默认值不是硬编码的 `false`,而是按「设置文档里有没有『你在用』的痕迹」迁移 —— 碰过的栏保持开着、没碰过的才是关;全新安装(空白文档)才是六栏全关。「快捷指令」那一栏多一条文件判据(0.3.0 起条目存在 `quick-prompts.json`,设置文档里看不出来)。
34
+ - **老用户不会被升级弄坏**:卡级开关的默认值不是硬编码的 `false`,而是按「设置文档里有没有『你在用』的痕迹」迁移 —— 碰过的栏保持开着、没碰过的才是关;全新安装(空白文档)才是各栏全关。「快捷指令」那一栏多一条文件判据(0.3.0 起条目存在 `quick-prompts.json`,设置文档里看不出来)。
35
35
  - **例外:0.7.0 的「统计行」是唯一默认开的栏**(它不参与上面那套"碰过才开"的迁移)。那五栏都**已经发布过**,一律默认关会在升级那一刻把用户正在用的东西当场关掉;而「统计行」是新能力,没有痕迹可依,用户要的是"装完立刻看得到效果"。`node test/check-sections.mjs` 把它单独列一行说明。
36
36
  - **升级前可以先看一眼**:`node test/check-sections.mjs` 拿你真实的设置文档跑一遍迁移,打印七栏会变成什么(**只读**,不写任何文件)。文件名按版本试:0.1.7 把 `settings.yaml` 改名成了 `settings.yaml.imported`,两个都读。
37
- - **关掉一栏 = 这一块完全不介入**,且栏内的值全部保留(打开即原样恢复):键位关 → 输入框按 DSH 原生键位;右键菜单关 → 本插件不介入;快捷指令关 → 输入框那枚按钮消失;设置面板关 → 不拖大小;OpenCode 请求头关 → 停止注入并撤销已写入的头;默认终端关 → 保持 PowerShell。
38
- - 「OpenCode 请求头」那一栏**没有额外的开关**:原来的「附加请求头」本来就是「这一栏要不要生效」,直接搬到了标题行。
37
+ - **关掉一栏 = 这一块完全不介入**,且栏内的值全部保留(打开即原样恢复):键位关 → 输入框按 DSH 原生键位;快捷指令关 → 输入框那枚按钮消失;设置面板关 → 不拖大小;默认终端关 → 保持 PowerShell。
39
38
  1. **设置 → 输入体验**(设置页新增条目)
40
- - 版式对齐社区插件 `@linxin666/dsh-web-all` 的「Web 插件」页:顶部常显「中文名 + 内嵌英文包名 `dsh-composer-ux` 的一行描述 + 总开关」;其下八个栏目为**可折叠卡片** —— 标题行左侧是「标题 + 一句动态概览 + 展开箭头」,右侧是**这一栏的开关与从属控件**(设置面板的缩放开关、默认终端的三档都在这里),长说明与其余控件在展开后的内容区;**默认全部折叠、不记忆展开状态**,可同时展开多个。标题行**只放卡级开关与从属控件**:右键菜单那 7 个条目开关按用户后来的反馈**搬回了卡内**(它们只对「自定义」档有意义,见下一条)。
39
+ - 版式对齐社区插件 `@linxin666/dsh-web-all` 的「Web 插件」页:顶部常显「中文名 + 内嵌英文包名 `dsh-composer-ux` 的一行描述 + 总开关」;其下为**可折叠卡片** —— 标题行左侧是「标题 + 一句动态概览 + 展开箭头」,右侧是**这一栏的开关与从属控件**(设置面板的缩放开关、默认终端的三档都在这里),长说明与其余控件在展开后的内容区;**默认全部折叠、不记忆展开状态**,可同时展开多个。标题行**只放卡级开关与从属控件**。
41
40
  - **键位**:分别配置「发送键」「换行键」——常用预设(Enter / Ctrl+Enter / Alt+Enter / Shift+Enter)+ 点击「自定义…」后直接按任意组合键录制(Esc 取消,Backspace 清除),支持清空为「无」;发送与换行不能设为相同按键;可一键恢复默认。
42
- - **右键菜单**:三档「菜单来源」(官方不介入 / 浏览器菜单 / 自定义菜单,见下「右键菜单」一节);卡内**只显示当前选中那一档的说明**(点官方看官方的、点浏览器看浏览器的、点自定义看自定义的;自定义档还带 Chrome / Edge 与 Firefox 的剪贴板授权说明);那 7 个条目(撤销 / 重做 / 剪切 / 复制 / 粘贴 / 删除 / 全选)**只在「自定义」档显示**、逐个开关(旁边一个「全部开启」)。这三件事有**真渲染测试**盯着(`node test/settings-render.mjs`:把设置页渲染成 HTML,断言三档正文互斥、那 7 行只在自定义档、标题行没有第二个入口)。
43
41
  - **快捷指令**:分类增删改 / 条目增删改与上下移 / 跨分类移动 / 每条一个插入模式(关 · 每次 · 仅首次)/ 优化强度三档(详见下节)。
44
42
  - **设置面板**:边缘调整大小一个开关(标题行);尺寸预设与拖拽说明在展开区。**导航列滚动由 DSH 官方的设置页自带**(0.1.7 起 `.navList` 就有 `overflow-y: auto`),所以 0.6.0 把「导航滚动」那个开关整项删掉了 —— 留着就是重复实现。
45
- - **OpenCode 请求头**:给 OpenCode 的模型请求自动附加 `x-opencode-session`(详见下节)。
46
43
  - **默认终端**:Windows 上把模型用的终端工具从 PowerShell 换成 Git Bash(三档在标题行;详见下节)。
47
44
  - **统计行**:输入框下方那行「缓存命中 xx%」按三位小数显示(0.7.0 新增,**唯一默认开的栏**,只有一个开关;详见下节)。
48
45
  - **金额**:输入框下方一颗极简金额胶囊(本会话费用估算,按 DeepSeek 官方刊例价;点开看三分项明细)。**0.24.0 起把子代理(含孙代理)花的钱也并进来** —— 那个数字是「本会话 + 子代理」的合计。**这一栏没有卡级开关**(胶囊常驻显示),卡内是**按模型改高峰/空闲两档单价**的编辑器(0.9.1 新增;留空=沿用官方价,可自加任意模型名;详见下节)。
@@ -50,7 +47,7 @@ DeepSeek Harness Web 输入体验增强插件:
50
47
  2. **快捷指令按钮**:输入框工具行里、「展开」按钮左侧的胶囊按钮,点开是常备提示词清单 + 「优化提示词」。
51
48
  3. **键位生效**(仅主聊天输入框):默认值 = 现状(Enter 发送、Shift+Enter 换行、Ctrl+Enter 加速提交),改动即时生效并持久保存。
52
49
 
53
- ## 0.23.0 新增:第九栏「自启动」
50
+ ## 0.23.0 新增:「自启动」卡
54
51
 
55
52
  - **开关跟着"你在哪个运行时里开"走**:网页版里打开就注册网页版(当前 profile、当前启动命令),
56
53
  桌面应用里打开就注册桌面应用;两个运行时各用自己那一条启动项(`dsh-composer-ux:autostart:<profile>`)。
@@ -63,10 +60,9 @@ DeepSeek Harness Web 输入体验增强插件:
63
60
 
64
61
  - **解释层「内置 Bash」按会话权限门控**:完全权限直跑;其它模式**必须**过 `ctx.sandbox.confine`,
65
62
  约束不是 `full` 就拒绝执行;拿不到权限模式或没有沙箱服务 ⇒ **不发这个工具**。
66
- - **「OpenCode 请求头」不再写你的配置**:改成挂官方 `internal/config` 事件做**运行期可逆覆盖**,
67
63
  0.21.x 及以前写进去的残留由迁移表一次性清掉(只删值与记账一致的,你自己写的同名头不动)。
68
64
  - **自检**:设置页多了「自检」卡(也可 `GET /composer-ux/doctor`),一行一句人话摊平
69
- 设置字段是否都在宿主 schema、九栏开关、终端服务与 bash 探测、请求头覆盖、快捷指令本、
65
+ 设置字段是否都在宿主 schema、各栏开关、终端服务与 bash 探测、快捷指令本、
70
66
  通知凭据、解释层档位、运行环境。
71
67
  - **CLI**:`npx dsh-composer-ux status|doctor|install|uninstall`(安装卸载一律委托官方 CLI)。
72
68
  - **长命令走文件**:解释层的 bash 工具支持 `command` 与 `script_path` 二选一,
@@ -118,29 +114,23 @@ dsh plugin --profile web add <你克隆或解压出来的目录>
118
114
  - 单个字母/数字键不允许裸绑定(会与打字冲突);纯修饰键不会触发录制。
119
115
  - 部分 Ctrl/Meta 组合被浏览器占用(如 Ctrl+L、Ctrl+W、Ctrl+J),录制时无法在页面捕获——建议使用 Alt/Shift 组合或 Enter 系键位。
120
116
 
121
- ## 右键菜单
117
+ ## 左侧「全局面板」折叠(0.27.0,0.28.1 起对别的插件友好)
122
118
 
123
- 设置 → 输入体验 → 右键菜单 里是三选一的**菜单来源**(只影响右键,不影响键位与其它功能):
119
+ 侧边栏最上面那一组(插件 / 自动化任务 / 上下文洞察 / MCP连接器 / 更多)可以折叠,设置 →
120
+ 输入体验 →「设置面板」卡 →「侧边栏」组:
124
121
 
125
- - **官方**(默认):本插件完全不介入,DSH 与其它插件自己的右键处理原样生效。DSH 官方输入框本身没有右键菜单,所以通常看到的就是浏览器的菜单。
126
- - **浏览器**:固定使用浏览器自带的菜单(样子随浏览器而变),并在本插件这一层**挡住其它插件的菜单**;好处是**粘贴免授权、零配置**。
127
- - **自定义**:使用本插件固定样式的菜单(未选中文本时「剪切 / 复制 / 删除」置灰)。这一档的「粘贴」要读剪贴板,浏览器会先要一次授权,三家处理不同:
128
-
129
- | 浏览器 | 首次/之后 | 改或撤销授权 | 免掉弹窗的办法 |
130
- | --- | --- | --- | --- |
131
- | Chrome / Edge | 弹出后点「允许」即记住这个站点,之后不再问 | 地址栏最左的网站图标 → 网站设置(Edge 叫「此站点的权限」)→ 剪贴板;或 `chrome://settings/content/clipboard`(Edge 是 `edge://settings/content/clipboard`) | 不需要:允许一次即可 |
132
- | Firefox | 每次点「粘贴」都会弹一个只有「粘贴(P)」一项的小窗(约 1 秒后才可点),点它才完成 | 无(没有站点授权面板) | **关不掉**:这是 Firefox 的安全机制,网页不允许静默读剪贴板。不想多这一步就按 Ctrl+V,或把「菜单来源」切成「浏览器 / 官方」档 |
133
-
134
- > 0.4.0 实测更正:早先文档里教过「改 `about:config` 里某个剪贴板首选项就不弹窗」——**那是错的**,那个弹窗与任何首选项都无关(用户在 Firefox 上照做后弹窗照旧)。依据:[MDN Clipboard API 的安全说明](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#security_considerations)(读到不允许的内容时,浏览器弹的临时菜单里只有一个 Paste 项、约 1 秒后才可点)、[caniuse](https://caniuse.com/mdn-api_clipboard_readtext)(只有带 `clipboardRead` 权限的扩展不显示粘贴提示)。能免掉它的设置都属于「允许任何网站静默读剪贴板」那一类,本插件不教、也不建议改。
135
- >
136
- > `chrome://` / `edge://` 这些地址**不能做成网页里的链接**(浏览器禁止页面跳转到内部协议),只能手输或复制粘贴到地址栏。被拒绝授权或无响应时,菜单里会提示「请用 Ctrl+V 粘贴」。
122
+ | 形态 | 界面 | 说明 |
123
+ |---|---|---|
124
+ | **收成一条**(默认) | 一行「全局面板 ▾」 | 点一下展开成原来那几颗,再点收起 |
125
+ | **只留图标** | 一行图标 | 悬停出名字 |
126
+ | **不折叠** | DSH 原样 | 等于关掉这个功能 |
137
127
 
138
- 自定义档的细节:
128
+ **"打开后默认折叠"是默认行为**:状态记在 `$DSH_HOME/composer-ux-nav-fold.json`,不占宿主设置字段。
139
129
 
140
- - 样式与系统编辑器菜单一致(深色圆角、三分组、快捷键右对齐);被关闭的条目不显示(分隔线自动合并);只在这档下才显示那 7 个条目开关与「全部开启」。
141
- - 撤销 / 重做 / 剪切 / 复制 / 删除 / 全选 走标准编辑命令;**粘贴 读取剪贴板后在光标处插入纯文本**——这是浏览器的安全限制。
142
- - 菜单打开时点击外部、Esc、滚动或窗口变化都会关闭。
143
- - 该档 `preventDefault` 掉浏览器菜单,并和「浏览器」档一样**挡住同一层里其它插件的捕获监听**(否则两边会各弹一个菜单)。
130
+ **别的插件往这里放按钮怎么办(0.28.1)**:只处理**认得出的面板行**(`button` + `aria-label` + 内含
131
+ `aria-hidden` 的图标)。别的插件**通过官方 panel 注册表**注册的入口,宿主会渲染成同样的结构,
132
+ 所以跟着一起折叠;而**直接往 DOM 里塞**的按钮/包装层**一个字都不改**——收成一条时它们原地留着、
133
+ 照样能点(这时只藏面板行,不藏整只 `nav`)。这条有 9 条测试盯着(`test/sidebar-nav.mjs` 第 6 节)。
144
134
 
145
135
  ## 快捷指令与提示词优化
146
136
 
@@ -363,25 +353,6 @@ Windows 桌面版实测通过(此前会弹「应用无法启动或已意外停
363
353
 
364
354
  Enter 那一路沿用既有的合成 Enter 回放;**官方发送按钮**那一路在捕获阶段认下点击、先把附加内容写回编辑器、再用同一个按钮重放一次点击。这样官方对「发送 / 排队 / 打断」的判定原样生效,本插件不做第二套提交语义。发送键与停止键共用同一个位置,靠图形区分——停止渲染 `<rect>`(方块),发送渲染 `<path>`(箭头),与界面文案、语言无关。
365
355
 
366
- ## OpenCode 请求头
367
-
368
- OpenCode 的接口要求客户端在每次请求里带上一个稳定的会话 ID 请求头(官方 Go 文档「可以在哪里使用?」第 3 条:为每段对话在 `x-opencode-session` 中发送会话 ID,以便其优化路由与提示词缓存)。DSH 的 Models 设置页明确不提供请求头编辑器(源码注释与 `README.zh.md` 都写明 profile `headers` 属于部署配置),所以本插件把这一项代办了。
369
-
370
- - **设置页位置**:设置 → 输入体验 → 「OpenCode 请求头」(折叠栏目):开关 / 头名 / 值 / 作用路由,附一行宿主半回写的「状态」。
371
- - **落点**:`llm-pi-ai` 的 provider profile —— `providers.<路由>.headers.<头名>`。这是 DSH 里唯一受支持的出网请求头入口:它作为 pi-ai 的 `optionsHeaders` **最后合并**(能覆盖默认头),且该适配器每次请求都重读配置,所以改完**下一次请求即生效**,不用重启、不用手工改 `settings.yaml`。
372
- - **写入方式**:`settings.mutate` 的路径寻址——只动我们那一个键,绝不重述或删除你写在同一个 profile 里的其它字段(`models` / `apiKeyEnv` / 其它 headers 都不碰)。
373
- - **目标路由怎么选**:profile 的 `models` 是必填项,凭空造一个只有 headers 的路由会让整份配置校验失败,所以只写**已存在**的路由。判据:
374
- - 「作用路由」**留空**时自动匹配——路由名以 `opencode` 开头(DSH 内置的 `opencode-go` 走这条,它的 baseURL 由 pi-ai 目录内置、配置里读不到),**或**该路由的 `baseURL` 主机是 `opencode.ai`(含子域)。后一条是为自建别名路由准备的:例如把 OpenCode 端点配成 `go: { baseURL: https://opencode.ai/zen/go/v1 }`,名字里没有 opencode 也能被认出来。
375
- - 「作用路由」**填了名单**时只看名单(逗号或空格分隔),且只保留其中确实存在的路由。
376
- - 判据只看主机名:`https://opencode.ai.evil.example/v1` 这类仿冒主机不会被匹配。
377
- - **撤销**:关栏目开关、关插件总开关、或改头名,都会自动清掉此前写入的那一个键;并且只清「值等于当前配置值或记账值」的,用户自己手写的同名头不会被误删。
378
- - **值**:所有对话共用同一个值(栏目里可改、可「重新生成」);第一次启用若留空,会自动生成一个 UUID 并保存沿用。OpenCode 文档原话是要「每段对话」一个 ID——按会话变化的头 DSH 的配置层做不到(`llm/stream` 钩子被设计成只能读不能改,`GenerateOptions` 里没有 `headers` 字段),所以这里退一步用固定值。已知代价:所有对话挤同一个上游(没有负载分散);单段对话内的缓存命中不受影响。另外它**不保证**缓存一定命中(还取决于上游模型与网关策略)。
379
- - **自校验**:写入前用与 llm-pi-ai 相同的规则(`new Headers()`)校验头名与头值;非法值被拒绝,原因写进栏目的「状态」行,而不是把整条路由弄坏。
380
- - **实测结论(本机验证过,不是推断)**:
381
- - **它是硬门槛,不是优化项。** 同一端点、同一模型,只把这条请求头去掉,OpenCode Go 直接返回 **HTTP 400 `MissingSessionID`**:*"Request is missing x-opencode-session and cannot be routed efficiently."* —— 没有它,DSH 里的 opencode-go 完全不可用。
382
- - **带着它时真实调用成功,且前缀缓存在工作**:同一段前缀连发两次,`input` 174 → 46、`cacheRead` 192 → 320(总前缀 366 不变),第二次有更多内容直接命中缓存。
383
- - **线级证据**:用 `test/opencode-header-wire-probe.mjs` 起一个本地端点,DSH 发过去的推理请求上确实带着 `x-opencode-session: <值>`,以及它自己的 `user-agent: deepseek-harness/0.1.5-rc.2 (+https://github.com/deepseek-ai/deepseek-harness)`(正好满足 OpenCode 文档对客户端标识的第 2 条要求)。
384
-
385
356
  ## 默认终端(Windows)
386
357
 
387
358
  Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`),而模型的训练语料里 bash 占绝对多数。这一栏把终端换成 **Git Bash**:模型看到的工具就叫 `bash`,`pwsh` 从它的工具列表里消失 —— 一个会话只面对一个终端工具。
@@ -429,7 +400,7 @@ Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`)
429
400
 
430
401
  输入框下方那行用量统计里的「缓存命中 12%」,官方给的是**整数**;这一栏把它改成**三位小数**(`缓存命中 12.346%`)。
431
402
 
432
- - **设置页位置**:设置 → 输入体验 → 「统计行」(0.7.0 新增的第 7 栏,**唯一默认开的栏**)。**只有一个开关**(标题行上那一个)—— 这一栏只有一件事可开可关,所以不另设子开关(与「OpenCode 请求头」那一栏同一处理)。
403
+ - **设置页位置**:设置 → 输入体验 → 「统计行」(0.7.0 新增的第 7 栏,**唯一默认开的栏**)。**只有一个开关**(标题行上那一个)—— 这一栏只有一件事可开可关,所以不另设子开关。
433
404
  - **为什么会有这一栏**:社区插件 [dsh-cache-precision](https://github.com/Cheng-cheng9669/dsh-cache-precision) 提供了这条路子,用户看到后要求把同样的能力并进本插件。**能力移植、实现没有照抄** —— 下面每条都与它不同,且各有测试盯着。
434
405
  - **数字口径与官方完全一致**:`缓存读 ÷(未缓存输入 + 缓存读 + 缓存写)`(三个桶互不重叠,与官方 `billedInputTokens()` 同一份)。所以屏幕上那两个百分比不会互相打架。
435
406
  - **不撒谎(最重要的一条)**:官方的整数档有一条很少人注意的规则 —— 当整数四舍五入会把「没满」显示成 `100%` 时,它会**自动多给几位**(例如 `99.95%`),源码注释原话是 "without rounding a partial hit to 100%"。本插件把默认档抬到三位后**保留**了这条规则:三位小数一旦会凑成 `100.000%` 就继续加位。阶梯如下:
@@ -493,7 +464,6 @@ Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`)
493
464
  - **写完价目要立刻生效,靠的是"写的人通知",不是设置变更事件**:0.1.7 的 `settings/document-updated` 只在 `describe()` 里比对 raw 变化后发出,而我们自己 `mutate` 写设置时并不调 `describe()` —— 设置页开着时会被界面刷新顺带触发,**设置页没开着**(例如后台的自动同步跑完)就不会发;而折叠缓存按 `seq` 去重、改规则不会重折旧事件,金额会**静静地停在旧价格档**上、界面上看不出。所以宿主半加了模块级 `moneyInvalidators` 注册表:用量路由登记"重读规则 + 作废价目缓存 + 作废折叠缓存",两条同步路径写完设置直接调 `invalidateMoney()`,事件只作补充。
494
465
  - **内置第三方价目快照(0.10.0)**:`src/provider-prices.ts`(**生成**的,别手改:`node scripts/gen-provider-prices.mjs <models.dev-api.json>`)收了 **11 个 provider / 346 个模型**,快照日期写在文件头。**没点过同步时**就是非 DeepSeek 模型的兜底价;点过一次「同步第三方价目」后最新数据会盖住同名条目,界面分别标明"内置快照价"还是已同步。查价优先级:**用户覆盖价 > 已同步价目 > 内置快照 > 未定价**。
495
466
  - **非 DeepSeek 模型定价(0.10.0)**:按 `(provider, model)` 查价 —— 精确 provider → `PROVIDER_ALIASES` 别名(`deepseek-official`→`deepseek`、`kimi-coding`→`moonshotai`…)→ **按模型 id 全局唯一匹配**(`glm-5` 在两家都有的价时**拒绝猜**)。**认不出价就说"未定价"**(单价全 0,明细页写明"去同步第三方价目或给这行填个价"),**不再把 DeepSeek 的 flash 价静默套到第三方模型头上** —— 编一个看着合理的假数字,比承认不知道更糟。
496
- - **「OpenCode 请求头」这一栏(0.25.0 起只给旧宿主)**:OpenCode Go 要求客户端按**每段对话**发 `x-opencode-session`。上游 `@earendil-works/pi-ai` 从 **1.0.2** 起自带 `dist/providers/opencode-headers.js`(`opencode` / `opencode-go` 两个 provider 都被包过),用`options.sessionId` 发**真实会话 id**,且「已有同名头就不覆盖」。本插件注入的是**固定 UUID**,开着反而会顶掉真实 id ⇒ 0.25.0 起宿主半**先探测**(判据是宿主 pi-ai 里有没有那个模块;桌面 asar与 pnpm 检出两种布局都覆盖),探到原生支持就**不注入、不生成 UUID、不记账**,状态行写「宿主已原生支持…本栏不再注入」;探不到按旧宿主处理(继续注入),也可用`DSH_COMPOSER_UX_HEADER_NATIVE=1|0` 强制。`test/host-header-mirror.mjs` 第 19 / 20 节把两条分支与探测判据逐条钉住。
497
467
  - **订阅通道的额度口径(0.26.0)**:OpenCode Go 这类「月费 + 额度」的套餐**单次调用不扣钱、扣额度**,
498
468
  所以"按刊例价折算"出来的 ¥ 既不是账单、也不是额度消耗(用户 2026-10-27 的「感觉还是不准」就是这个)。
499
469
  现在宿主半对订阅通道**另算一份额度**:`额度(USD) = Σ token × 该模型该档单价`(单价与月度上限来自
@@ -520,41 +490,6 @@ Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`)
520
490
  - **六条自开路由全部过了官方那道关卡**:`connection.requestRejection(request)`(Host/Origin 围栏 + 浏览器登录令牌)。`webServer` 是可以绑 `0.0.0.0` 的,漏掉它等于把会话用量、设置写入与**账号余额**摊给同网段任何一台机器 —— 2026-09-29 评审时先补了金额那三条,同日对比社区插件时又发现**「优化提示词」与「快捷指令存储」这两条也漏着**(一个花你的模型额度、一个会覆盖你的提示词库),一并补上,且关卡摆在**读请求体之前**(被拒的请求连 body 都不读)。现在 6/6:用量、价目同步、余额、重启、优化、快捷指令存储(终端状态那条在 `terminal/host.ts` 里自带同一道关卡)。变异 `CN`/`CO` 守着这两条。
521
491
  - **与出处有意不同的两处**(都钉在测试里):① 金额**至少保留两位小数**(出处会把 `0.1` 显示成 `¥0.1`、把 `0` 显示成 `¥0.`);② **覆盖价的币种语义**——出处是"有覆盖价时先整体重建人民币档、美元再从人民币折算",写成"在美元列上套覆盖价再折算"会差一个汇率(≈15 倍)而屏幕上只是个数字,所以专门有一条断言守着。
522
492
 
523
- ## 工作区目录选择:设置里开关,桌面用系统文件夹框、手机/远端用官方应用内框(0.18.0 起,0.19.0 起可开关且默认关)
524
-
525
- **为什么会有这一节**:DSH 官方的目录选择是两个**互斥**的界面 —— `-native`(本机 OS 选择器,Windows 上就是系统文件夹框)与 `-browse`(应用内 Miller 分栏对话框),由 `-auto` 在**宿主启动时**二选一,判据只有「绑定地址 / SSH / 平台」,**不看客户端是谁**。官方在 `-browse` 的 README「已知限制」里承认了由此产生的缺口:
526
-
527
- > **不枚举盘符根** —— Windows 上祖先链止于盘符根;跨盘依赖浏览器 UI 的路径输入入口。
528
-
529
- 于是本机换盘必须手输路径;而社区 bundle `dsh-mobile` 为了手机端能选目录又把 browse 钉死,连桌面也一起用上了应用内框(官方把「按客户端自适应」明确列为 deferred)。
530
-
531
- **开关在哪儿**:设置 →「输入体验」→ 卡片「**工作区目录选择**」(在「微信通知」下面)。**默认关**,拨一下就即时生效(不用重启)。
532
-
533
- - **关(默认)**:完全不管这件事,桌面用的是官方那个应用内浏览对话框(与没装本插件时一致)。
534
- - **开**:桌面应用里点「选择工作区目录」→ 直接弹 **Windows 系统文件夹框**:任意盘符点进去、可新建文件夹,**不用手输路径**。
535
-
536
- 为什么默认关:这一项的做法是**接管官方那两个目录流程槽位**(把官方的框换成系统的框),属于改默认行为 —— 不打招呼就换掉别人的选择框是越界,所以留给你自己点(与「微信通知」总开关默认关同一个理由)。
537
-
538
- **本插件怎么做的**(不改官方任何文件、不抄官方那一千多行界面):用槽位自身的 shadow 能力 —— 官方 `ui-slots` 的规则是「entries sharing one cell coexist at distinct priorities … the cell's lowest live entry renders」,只有**同优先级**的第二次注册才报错:
539
-
540
- | 客户端 | 有没有 preload 桥 | 开关打开时本插件 | 你看到什么 |
541
- |---|---|---|---|
542
- | 桌面应用(`DeepSeek Harness.exe`) | 有(`window.__DSH_DIRECTORY_PICKER__`) | 在优先级 `-1` 上接管这两个目录流程槽位 | 直接弹 **Windows 文件夹框**:任意盘符点进去、可新建文件夹,**不用手输** |
543
- | 手机 / 远端浏览器 | 没有 | **一个槽位都不注册** | 官方那个应用内浏览对话框(路径输入、层级列表、隐藏文件开关、新建文件夹) |
544
-
545
- 所以两件事同时成立:桌面不再手输盘符,手机端体验**零退化**;也**不需要任何 profile 补丁**(插件不装或开关关着 = 官方原样)。
546
-
547
- 几条边界(都是刻意的):
548
-
549
- - **判据是 preload 桥,不是"你是不是本机"**:在电脑上用普通浏览器打开 `127.0.0.1:19387`(不是桌面应用窗口)也会走应用内框 —— 客户端拿不到"我离宿主多远"这个事实,官方把「按连接自适应」列为 deferred。
550
- - **关掉要能真的卸掉**:开关是运行时值,用户会在不重启的情况下反复拨。`createDirectoryFlowController` 负责幂等装卸(开就装、关就卸、再开重装),`test/directory-flow.mjs` 第七节 12 条盯着它,变异 `GZ`/`HA` 盯着"幂等"与"关掉要卸"。
551
- - **优先级必须是负数**:官方界面注册在默认优先级 `0`;同优先级会撞上「第二次注册」⇒ 安装期直接抛 `single slot "…" already has a registration`,整块客户端插件挂不上。`test/directory-flow.mjs` 钉住这条。
552
- - **一条上升沿只弹一次**:持有方正在采纳(`open` 仍为 true、`busy`)时的重渲染不会再弹一次;`open` 落回才重新上膛。
553
- - **卸载后迟到的结果丢弃**:原生对话框没有按请求中止的能力;HMR 换掉占用者后旧实例的请求仍会落地,让它去 `onPicked` 会凭空多出一个工作区。
554
- - **取消与失败各是一条路**:取消(`pick` resolve 成 null)→ 持有方只关掉流程;失败(抛异常)→ 界面显示 `Error.message`(不是 `Error: xxx`)。
555
- - **只依赖槽位服务**:本插件客户端半的 `inject` 仍然只有 `slots`,没有引入 `uiWorkspace` —— 少一个依赖就少一种"等不到依赖而整块不挂载"的死法。
556
-
557
-
558
493
  ## 维护:重启 DSH
559
494
 
560
495
  装了新插件、改了宿主半代码(比如本插件的「默认终端」、键位 schema)之后,DSH 需要重启才会加载新代码。设置页「输入体验」卡片**抬头右端**(GitHub 链接左边)有一枚「重启 DSH」。
@@ -673,7 +608,6 @@ dsh-composer-ux/
673
608
  │ ├── interceptors.ts # keydown/contextmenu 捕获拦截 + 回放
674
609
  │ ├── SettingsSection.tsx # 设置页「输入体验」(顶部常显 + 折叠栏目 + 默认终端 / 统计行卡片)
675
610
  │ ├── settings-style.ts # 折叠卡片样式表(dsh-ux-* 类名注入)
676
- │ ├── ContextMenuHost.tsx # shell.overlay 右键菜单
677
611
  │ ├── QuickCommandsButton.tsx # 快捷指令:工具行入口按钮(order 89)
678
612
  │ ├── QuickCommandsPanel.tsx # 快捷指令:展开面板(分类两级 + 条目 + 优化入口)
679
613
  │ ├── optimize-clock.ts # 优化:面板与结果框共用的秒表读数(0.11.1 新增)
@@ -735,9 +669,9 @@ node build.mjs # 产出 lib/index.js + lib/client.js
735
669
  # ⚠️ 宿主半会**内联** schemastery / cosmokit:
736
670
  # 优先用 DSH 检出里的 vendor 副本,检出不在时退到 node_modules
737
671
  # 里同版本的 npm 包(两者逐字节相同)——CI 上走的就是退路
738
- npm test # 35 个套件;当前 2889 passed, 0 failed(2026-10-28 实测;CI 三平台同样全绿)
672
+ npm test # 29 个套件;当前 2603 passed, 0 failed(2026-10-28 实测;CI 三平台同样全绿)
739
673
  # 走 scripts/run-tests.mjs:顺带把"多少套件/多少条"记进 test/.last-run.json
740
- npm run test:mutations # 手动跑:变异测试,证明那套护栏真的在咬人(233 条,须单独跑)
674
+ npm run test:mutations # 手动跑:变异测试,证明那套护栏真的在咬人(200 条,须单独跑)
741
675
  # 同样记录结果,供下面的文档门禁核对
742
676
  npm run gates # 发版门禁三条一起跑:tag 指向 / 包内容 / 文档数字
743
677
  npm run check:tag # ① tag 名里的版本 == 该 tag 所指提交里的 package.json 版本
package/README.simple.md CHANGED
@@ -59,10 +59,8 @@ dsh plugin --profile <你的profile名> add dsh-composer-ux
59
59
  | 功能 | 说人话 | 什么时候用得上 |
60
60
  |---|---|---|
61
61
  | **键位** | 发送键、换行键随你设(Enter / Ctrl+Enter / 自己按一遍录下来) | 用别的软件习惯了另一种键位 |
62
- | **右键菜单** | 在输入框里右键,出来的是官方那套、浏览器那套、还是这个插件补的 7 项(撤销/重做/剪切/复制/粘贴/删除/全选) | 嫌浏览器右键菜单在输入框里不好用 |
63
62
  | **快捷指令** | 输入框上方一枚「⚡ 快捷指令」按钮,点开是一个提示词清单,点一下就插进输入框;常写的可以设成"每次发送都自动带上" | 天天重复敲同一段提示词 |
64
63
  | **优化提示词** | 同上那枚按钮点开 → 点「✨ 优化提示词」→ 模型把你的草稿改好,**一条一条**给你看改了哪儿,改完你可以自己再改,最后点「插入输入框」才替换你的原文 | 打字快但表达糙,或者想让提示词更完整 |
65
- | **OpenCode 请求头** | 自动给 OpenCode 的模型请求加一个它认识的标记,让它在 DSH 里能用 | 你在用 OpenCode(Go)那类路由 |
66
64
  | **默认终端** | Windows 上把模型用的终端从 PowerShell 换成 Git Bash | 模型敲的命令老是 PowerShell 语法报错 |
67
65
  | **官方持久终端** | 给模型一套"开了就一直开着"的终端工具 | 官方自带的那几个终端工具 |
68
66
  | **统计行** | 输入框下面「缓存命中 xx%」显示成三位小数(不再四舍五入到 100%) | 想看缓存到底命中多少 |
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "dsh-composer-ux.input-experience",
5
5
  "name": "dsh-composer-ux",
6
- "version": "0.27.0",
6
+ "version": "0.28.1",
7
7
  "license": "MIT",
8
8
  "facets": {
9
9
  "host": {
@@ -40,7 +40,7 @@
40
40
  {
41
41
  "icon": "./icon.png",
42
42
  "title": "dsh-composer-ux",
43
- "description": "输入体验增强:键位、右键菜单、快捷指令、提示词优化、通知、统计与成本、目录选择、终端策略、自启动(九栏,逐栏开关)"
43
+ "description": "输入体验增强:键位、快捷指令、提示词优化、统计与成本、终端策略、自启动(逐栏开关)"
44
44
  }
45
45
  ],
46
46
  "x-prompt-sections": [