dsh-speak 1.8.1 → 1.8.2

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/docs/DESIGN.md CHANGED
@@ -140,20 +140,31 @@ no "reply finished" hook, so the plugin observes the session event stream:
140
140
  - **Optional event announcements** (1.6.0, all off by default): `turn/end`,
141
141
  `command/done`, `goal/change`, `tool/result` (on error), and `todo/write` each
142
142
  have an independent toggle and announce a fixed phrase on fire (see §5).
143
- - **Settings namespace registration** (1.6.0): the plugin wires its namespace
144
- through the settings *service* — `ctx.inject(['settings'])` →
145
- `settings.register('dsh-speak', schema, { base: patchConfig })` → re-derive
146
- `cfg` from `scope.get()` on every `scope.watch` notification, and restore the
147
- composed patch config when the fiber unloads. Resolution stays schema default
148
- → patch `config` → UI user layer.
149
- The plugin never imports `@deepseek-ai/dsh-settings`: DSH 0.1.2-alpha.1 deleted
150
- the `installSettingsSection` / `settingsNamespace` helpers, and referencing
151
- them is fatal — a missing named export is a module-evaluation error, and the
152
- old lazy call threw `settingsNamespace is not a function` inside a timer
153
- callback, which crashed the host (dsh exited 1 instead of booting). The service
154
- itself never changed. On hosts without a settings service the inject callback
155
- never runs and the plugin works purely from the patch config — graceful
156
- degradation with no version check.
143
+ - **Settings form** (1.6.0, reworked in 1.8.2 for DSH 0.1.7): the plugin exports a
144
+ static `Config` schema (`module.exports.Config`, every field `.volatile()`), and
145
+ the settings service projects it into the settings UI. The entry's **Loader id
146
+ is the settings namespace** (`dsh-speak` in `cordis.patch.yml`), so there is
147
+ nothing to register: `ctx.settings.describe()` reads the schema off the active
148
+ entry, and the browser binds the same id through `ctx.configForms`. A write
149
+ commits the new values into the running fiber's config *references* and emits
150
+ `loader/volatile-update`; the plugin re-derives `cfg` there, which is what makes
151
+ an edit audible without a restart. Resolution stays schema default → patch
152
+ `config` → UI user layer.
153
+ The schema must exist at module load: the Loader reads
154
+ `module.exports.Config` right after importing the plugin, before any context
155
+ exists, so it is built with `createRequire(__filename)` instead of the old
156
+ `ctx.baseUrl` fallback. When the schemastery peer cannot be resolved, `Config`
157
+ is `undefined`, the entry simply owns no settings page, and the plugin keeps
158
+ running on the composed patch config — graceful degradation with no version
159
+ check. `settings.configure({ auto: false }, ctx.fiber)` opts the entry out of
160
+ the shell's automatically generated form, because the browser half ships a
161
+ hand-written page.
162
+ The plugin still never imports `@deepseek-ai/dsh-settings`: DSH 0.1.2-alpha.1
163
+ deleted the `installSettingsSection` / `settingsNamespace` helpers (referencing
164
+ them is fatal — a missing named export is a module-evaluation error, and the old
165
+ lazy call threw `settingsNamespace is not a function` inside a timer callback,
166
+ which crashed the host), and 0.1.7 then deleted the
167
+ `settings.register(ns, schema, { base })` service API that had replaced them.
157
168
 
158
169
  Registration snippet (also automated by `install.ps1`; npm installs use the bare
159
170
  package name `'dsh-speak'` — this is the file-install path):
@@ -161,14 +172,60 @@ package name `'dsh-speak'` — this is the file-install path):
161
172
  ```yaml
162
173
  # ~/.dsh/profiles/web/cordis.patch.yml
163
174
  - insert:
164
- - id: speech-hook
175
+ - id: dsh-speak
165
176
  # replace <your-username> with your Windows username
166
177
  name: 'file:///C:/Users/<your-username>/.dsh/profiles/web/plugins/speech-hook.js'
178
+ - id: dsh-speak
179
+ name: 'file:///C:/Users/<your-username>/.dsh/profiles/web/plugins/speech-hook.js'
180
+ config: {}
167
181
  ```
168
182
 
169
183
  > Node's ESM loader does not accept Windows absolute paths as plugin names — the
170
184
  > `file:///C:/...` URL form is required.
171
185
 
