dsh-chat-preset 1.1.1 → 1.2.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.
Files changed (4) hide show
  1. package/README.md +206 -111
  2. package/cordis.patch.yml +8 -3
  3. package/index.js +480 -366
  4. package/package.json +3 -3
package/README.md CHANGED
@@ -1,185 +1,280 @@
1
1
  # dsh-chat-preset
2
2
 
3
- 给 dsh 的 **ChatOnly** 预设打包的一整套实现:预设行本身,加上它应有的请求形态——没有运行时上下文快照、没有工具 schema、系统提示词从文件热读。
3
+ > ChatOnly —— 本项目提供的预设 agent。抑制工具注入与工作目录注入,使请求仅包含用户输入,以及可选的自定义系统提示词。
4
4
 
5
+ [![CI](https://github.com/Xxx-Bin/dsh-chat-preset/actions/workflows/ci.yml/badge.svg)](https://github.com/Xxx-Bin/dsh-chat-preset/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/dsh-chat-preset.svg)](https://www.npmjs.com/package/dsh-chat-preset)
5
7
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
6
8
  [![node](https://img.shields.io/badge/node-%E2%89%A518-3c873a.svg)](https://nodejs.org)
7
- [![dsh](https://img.shields.io/badge/dsh-plugin-4b5563.svg)](#前置要求)
8
9
 
9
- ---
10
-
11
- ## 目录
12
-
13
- - [为什么需要它](#为什么需要它)
14
- - [它做什么](#它做什么)
15
- - [两个 role](#两个-role)
16
- - [前置要求](#前置要求)
17
- - [安装](#安装)
18
- - [配置](#配置)
19
- - [系统提示词](#系统提示词)
20
- - [限制与冲突](#限制与冲突)
21
- - [排查](#排查)
22
- - [测试](#测试)
23
- - [License](#license)
10
+ > **安装前须知。** 本包不依赖公开插件 API,其行为由 DSH 的三处内部语义决定(作用域同名 shadow 覆盖、`system-prompt/assemble` 瀑布、`refreshContext()` 的合并顺序)。上述语义变更时,本包会**静默失效**:不报错、不崩溃,仅退回默认请求形态。详见[兼容性与脆弱性](#兼容性与脆弱性)。
24
11
 
25
12
  ---
26
13
 
27
- ## 为什么需要它
28
-
29
- dsh 的默认请求对「纯对话」这个场景是过重的。三件事会同时出现在每一条 ChatOnly 请求里:
14
+ ## 概述
30
15
 
31
- 1. **运行时上下文快照**——`dsh-working-directory` 把当前工作目录注入成一条 `runtime-context` 用户消息;
32
- 2. **全局工具 schema**——桌面端 bundle 注册的 `acp_*`、`sidebar_open`、`working_directory`、`search_context` 等,约 10 个工具对纯对话没有任何意义,却占着 prompt 预算;
33
- 3. **写死在预设里的系统提示词**——改一次要重启,没法边聊边调。
16
+ DSH 默认的最小配置是精简模式。即便在该模式下,请求仍包含两类注入,且均无法在预设配置层消除:
34
17
 
35
- 前两项**都不能在预设行里解决**:
18
+ 1. **工具 schema。** 工具在全局层收集,与预设作用域无关,预设只能追加、不能移除。
19
+ 2. **工作目录消息。** DSH 默认在用户输入之后追发一条 `runtime-context` 用户消息,内容为当前工作目录 `Current working directory: C:\…`;该 contribution 以 `required: true` 注册,`includeRuntimeContext: false` 只过滤可选 contribution。
36
20
 
37
- - 上下文那项被注册成 `required: true`。预设的 `includeRuntimeContext: false` 走的是 `suppressRuntimeContext()`,它只移除 *可选* contribution,`refreshContext()` 仍会保留所有 `required === true` 的条目。
38
- - 工具是**全局**收集的(`dsh-system-prompt/lib/index.js:364-365`),预设作用域只能加、不能删,也没有 `suppressTools()`。
39
- - 连 `agent/pre-step` 里改写 decision 也没用:`dsh-agent-loop` 的 `step()` 会自己重算上下文并 append 到 surface,`decision.messages` 只用来判断*要不要*接纳它。
21
+ 本包提供 ChatOnly 预设 agent,在装配瀑布与 context 注册层分别消除这两类注入,并将系统提示词改为文件热更新,用于受控条件下的模型能力评测。
40
22
 
41
- 所以这个包换了一条路:**在 agent 作用域里用同名影子覆盖那个 contribution**,再从装配瀑布里把工具列表清空。详细推导和源码行号见 [`index.js`](./index.js) 头部注释。
23
+ ## 请求形态
42
24
 
43
- ## 它做什么
25
+ 测量环境:DSH 桌面端,`desktop` profile。
44
26
 
45
- | 能力 | 做法 | 结果 |
46
- |---|---|---|
47
- | 拦截运行时上下文快照 | 在 **agent 作用域**用同名影子覆盖 `working-directory:current`(`required: false` + `text: ''`) | 请求与会话日志里都不出现 `Current runtime context` / 工作目录那句 |
48
- | 剥离工具 | 钩 `system-prompt/assemble` 瀑布,清空 `assembly.tools` | 请求里连 `tools` 键都没有 |
49
- | 系统提示词热读 | 占 `deployment:persona-prefix` 槽位(`complete: true`),`text` 是函数,每次装配读文件 | 改文件 → 下一条消息生效,免重启 |
50
-
51
- 前两项只对 `config.presetId`(默认 `chatonly`)生效,其他预设不受影响。
27
+ ```diff
28
+ # 抑制前
29
+ - tools: 9 个 — acp_cache, acp_read_original_image, acp_status, compress,
30
+ - decompress, external_plugins, load_workspace_dependencies,
31
+ - search_context, working_directory
32
+ - messages: [ …, { source: { kind: "runtime-context" } } ]
33
+ - # 用户输入之后追发的工作目录消息
34
+ # 抑制后
35
+ + tools: 该键不存在
36
+ + messages: 仅用户输入
37
+ ```
52
38
 
53
- ## 两个 role
39
+ 工具数量随已安装插件变化,本机观测区间为 **8–14 个**。
54
40
 
55
- 同一个模块挂两次,靠 `config.role` 分流:
41
+ ## 预设层的限制
56
42
 
57
- | role | 挂载位置 | 做什么 |
58
- |---|---|---|
59
- | `policy`(默认) | 全局 | 拦截上下文、剥离工具、注册 `/chatprompt` |
60
- | `section` | 预设的 `plugins` 里 | 占 persona 槽位,从文件读系统提示词 |
61
-
62
- ## 前置要求
43
+ | 目标 | 预设层的限制 |
44
+ |---|---|
45
+ | 移除工作目录消息 | 该 contribution 注册为 `required: true`;`suppressRuntimeContext()` 仅过滤可选项 |
46
+ | 移除全局工具 schema | 工具在全局层收集,预设作用域只可追加;不存在 `suppressTools()` |
47
+ | 改写 `agent/pre-step` 决策 | `dsh-agent-loop` 的 `step()` 会重新计算上下文并写入 surface,从 decision 中移除消息只会使其在 fallback 分支重建 |
63
48
 
64
- - **dsh**(桌面端或 CLI),`dsh plugin` 子命令可用
65
- - **pnpm** 在 `PATH` 上——`dsh plugin add` 内部就是转发 `pnpm add`
66
- - **Node.js ≥ 18**(ESM;`test.mjs` 用到顶层 `await`)
49
+ 本包因此改由三处机制实现:agent 作用域内的同名空 shadow、`system-prompt/assemble` 瀑布、以及 `buildRequest()` 在工具列表为空时省略该键的行为。推导过程与源码行号见 [`index.js`](./index.js) 头部注释。
67
50
 
68
51
  ## 安装
69
52
 
70
- dsh 把「把一个包装进 profile」做成了 **pnpm 转发 + 事后对账**,所以不用手改 `bundles`:
53
+ 依赖:**dsh**(桌面端或 CLI)、`pnpm` 位于 `PATH`(`dsh plugin add` 内部转发 `pnpm add`)、**Node.js ≥ 18**。
71
54
 
72
55
  ```bash
73
- # 直接从 GitHub 装
56
+ # npm
57
+ dsh plugin --profile desktop add dsh-chat-preset
58
+
59
+ # GitHub
74
60
  dsh plugin --profile desktop add github:Xxx-Bin/dsh-chat-preset
75
- ```
76
61
 
77
- ```bash
78
- # 或先克隆再 link(改代码即时生效,调试用这个)
62
+ # 本地路径(修改代码后需重启 dsh,模块存在缓存)
79
63
  git clone git@github.com:Xxx-Bin/dsh-chat-preset.git
80
- dsh plugin --profile desktop add link:/absolute/path/to/dsh-chat-preset
64
+ dsh plugin --profile desktop add /absolute/path/to/dsh-chat-preset
81
65
  ```
82
66
 
83
- `dsh plugin <profile> add` 会在 profile 目录里跑 `pnpm add`,然后对账 `dsh.profile.bundles`:凡是**已安装的依赖**且 **`package.json` 声明了 `dsh.bundle`** 的,自动追加进 layer stack。本包声明了 `dsh.bundle.patch`,所以加完依赖它自己就进 bundles 了。
67
+ `--profile` 为 profile 名,即 `~/.dsh/profiles/` 下的目录名;桌面端默认为 `desktop`。`add` 在该目录执行 `pnpm add`,随后核对 `dsh.profile.bundles`:凡已安装且 `package.json` 声明 `dsh.bundle` 的依赖会被追加进 layer stack。本包声明 `dsh.bundle.patch`,安装后自动登记。
84
68
 
85
- 重启 dsh,在预设选择里勾 **ChatOnly**。
69
+ **安装后须重启 dsh。** bundles 层仅在启动时合成,运行期不监听变更。重启后在预设选择中启用 **ChatOnly**。
86
70
 
87
71
  <details>
88
72
  <summary>手动安装(不依赖 dsh CLI)</summary>
89
73
 
90
- 1. 在 `~/.dsh/profiles/<profile>/package.json` 的 `dependencies` 里加
91
- `"dsh-chat-preset": "link:/absolute/path/to/dsh-chat-preset"`
92
- 2. 把它加进同文件的 `dsh.profile.bundles` 数组
93
- 3. 在该 profile 目录跑 `pnpm install`
74
+ 1. 在 `~/.dsh/profiles/desktop/package.json` 的 `dependencies` 中添加 `"dsh-chat-preset": "link:/absolute/path/to/dsh-chat-preset"`
75
+ 2. 在同一文件的 `dsh.profile.bundles` 数组中添加 `"dsh-chat-preset"`
76
+ 3. 在该目录执行 `pnpm install`
94
77
  4. 重启 dsh
95
78
 
96
79
  </details>
97
80
 
98
- 装完先确认它真的加载了:看 `~/.dsh-filter-rtc-debug.log`,应该出现 `{"evt":"applied",...}` 和 `{"evt":"prompt-section",...}` 各一行。
81
+ ## 使用
82
+
83
+ ### 系统提示词:`/chatprompt`
84
+
85
+ 提示词存于 `~/.dsh/chatonly-system-prompt.md`,命令仅作为读写入口,亦可直接编辑文件。
86
+
87
+ | 命令 | 行为 |
88
+ |---|---|
89
+ | `/chatprompt <text>` | 写入文件,下一条消息生效 |
90
+ | `/chatprompt` | 输出预览、字符数与文件路径 |
91
+ | `/chatprompt show` | 输出全文 |
92
+ | `/chatprompt clear` | 清空文件;此后不发送系统提示词 |
93
+
94
+ 文件为空时不发送任何系统消息,请求中仅含用户输入。`deployment:persona-prefix` 段由本包以 `complete: true` 独占,不再拼接部署层其它文本。
95
+
96
+ > 该命令全局注册,可在任意预设下调用,但仅影响 ChatOnly;在其它预设下修改不会生效,亦无提示。
97
+
98
+ ### 工具抑制开关:`/chattools`
99
+
100
+ 默认抑制,即请求不携带任何工具 schema。
101
+
102
+ | 命令 | 行为 |
103
+ |---|---|
104
+ | `/chattools` 或 `/chattools status` | 输出当前模式与状态文件路径 |
105
+ | `/chattools on` | 抑制工具注入 |
106
+ | `/chattools off` | 放行,`assembly.tools` 原样传递 |
107
+ | `/chattools reset` | 删除状态文件,回退出厂默认 |
108
+
109
+ 状态存于 `~/.dsh/chatonly-strip-tools.state`,内容为 `strip` 或 `pass`。每次装配重新读取,切换后下一条消息生效;文件不存在时采用 `config.stripTools`。参数接受 `on`/`strip`/`intercept`/`block`/`true`/`1` 与 `off`/`pass`/`allow`/`bypass`/`false`/`0` 两组同义词,无法识别时返回错误且不改变状态。
110
+
111
+ 放行状态下,本包原样返回下游装配结果,其它插件的工具追加与过滤逻辑均按原有顺序生效。本包不提供工具,仅在抑制状态下清空列表。
112
+
113
+ > 该命令全局注册且作用于所有 ChatOnly 会话:状态是单一文件,不按会话或 agent 区分。
114
+
115
+ #### 两种抑制相互独立
116
+
117
+ `/chattools off` 仅放行工具。`working-directory:current` 在该会话中仍为空,模型不获得工作目录及其它环境信息。
118
+
119
+ 若启用文件类工具而未提供工作目录,模型缺少定位依据,评测结果将混入环境缺失的影响。因此放行工具时应同时满足两项条件:
99
120
 
100
- ## 配置
121
+ 1. 通过 `dsh-tool-manager` 等插件将工具集限制为本次评测所需的最小集合。全局注入的 8–14 个工具签名不属于评测变量。
122
+ 2. 在系统提示词中补充工作目录与环境约束。抑制状态下,该文件是唯一的环境信息来源。
101
123
 
102
- `cordis.patch.yml` 里的 `chatonly-context-policy` 行:
124
+ ### 配置
125
+
126
+ `cordis.patch.yml` 以 `config.role` 分流两处挂载:
127
+
128
+ | role | 挂载位置 | 职责 |
129
+ |---|---|---|
130
+ | `policy`(默认) | 全局 | 抑制工作目录注入、按开关抑制工具、注册 `/chatprompt` 与 `/chattools` |
131
+ | `section` | ChatOnly 预设 agent 的 `plugins` | 占用 persona 槽位,从文件读取系统提示词 |
103
132
 
104
133
  ```yaml
105
- - id: chatonly-context-policy
106
- name: 'dsh-chat-preset'
107
- config:
108
- role: 'policy'
109
- presetId: 'chatonly' # 目标预设 id
110
- contextName: 'working-directory:current' # 要覆盖的 contribution 名
111
- stripTools: true # false = 保留工具
112
- file: '~/.dsh/chatonly-system-prompt.md' # /chatprompt 的写入目标
134
+ - insert:
135
+ # 行 1:全局挂载(policy)
136
+ - id: chatonly-context-policy
137
+ name: 'dsh-chat-preset'
138
+ config:
139
+ role: 'policy'
140
+ presetId: 'chatonly' # 须与预设行的 config.id 一致
141
+ contextName: 'working-directory:current' # 被覆盖的 contribution 名
142
+ stripTools: true # 出厂默认;运行期由 /chattools 切换
143
+ toolsStateFile: '~/.dsh/chatonly-strip-tools.state'
144
+ file: '~/.dsh/chatonly-system-prompt.md' # /chatprompt 写入目标
145
+
146
+ # 行 2:定义预设 ChatOnly,并在其 plugins 内挂载 section
147
+ - id: preset-chat
148
+ name: '@deepseek-ai/dsh-agent-preset'
149
+ config:
150
+ id: chatonly
151
+ name: ChatOnly
152
+ order: 5
153
+ plugins:
154
+ - id: prompt
155
+ name: 'dsh-chat-preset'
156
+ config:
157
+ role: 'section'
158
+ file: '~/.dsh/chatonly-system-prompt.md' # 装配读取来源
113
159
  ```
114
160
 
115
- ## 系统提示词
161
+ **`presetId` 必须与预设行的 `config.id` 一致。** 两者不同时,`policy` 行不会作用于该预设,注入照常存在。
162
+
163
+ **两处 `file` 必须一致。** 写入与读取路径不同时,`/chatprompt` 会报告保存成功而系统提示词从未生效。
116
164
 
117
- - 文件:`~/.dsh/chatonly-system-prompt.md`(空文件 = 不发任何系统消息)
118
- - 命令:`/chatprompt`(预览)· `/chatprompt show` · `/chatprompt clear` · `/chatprompt 正文…`
119
- - **文件内容就是全部系统提示词**,不会自动拼 `You are an AI agent…` 之类的框架话
165
+ **`toolsStateFile` 不得与 `file` 指向同一路径。** 开关文件按单行关键字解析,读到提示词全文时识别失败并永久回退出厂默认;反向更糟,`/chattools on|off` 会把提示词覆盖成一行 `strip`/`pass`。
120
166
 
121
- ## 限制与冲突
167
+ **`stripTools` 是出厂默认值,不是总开关。** 状态文件存在时以其为准;设为 `false` 不会禁用 `/chattools`,仅改变未切换时的默认行为。需要彻底禁用抑制行为时应卸载本包。
122
168
 
123
- **1. 没有上下文压缩**
124
- 本预设不挂压缩插件,没有自动压缩也不支持 `/compact`。对话一旦超出模型上下文窗口,**直接失败**,不会自动收缩。
169
+ ## 兼容性与脆弱性
125
170
 
126
- **2. 与 `dsh-prompt-setting` 的「我的 Prompt」互斥**
127
- `complete: true` 会吞掉同作用域的其他段。两者不能共存。`dsh-prompt-setting` 自己的实验文档也写了这一点(`core/experiments.js` E3):complete 段生效时,最终装配只剩它一个,监听器改写的顺序会被还原。
171
+ 本包依赖三处非公开行为:
128
172
 
129
- **3. 一个作用域内同名 section 只能注册一次**
130
- 不要在预设里再挂 `dsh-persona`,会撞名抛错。
173
+ | 依赖 | 失效后果 |
174
+ |---|---|
175
+ | agent 作用域同名 context 覆盖全局同名 context | shadow 不生效,工作目录消息恢复 |
176
+ | `required: false` + `text: ''` 使 contribution 被过滤、投影返回 `undefined` | 空字符串被视为内容,请求中多出一条空 `runtime-context` |
177
+ | `buildRequest()` 在 `tools` 为空时省略该键 | 该键变为 `tools: []`,多数模型 API 会拒绝 |
131
178
 
132
- **4. 旧会话不追溯**
133
- 修复前落库的快照仍在。那类会话下次对话会多一条一次性 `Current runtime context: none.`,新会话没有。
179
+ 失效时无报错、无异常,仅退回默认形态。升级 dsh 后应依次确认:bundles 仍然登记(见[排障](#确认生效)第 1 条);新建 ChatOnly 会话的请求中不存在 `tools` 键。
134
180
 
135
- ## 排查
181
+ 开发与测量环境为 DSH 桌面端 `desktop` profile;CLI profile 原理相同,未逐项验证。
136
182
 
137
- `~/.dsh-filter-rtc-debug.log`,一行一个 JSON:
183
+ ## 限制
138
184
 
139
- | 事件 | 含义 |
140
- |---|---|
141
- | `applied` | 插件加载(`stripTools` 值一并记下) |
142
- | `suppress` | 影子已在 agent 作用域注册 |
143
- | `strip-tools` | 本次装配剥掉了 N 个工具(带名字) |
144
- | `prompt-section` | section 注册成功,带文件路径与当前字数 |
145
- | `miss` / `error` | 能力缺失或异常(带 `stage`) |
185
+ 1. **无上下文压缩。** 未挂载压缩插件,不支持自动压缩与 `/compact`。对话超出模型上下文窗口时请求直接失败。
186
+ 2. **与 `dsh-prompt-setting` 的「我的 Prompt」互斥。** `complete: true` 会覆盖同作用域内的其它段(参见该项目 `core/experiments.js` E3),两者不可共存。
187
+ 3. **同一作用域内同名 section 只能注册一次。** 预设内不应再挂载 `dsh-persona`,否则抛错。
188
+ 4. **会话内切换预设不撤销工作目录抑制。** shadow 在 agent 作用域一次性注册且无卸载路径。由 ChatOnly 切换至其它预设后,该会话的 `working-directory:current` 仍为空,直至新建会话。
189
+ 5. **旧会话不追溯。** 修复前已落库的工作目录消息仍然存在;此类会话下次对话会多出一条一次性的 `Current runtime context: none.`。
190
+ 6. **工具开关为全局状态。** 其值为单一文件,`/chattools off` 对所有已打开的 ChatOnly 会话同时生效。工作目录抑制则按 agent 注册且不可撤销。两者机制不同,行为不对称。
146
191
 
147
- 会话侧要看**解压后**的日志(会话文件是 11 帧拼接的 zstd,普通解压只读得到 header)。
192
+ ## 卸载与清理
193
+
194
+ ```bash
195
+ # 1. 从 profile 移除:dependencies 与 dsh.profile.bundles 各删除一条
196
+ # ~/.dsh/profiles/desktop/package.json
197
+ # 随后在该目录执行 pnpm install 并重启 dsh
148
198
 
149
- ### Windows:`ERR_PNPM_EPERM ... rename '<name>_tmp_N_M' -> '<name>'`
199
+ # 2. 系统提示词文件(用户内容,按需保留)
200
+ rm ~/.dsh/chatonly-system-prompt.md
150
201
 
151
- 在同一个 profile 上**反复切换同一个包的数据源**(`link:` ↔ `github:` ↔ 版本号)时会撞到:
202
+ # 3. 工具开关状态
203
+ rm ~/.dsh/chatonly-strip-tools.state
152
204
 
205
+ # 4. 调试日志
206
+ rm ~/.dsh-filter-rtc-debug.log
153
207
  ```
154
- [ERR_PNPM_EPERM] [importPackage ...node_modules\dsh-chat-preset]
155
- EPERM: operation not permitted, rename '...\dsh-chat-preset_tmp_15912_4' -> '...\dsh-chat-preset'
208
+
209
+ `dsh plugin remove`(或桌面端插件页的卸载)**不会**动上面第 2–4 项。它的流程是:从 `dsh.profile.bundles` 摘除 → 卸载运行期 contribution → `pnpm remove` → 重新解析。插件 API 没有卸载钩子,流程也不扫描插件写过哪些文件,因此这几步只能手动执行。
210
+
211
+ 不清理不算残留故障:卸载会 dispose 全部运行期注册(命令、section、监听器、shadow),行为当场回到默认;上述文件此后无人读取,只是留在主目录。唯一的真实影响是**重装沿用旧开关** —— `chatonly-strip-tools.state` 仍在时 `/chattools` 不回出厂默认,而是上次写入的 `strip`/`pass`;要拿到干净初值,先执行 `/chattools reset`。
212
+
213
+ Windows 下 `rm` 不可用:PowerShell 用 `Remove-Item`,cmd 用 `del`。
214
+
215
+ ### 调试日志
216
+
217
+ `index.js` 向 `~/.dsh-filter-rtc-debug.log` 同步追加 JSON 行,记录每次装配、每次工具剥离、每次模式切换与每次 shadow 注册。日志无大小上限与轮转机制,长期使用会持续增长。
218
+
219
+ `{"evt":"strip-tools","count":9,...}` 为抑制生效的记录,可按 `sessionId` 核对具体会话。该日志为调试期脚手架,删除后会在下次触发时重建。
220
+
221
+ ## 确认生效
222
+
223
+ 按序检查,用 `desktop` 举例:
224
+
225
+ ```bash
226
+ # 1. 依赖与 bundles 登记状态(可同时区分"未安装"与"已安装未登记")
227
+ node -e "const os=require('os'),fs=require('fs');const f=os.homedir()+'/.dsh/profiles/desktop/package.json';if(!fs.existsSync(f)){console.log('profile 不存在: '+f);process.exit(0)}const p=JSON.parse(fs.readFileSync(f,'utf8'));console.log('dep :',p.dependencies?.['dsh-chat-preset']??'<未安装>');const b=p.dsh?.profile?.bundles;console.log('bundles:',Array.isArray(b)?(b.includes('dsh-chat-preset')?'已登记':'未登记'):'<无此字段>')"
228
+
229
+ # 2. 依赖树中存在该包(应输出 dsh-chat-preset@<版本>)
230
+
231
+ # Linux / macOS
232
+ dsh plugin --profile desktop ls | grep dsh-chat-preset
233
+
234
+ # Windows(PowerShell,无 grep)
235
+ dsh plugin --profile desktop ls | Select-String dsh-chat-preset
236
+ # Windows(cmd,无 grep)
237
+ dsh plugin --profile desktop ls | findstr dsh-chat-preset
238
+
239
+ # 3. 重启 dsh —— bundles 层仅在启动时合成
156
240
  ```
157
241
 
158
- 原因是 Windows 的 `MoveFileEx` **不能** rename 覆盖一个已存在的目录(空目录也不行),而 POSIX 允许——
159
- 所以这个错几乎只在 Windows 上出现。pnpm 的 import 是「临时目录 → rename」,目标还在就卡死。
242
+ 前两步仅确认安装状态,第三步之后才涉及生效。Windows 用户以 git-bash 执行时可沿用 `grep` 写法。
160
243
 
161
- 绕过:**先把目标条目摘掉,再执行 add。**
244
+ | 第 1 步输出 | 结论 |
245
+ |---|---|
246
+ | `dep: <未安装>` | 依赖未写入,重新安装 |
247
+ | `dep: ^1.2.0` + `bundles: 未登记` | 已安装但未进入 layer stack,手动补充 `dsh.profile.bundles` |
248
+ | 两项均正常,但 ChatOnly 不在预设列表 | 未重启,或重启的 profile 不符 |
249
+
250
+ > `dsh --profile desktop --dump-config` 会报 *profile "desktop" is managed exclusively by the Electron application*。桌面端 profile 不允许 CLI 渲染配置树;如需检查合成结果,可在 CLI 管理的 profile(`web` / `headless`)中安装一次。
162
251
 
163
- ```powershell
164
- # 摘链。只删链接本身,不会动到链接指向的仓库
165
- (Get-Item 'C:\Users\<you>\.dsh\profiles\<profile>\node_modules\<pkg>' -Force).Delete()
252
+ ## 开发
253
+
254
+ ```bash
255
+ node test.mjs
166
256
  ```
167
257
 
168
- > 用 `.Delete()` 而不是 `rm -rf`:前者对 reparse point 语义明确(只摘链)。实测 Node 的
169
- > `fs.rmSync(recursive)` 和 Git Bash 的 `rm -rf` 在这台机器上都不会跟进目标,但不同 MSYS /
170
- > 文件系统驱动版本行为不一致,没有必要冒险。
258
+ 25 项测试,全部通过。基于 `@deepseek-ai/dsh-system-prompt` 与 `agent-loop step()` 的等价模型,覆盖作用域隔离、预设判定、幂等性、能力缺失时的降级、工具瀑布的两种模式(抑制清空/放行后其它插件的增删可见)以及 `/chattools` 的切换语义。仅依赖 `node:` 内置模块。
171
259
 
172
- 然后正常 `dsh plugin --profile <p> add <spec>` 即可。
260
+ 测试使用等价装配模型而非 DSH 本体;真实链路仍以新建 ChatOnly 会话检查请求为准。
173
261
 
174
- ## 测试
262
+ CI 在 Node 18/20/22 上运行同一套测试(`.github/workflows/ci.yml`)。
263
+
264
+ ## 发布
265
+
266
+ 推版本标签即触发 npm 发布(`.github/workflows/publish.yml`):
175
267
 
176
268
  ```bash
177
- node test.mjs # 11 项:作用域隔离、预设判定、幂等、降级
269
+ npm version patch # 或 minor / major,同时打 tag(如 v1.2.0)
270
+ git push --follow-tags
178
271
  ```
179
272
 
180
- 不依赖任何第三方包,只用到 `node:` 内置模块,clone 下来直接能跑。
273
+ 工作流依次执行:升级 npm(OIDC 需 ≥ 11.5.1)→ 测试 → 校验 tag 与 `package.json` 版本一致 → `npm publish --access public --provenance`。
274
+
275
+ 发布使用 npm **Trusted Publishing**(OIDC),不需要任何仓库 Secret。npm 侧需为该包登记 publisher:GitHub 仓库 `Xxx-Bin/dsh-chat-preset`、workflow 文件名 `publish.yml`;两者之一对不上,认证即失败。工作流内 `NODE_AUTH_TOKEN` 被显式置空——`actions/setup-node` 会注入占位值,非空时会压过 OIDC。
181
276
 
182
- 另有一套 17 项的集成测试,跑的是真实 `cordis` / `dsh-scope` / `dsh-system-prompt`(验证 `step()` 请求路径、工具剥离、`section` 热读)。它需要从 dsh 桌面端 `app.asar` 里抽出的 `@deepseek-ai/*`,**无法在仓库内独立运行**,因此不随仓库发布。
277
+ > 本机 `~/.npmrc` 若将 registry 指向镜像(如 `registry.npmmirror.com`),手动发布需显式指定 `--registry https://registry.npmjs.org`;工作流的 `setup-node` 已固定为官方源。
183
278
 
184
279
  ## License
185
280
 
package/cordis.patch.yml CHANGED
@@ -6,7 +6,11 @@
6
6
  # - shadows the required `working-directory:current` contribution, so the
7
7
  # runtime-context snapshot never reaches the request or the session log;
8
8
  # - clears `assembly.tools`, because global tool providers are collected for
9
- # every preset and a preset row cannot remove them;
9
+ # every preset and a preset row cannot remove them. That interception is a
10
+ # LIVE switch, not this file: `stripTools` is only the factory default, the
11
+ # current value lives in `toolsStateFile` and is flipped with
12
+ # `/chattools on|off`. With the switch off the assembly is passed through
13
+ # untouched, so other plugins own the tool list again;
10
14
  # - registers `/chatprompt`, the composer-side editor for the prompt file.
11
15
  #
12
16
  # Row 2 (`preset-chat`) defines the preset. Its `plugins` list mounts THIS
@@ -27,14 +31,15 @@
27
31
  role: 'policy'
28
32
  presetId: 'chatonly'
29
33
  contextName: 'working-directory:current'
30
- stripTools: true
34
+ stripTools: true # 出厂默认;运行中用 /chattools 切
35
+ toolsStateFile: '~/.dsh/chatonly-strip-tools.state' # 当前开关值(strip/pass),缺省即默认
31
36
  file: '~/.dsh/chatonly-system-prompt.md'
32
37
  - id: preset-chat
33
38
  name: '@deepseek-ai/dsh-agent-preset'
34
39
  config:
35
40
  id: chatonly
36
41
  name: ChatOnly
37
- description: '纯对话:无工具无技能;系统提示词来自 ~/.dsh/chatonly-system-prompt.md(留空则仅发送用户输入)'
42
+ description: '纯对话:默认不注入工具与工作区上下文;系统提示词来自 ~/.dsh/chatonly-system-prompt.md(留空则仅发送用户输入);/chattools 可放行工具'
38
43
  order: 5
39
44
  plugins:
40
45
  - id: prompt