186
+ > **Two rows, and the shape is load-bearing.** The `insert` row provides the entry;
187
+ > the TOP-LEVEL row is the one the settings page persists into.
188
+ > `config-editor.edit()` rewrites a `config` in place only after finding a
189
+ > top-level, non-insert row with the same id+name (`document.setIn([index,
190
+ > "config"], …)`), and its `inherited()` helper strips `config` from exactly those
191
+ > rows to compute the base layer. A `config` nested inside the `insert` row — the
192
+ > shape 1.8.x profiles carry, and the obvious-looking one — is not addressable that
193
+ > way: the editor appends a fresh top-level row, that generation's
194
+ > `reconcileProfilePatches` runs, and the write is then rolled back. Observed
195
+ > symptom: the settings page answers `ok: true`, the running plugin obeys the edit
196
+ > immediately (the volatile refs are committed), the patch file flickers
197
+ > `3874 → 4345 → 3874` bytes, and the option silently reverts on the next boot.
198
+ > With the two-row shape the same edit lands in-place in ~0.3 s
199
+ > (`scripts/settings-ui-check.py` asserts the persistence).
200
+
201
+ > **Entry id = settings namespace.** Before 1.8.2 the id was `speech-hook` and the
202
+ > namespace was registered in code, so the two were independent; every 1.8.x
203
+ > profile patch therefore still says `speech-hook`. The browser half accepts both
204
+ > ids (`SETTINGS_NAMESPACES`) and binds whichever the Host serves, so upgrading
205
+ > needs no profile edit — only the *documented* id changed, and with it the key a
206
+ > fresh install stores its options under.
207
+
208
+ > **One entry per profile.** Two rows mounting this plugin (usually the bundle
209
+ > entry plus a leftover hand-written insert) would run two speech queues and
210
+ > double every announcement. `apply()` claims the process with a
211
+ > `Symbol.for('dsh-speak.active')` flag on `globalThis` (not a module variable: two
212
+ > rows may spell the same file differently, and Node would then evaluate the module
213
+ > twice, each copy seeing its own flag); the extra instance logs and stays inert,
214
+ > and the claim is released when the owning fiber disposes so a surviving row can
215
+ > take over.
216
+ >
217
+ > The guard covers **speech only**, and a duplicate entry id has a second,
218
+ > nastier consequence this half cannot fix: `config-editor.entries()` keeps only
219
+ > uniquely-ided entries (`counts.get(entry.options.id) === 1`), and its sanity
220
+ > check composes the candidate patches and compares `find(row => row.id === …)`
221
+ > — the FIRST row for that id — against the value it is writing. With the id
222
+ > present twice that comparison can never match, so **every settings write is
223
+ > refused** with `settings/rejected: Configuration for "dsh-speak" is overridden by
224
+ > a home patch or command-line overlay`, while speech keeps working normally. That
225
+ > is why the installer, the two READMEs and this note all insist on exactly one
226
+ > registration path (bundle entry **or** hand-written rows, never both), and why
227
+ > the log line for the duplicate names the settings symptom too.
228
+
172
229
  ### 3.4 DSH browser half — `client/client.js`
173
230
 
174
231
  A DSH client bundle (`window.__ModuleLoader__.load({ id: 'dsh-speak', factory })`)
@@ -191,8 +248,13 @@ that registers two pieces of UI:
191
248
  (Button / DisclosureRow / Input; Toggle / Options / SettingInput helpers). Every
192
249
  option (master switch, automatic speech, queueAllMessages, Markdown cleaning,
193
250
  code blocks, maxChars, longTextMode, fixed prompt, approvals/questions, the five
194
- optional events) is read/written through `settingsScope.bind({ namespace:
195
- 'dsh-speak' })`.
251
+ optional events) is read/written through `ctx.configForms.get(entryId)` — a
252
+ snapshot (`status` / `value` / `writable`) plus `subscribe` / `set` / `unset`,
253
+ the DSH 0.1.7 replacement for `settingsScope.bind({ namespace })`. The form is
254
+ bound inside `ctx.configForms.whileServed(SETTINGS_NAMESPACES, …)`, so the page
255
+ exists exactly while the Host actually serves one of those namespaces (and the
256
+ bound `entryId` is the one it serves); `dsh-speak` is the documented id and
257
+ `speech-hook` the pre-1.8.2 one.
196
258
 
197
259
  - The package declares its browser half via `package.json`
198
260
  `dsh.client: { platform: 'web' }` + `exports['./client']`; DSH's client-modules
@@ -287,8 +349,10 @@ config:
287
349
 
288
350
  Resolution order: schema default → patch `config` (base) → UI user layer. The
289
351
  browser dsh-speak settings page (`client/client.js`) and the patch YAML read/write
290
- the same settings document. Platform note: `maxChars` defaults to 0 on macOS
291
- (`say` has no ceiling) and 300 on Windows (SAPI safe limit).
352
+ the same profile patch: a UI write is a field operation (`set` / `unset` path op)
353
+ the settings service persists into the entry's `config` block. Platform note:
354
+ `maxChars` defaults to 0 on macOS (`say` has no ceiling) and 300 on Windows (SAPI
355
+ safe limit).
292
356
 
293
357
  Full configuration guide: the README's Configuration section.
294
358
 
@@ -353,8 +417,8 @@ dsh-speak` alone is sufficient — no separate copying step.
353
417
 
354
418
  ### Host requirement (`engines.dsh`)
355
419
 
356
- `package.json` declares `engines.dsh: >=0.1.5-rc.1`. That one field is what
357
- dsh-market reads to label the catalog card (`DSH >=0.1.5-rc.1`) and to decide
420
+ `package.json` declares `engines.dsh: >=0.1.7-rc.2`. That one field is what
421
+ dsh-market reads to label the catalog card (`DSH >=0.1.7-rc.2`) and to decide
358
422
  whether the plugin survives its "compatible with current DSH" filter:
359
423
 
360
424
  - the facts come from the package's npm `latest` manifest
@@ -365,7 +429,7 @@ whether the plugin survives its "compatible with current DSH" filter:
365
429
  (`@deepseek-ai/schemastery`, `@deepseek-ai/cordis` — the schemastery peer above
366
430
  therefore declares nothing about the DSH version);
367
431
  - all declarations are conjunctive and compared prerelease-aware, so
368
- `>=0.1.5-rc.1` matches a `0.1.5-rc.1` host; a missing declaration shows up as
432
+ `>=0.1.7-rc.2` matches a `0.1.7-rc.2` host; a missing declaration shows up as
369
433
  "host requirement undeclared", never as "incompatible";
370
434
  - npm itself only enforces `engines.node` / `engines.npm`, so this key never
371
435
  blocks an install — it is marketplace metadata;
@@ -392,7 +456,7 @@ npm publish # publishConfig.registry pins
392
456
  dsh plugin --profile web add dsh-speak
393
457
  # then register in ~/.dsh/profiles/web/cordis.patch.yml:
394
458
  # - insert:
395
- # - id: speech-hook
459
+ # - id: dsh-speak
396
460
  # name: 'dsh-speak'
397
461
  # restart the DSH web app
398
462
  ```
@@ -122,17 +122,26 @@ Agent 工具会跑长任务(构建、测试、迁移、批量修改),而
122
122
  事件;开 = 每条 assistant 消息立即入队朗读(中间消息也读)。
123
123
  - **可选事件播报**(1.6.0,默认全关):`turn/end`、`command/done`、
124
124
  `goal/change`、`tool/result`(出错时)、`todo/write` 各自独立开关(见 §5)。
125
- - **settings namespace 注册**(1.6.0):插件通过 settings **服务**接线——
126
- `ctx.inject(['settings'])` → `settings.register('dsh-speak', schema, { base:
127
- patchConfig })` → 每次 `scope.watch` 通知时从 `scope.get()` 重新解析 cfg,
128
- fiber 卸载时恢复成组合层的 patch config。配置解析仍是 schema 默认 → patch
129
- `config` → UI 用户设置三层。
130
- 插件**不再 import** `@deepseek-ai/dsh-settings`:DSH 0.1.2-alpha.1 删除了
131
- `installSettingsSection` / `settingsNamespace` 两个辅助导出,引用它们是致命
132
- 的——缺失的具名导出会在模块求值期直接报错;旧代码在 timer 回调里懒调用,抛
133
- `settingsNamespace is not a function` 把宿主打崩(dsh 直接退出 1,起不来)。
134
- 服务本身从未变过。宿主没有 settings 服务时 inject 回调永不执行,插件完全按
135
- patch `config` 工作——优雅退化,无需版本判断。
125
+ - **设置表单**(1.6.0;1.8.2 为 DSH 0.1.7 重做):插件导出一份静态 `Config`
126
+ schema(`module.exports.Config`,每个字段都 `.volatile()`),由 settings 服务
127
+ 投影成设置界面。条目的 **Loader id 就是 settings namespace**
128
+ (`cordis.patch.yml` 里的 `dsh-speak`),所以没有任何需要注册的东西:
129
+ `ctx.settings.describe()` 直接从已激活条目上读 schema,浏览器端用
130
+ `ctx.configForms` 绑定同一个 id。写入会把新值提交进运行中 fiber 的 config
131
+ **引用**并发出 `loader/volatile-update`,插件在那里重新解析 `cfg`——这就是
132
+ "改设置无需重启即刻生效"的实现。解析顺序仍是 schema 默认 → patch `config` →
133
+ UI 用户设置。
134
+ schema 必须在模块加载时就存在:Loader 在 import 之后、任何 context 存在之前
135
+ 就要读 `module.exports.Config`,所以它用 `createRequire(__filename)` 构建,而
136
+ 不是旧的 `ctx.baseUrl` 回退。schemastery peer 解析不到时 `Config` 为
137
+ `undefined`,条目就没有设置页,插件继续按组合层 patch config 工作——优雅退化,
138
+ 无需版本判断。`settings.configure({ auto: false }, ctx.fiber)` 让条目退出 shell
139
+ 的自动生成表单(浏览器端自带手写页面)。
140
+ 插件仍然**不 import** `@deepseek-ai/dsh-settings`:DSH 0.1.2-alpha.1 删除了
141
+ `installSettingsSection` / `settingsNamespace` 两个辅助导出(引用它们是致命的
142
+ ——缺失的具名导出会在模块求值期直接报错;旧代码在 timer 回调里懒调用,抛
143
+ `settingsNamespace is not a function` 把宿主打崩),0.1.7 又删掉了接替它们的
144
+ `settings.register(ns, schema, { base })` 服务 API。
136
145
 
137
146
  注册片段(`install.ps1` 也会自动完成;npm 安装用裸包名 `'dsh-speak'` 即可,
138
147
  这是文件安装方式用的路径):
@@ -140,14 +149,48 @@ Agent 工具会跑长任务(构建、测试、迁移、批量修改),而
140
149
  ```yaml
141
150
  # ~/.dsh/profiles/web/cordis.patch.yml
142
151
  - insert:
143
- - id: speech-hook
152
+ - id: dsh-speak
144
153
  # 把 <your-username> 换成你的 Windows 用户名
145
154
  name: 'file:///C:/Users/<your-username>/.dsh/profiles/web/plugins/speech-hook.js'
155
+ - id: dsh-speak
156
+ name: 'file:///C:/Users/<your-username>/.dsh/profiles/web/plugins/speech-hook.js'
157
+ config: {}
146
158
  ```
147
159
 
148
160
  > Node 的 ESM 加载器不接受 Windows 绝对路径作为插件名——必须用
149
161
  > `file:///C:/...` URL 形式。
150
162
 
163
+ > **必须两行,而且这个形状是承重的。** `insert` 行负责提供条目,**顶层行**才是设置页
164
+ > 写入的目标:`config-editor.edit()` 只有找到同 id+name 的顶层非 insert 行,才会用
165
+ > `document.setIn([index, "config"], …)` 就地改写;它的 `inherited()` 也是从这些行里
166
+ > 剥掉 `config` 来算 base 层。把 `config` 嵌在 `insert` 行里(1.8.x 的 profile 就是
167
+ > 这个形状,看起来也最自然)则完全不可寻址:编辑器会追加一个新的顶层行,这一代
168
+ > `reconcileProfilePatches` 跑完,然后这次写入被回滚。实测症状:设置页返回
169
+ > `ok: true`、运行中的插件立刻生效(volatile 引用已提交)、patch 文件在
170
+ > 3874 → 4345 → 3874 字节之间闪一下,然后选项在下次启动时悄悄变回去。改成两行后,
171
+ > 同样的修改约 0.3 秒就地落盘(`scripts/settings-ui-check.py` 会断言这一点)。
172
+
173
+ > **条目 id = settings namespace。** 1.8.2 之前条目 id 是 `speech-hook`,而
174
+ > namespace 是在代码里注册的,两者互不相干;所以每个 1.8.x 的 profile patch
175
+ > 写的都是 `speech-hook`。浏览器端两个 id 都认(`SETTINGS_NAMESPACES`),绑定
176
+ > Host 实际服务的那个,因此升级**不需要改 profile**——变的只是文档里写的 id,
177
+ > 以及全新安装时选项存放的 key。
178
+
179
+ > **一个 profile 只能有一个条目。** 同一插件挂两次(通常是 bundle 条目 + 手写 insert
180
+ > 残留)会跑两个语音队列,每条播报都念两遍。`apply()` 用
181
+ > `Symbol.for('dsh-speak.active')` 挂在 `globalThis` 上占位(不用模块变量:同一个文件
182
+ > 可能被写成两种说明符,Node 会求值两次,各自看到自己的标志),多余的实例只写日志、
183
+ > 不挂载;拥有者 fiber 销毁时会释放占位,让活着的行接管。
184
+ >
185
+ > 这个守卫**只保语音**。重复的条目 id 还有一个这一半修不了的后果:
186
+ > `config-editor.entries()` 只保留 id 唯一的条目(`counts.get(id) === 1`),而它的
187
+ > 自检会把候选 patch 组合起来、拿 `find(row => row.id === …)`(**第一个**同 id 行)
188
+ > 和它正要写入的值比较——id 出现两次时这个比较永远不相等,于是**每一次设置写入都被拒**:
189
+ > `settings/rejected: Configuration for "dsh-speak" is overridden by a home patch or
190
+ > command-line overlay`,而语音照常工作。所以安装脚本、两份 README 和这里都强调
191
+ > **只能有一种注册方式**(bundle 条目 **或** 手写行,不能两者都有),重复实例的日志行
192
+ > 里也会点名这个设置侧的症状。
193
+
151
194
  ### 3.4 DSH 浏览器端 — `client/client.js`
152
195
 
153
196
  一个 DSH client bundle(`window.__ModuleLoader__.load({ id: 'dsh-speak',
@@ -167,8 +210,12 @@ factory })`),注册两条 UI:
167
210
  用 `@deepseek-ai/dsh-client-ui-primitives` 的 Button/DisclosureRow/Input
168
211
  绘制(Toggle/Options/SettingInput 组件),所有配置项(总开关、自动朗读、
169
212
  queueAllMessages、Markdown 清洗、代码块、maxChars、longTextMode、固定提示语、
170
- 审批/提问、5 类可选事件)都通过 `settingsScope.bind({ namespace: 'dsh-speak' })`
171
- 读写。
213
+ 审批/提问、5 类可选事件)都通过 `ctx.configForms.get(entryId)` 读写——一份快照
214
+ (`status` / `value` / `writable`)加 `subscribe` / `set` / `unset`,即 DSH
215
+ 0.1.7 用来取代 `settingsScope.bind({ namespace })` 的接口。表单在
216
+ `ctx.configForms.whileServed(SETTINGS_NAMESPACES, …)` 里绑定,所以页面只在 Host
217
+ 确实服务其中之一时存在(绑定的 `entryId` 就是它服务的那个);`dsh-speak` 是
218
+ 文档里的 id,`speech-hook` 是 1.8.2 之前那个。
172
219
 
173
220
  - 包通过 `package.json` 的 `dsh.client: { platform: 'web' }` +
174
221
  `exports['./client']` 声明浏览器端;DSH 的 client-modules 扫描到后自动加载。
@@ -259,8 +306,10 @@ config:
259
306
  ```
260
307
 
261
308
  配置解析顺序:schema 默认值 → patch `config`(base)→ UI 用户设置(user 层)。
262
- 浏览器端 dsh-speak 设置页(`client/client.js`)与 patch YAML 读写同一个 settings
263
- 文档。平台差异:`maxChars` macOS 默认 0(`say` 无上限)、Windows 默认 300。
309
+ 浏览器端 dsh-speak 设置页(`client/client.js`)与 patch YAML 读写同一份 profile
310
+ patch:UI 写入是一组字段操作(`set` / `unset` path op),由 settings 服务落进该
311
+ 条目的 `config` 块。平台差异:`maxChars` macOS 默认 0(`say` 无上限)、Windows
312
+ 默认 300。
264
313
 
265
314
  完整配置指南见 README 的"配置"一节。
266
315
 
@@ -306,8 +355,8 @@ DSH 的插件机制基于 Cordis,官方安装树外插件的路径是
306
355
  - `package.json` — `name: dsh-speak`,`main: adapters/dsh/speech-hook.js`,
307
356
  `files` 白名单精确列出发布内容(插件、`engine/*.ps1`、`install.ps1`、文档、
308
357
  LICENSE)。`prepublishOnly` 会对插件跑 `node --check`。
309
- - 插件入口就是文件安装已用的同一个 CJS 模块(`module.exports = { apply(ctx) }`)
310
- ——发布**不需要改任何代码**。
358
+ - 插件入口就是文件安装已用的同一个 CJS 模块(`module.exports = { apply(ctx) }`,
359
+ 另加 1.8.2 起用于设置表单的静态 `Config` 导出)——发布**不需要改任何代码**。
311
360
 
312
361
  ### 引擎解析(npm 安装 vs 文件安装)
313
362
 
@@ -323,15 +372,15 @@ DSH 的插件机制基于 Cordis,官方安装树外插件的路径是
323
372
 
324
373
  ### 宿主要求(`engines.dsh`)
325
374
 
326
- `package.json` 声明了 `engines.dsh: >=0.1.5-rc.1`。dsh-market 只读这一个字段来标注
327
- 目录卡片(`DSH >=0.1.5-rc.1`)并决定插件能否通过「适配当前 DSH」筛选:
375
+ `package.json` 声明了 `engines.dsh: >=0.1.7-rc.2`。dsh-market 只读这一个字段来标注
376
+ 目录卡片(`DSH >=0.1.7-rc.2`)并决定插件能否通过「适配当前 DSH」筛选:
328
377
 
329
378
  - 数据源是该包 npm `latest` manifest(`{registry}/<pkg>/latest`,缓存约 24 小时)
330
379
  ——`awesome-dsh-plugin` 里那份目录 YAML 没有宿主字段,往那里提 PR 声明不了;
331
380
  - `engines.dsh` 属于 `engine` 声明;同版本线的 `@deepseek-ai/dsh*` **peer** 也算,
332
381
  但非同一版本线的宿主包会被有意跳过(`@deepseek-ai/schemastery`、
333
382
  `@deepseek-ai/cordis`)——所以上面那条 schemastery peer 不构成任何 DSH 版本声明;
334
- - 所有声明取交集,比较时带 prerelease 语义,`>=0.1.5-rc.1` 能匹配 `0.1.5-rc.1`
383
+ - 所有声明取交集,比较时带 prerelease 语义,`>=0.1.7-rc.2` 能匹配 `0.1.7-rc.2`
335
384
  宿主;完全没声明只会显示「未声明宿主要求」,不会显示成不兼容;
336
385
  - npm 本身只强制 `engines.node` / `engines.npm`,这个键不会挡住安装,它只是市场
337
386
  元数据;
@@ -357,7 +406,7 @@ npm publish # publishConfig.registry 已
357
406
  dsh plugin --profile web add dsh-speak
358
407
  # 然后在 ~/.dsh/profiles/web/cordis.patch.yml 注册:
359
408
  # - insert:
360
- # - id: speech-hook
409
+ # - id: dsh-speak
361
410
  # name: 'dsh-speak'
362
411
  # 重启 DSH web 应用
363
412
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-speak",
3
- "version": "1.8.1",
3
+ "version": "1.8.2",
4
4
  "description": "Make your AI harness speak — voice announcements for DSH and other AI coding harnesses (Windows SAPI5 + macOS system voices)",
5
5
  "main": "adapters/dsh/speech-hook.js",
6
6
  "exports": {
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=18",
63
- "dsh": ">=0.1.5-rc.1"
63
+ "dsh": ">=0.1.7-rc.2"
64
64
  },
65
65
  "repository": {
66
66
  "type": "git",
@@ -71,11 +71,6 @@
71
71
  "peerDependencies": {
72
72
  "@deepseek-ai/schemastery": "^3.18.1"
73
73
  },
74
- "peerDependenciesMeta": {
75
- "@deepseek-ai/schemastery": {
76
- "optional": true
77
- }
78
- },
79
74
  "publishConfig": {
80
75
  "registry": "https://registry.npmjs.org"
81
76
  }