dsh-plugin-guide 0.3.23 → 0.3.25
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-es.md +11 -0
- package/README-hi.md +11 -0
- package/README-pt.md +11 -0
- package/README-zh.md +11 -0
- package/README.md +12 -0
- package/dist/dsh-plugin-dev.js +48 -8
- package/guide/migration-0.2.md +3 -3
- package/guide/plugin-dev-guide.md +25 -3
- package/package.json +13 -4
- package/scripts/verify-artifacts.mjs +13 -1
- package/templates/ts/tsdown.config.mjs +6 -0
package/README-es.md
CHANGED
|
@@ -179,6 +179,17 @@ El bundle de skill no expone ningún `Config` de Schemastery — registra la bas
|
|
|
179
179
|
- **`downloads/` es generado, no confirmado.** Las instantáneas crudas (archivos de repos de la comunidad, Discussions, artículos) deben generarse con los scripts antes de usarse.
|
|
180
180
|
- **El contenido de `awesome-dsh-plugins` es solo local.** Su upstream declara una restricción de uso interno, por lo que no se redistribuye con el repo.
|
|
181
181
|
|
|
182
|
+
## Release channels
|
|
183
|
+
|
|
184
|
+
Todos los canales resuelven desde este repositorio o desde npm, así que **una etiqueta llega a todos**; solo el espejo de Gitee es una copia programada:
|
|
185
|
+
|
|
186
|
+
| Canal | Fuente de verdad | Cómo se actualiza |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| npm (`dsh-plugin-guide`) | el `package.json` de este repositorio | `.github/workflows/release.yml` en una etiqueta `v*`: puerta `verify-kit` → comprobación de la sección del CHANGELOG → `npm publish --provenance` (se omite si la versión ya existe) |
|
|
189
|
+
| GitHub Release | la misma etiqueta | el mismo workflow la crea de forma idempotente |
|
|
190
|
+
| Insignias de descubrimiento (versión npm, etiqueta de GitHub, DSH Market, dsh-doctor) | consultas en vivo | no hay nada que publicar: leen npm/el registro en cada render |
|
|
191
|
+
| Espejo de Gitee | este repositorio | **espejo programado de solo lectura**, no un destino de publicación: el workflow `gitee-sync` de la familia lo alinea al tip de GitHub con `--force-with-lease`, así que `main` suele ir una ejecución programada por detrás. **Aquí nunca se empuja una etiqueta a Gitee**; compara con `git ls-remote` antes de reportar el espejo como obsoleto |
|
|
192
|
+
| DSH Desktop Market / dsh1024 | npm | el mercado instala el paquete de npm, así que una versión publicada es instalable de inmediato; no hay envío por versión |
|
|
182
193
|
## Keeping it fresh
|
|
183
194
|
|
|
184
195
|
```sh
|
package/README-hi.md
CHANGED
|
@@ -179,6 +179,17 @@ skill बंडल कोई Schemastery `Config` उजागर नहीं
|
|
|
179
179
|
- **`downloads/` उत्पन्न है, कमिट नहीं।** कच्चे स्नैपशॉट (सामुदायिक रेपो संग्रह, Discussions, लेख) उपयोग से पहले स्क्रिप्ट से उत्पन्न करने चाहिए।
|
|
180
180
|
- **`awesome-dsh-plugins` सामग्री केवल स्थानीय है।** इसका अपस्ट्रीम आंतरिक-उपयोग प्रतिबंध घोषित करता है, इसलिए इसे रेपो के साथ पुनर्वितरित नहीं किया जाता।
|
|
181
181
|
|
|
182
|
+
## Release channels
|
|
183
|
+
|
|
184
|
+
हर channel इस repo या npm से resolve होता है, इसलिए **एक tag सब जगह पहुँच जाता है**; केवल Gitee mirror एक शेड्यूल्ड कॉपी है:
|
|
185
|
+
|
|
186
|
+
| Channel | सत्य-स्रोत | कैसे अपडेट होता है |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| npm (`dsh-plugin-guide`) | इस repo का `package.json` | `v*` tag पर `.github/workflows/release.yml`: `verify-kit` गेट → CHANGELOG में उस संस्करण का section जाँच → `npm publish --provenance` (संस्करण पहले से हो तो छोड़ देता है) |
|
|
189
|
+
| GitHub Release | वही tag | वही workflow इसे idempotent तरीके से बनाता है |
|
|
190
|
+
| Discovery बैज (npm version, GitHub tag, DSH Market, dsh-doctor) | लाइव lookup | प्रकाशित करने को कुछ नहीं — हर render पर npm/registry पढ़ते हैं |
|
|
191
|
+
| Gitee mirror | यह repo | **read-only शेड्यूल्ड mirror**, प्रकाशन लक्ष्य नहीं: परिवार का `gitee-sync` workflow इसे `--force-with-lease` से GitHub tip पर align करता है, इसलिए `main` सामान्यतः एक शेड्यूल्ड रन पीछे रहता है। **यहाँ से Gitee पर tag कभी push नहीं होता**; mirror को पुराना बताने से पहले `git ls-remote` से तुलना करें |
|
|
192
|
+
| DSH Desktop Market / dsh1024 | npm | market npm पैकेज इंस्टॉल करता है, इसलिए प्रकाशित संस्करण तुरंत इंस्टॉल-योग्य है; प्रति-संस्करण सबमिशन नहीं |
|
|
182
193
|
## Keeping it fresh
|
|
183
194
|
|
|
184
195
|
```sh
|
package/README-pt.md
CHANGED
|
@@ -179,6 +179,17 @@ O bundle de skill não expõe nenhum `Config` de Schemastery — ele registra a
|
|
|
179
179
|
- **`downloads/` é gerado, não commitado.** As instantâneas cruas (arquivos de repos da comunidade, Discussions, artigos) devem ser geradas com os scripts antes do uso.
|
|
180
180
|
- **O conteúdo de `awesome-dsh-plugins` é somente local.** Seu upstream declara uma restrição de uso interno, então não é redistribuído com o repo.
|
|
181
181
|
|
|
182
|
+
## Release channels
|
|
183
|
+
|
|
184
|
+
Todos os canais resolvem deste repositório ou do npm, então **uma tag chega a todos**; só o espelho do Gitee é uma cópia agendada:
|
|
185
|
+
|
|
186
|
+
| Canal | Fonte da verdade | Como atualiza |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| npm (`dsh-plugin-guide`) | o `package.json` deste repositório | `.github/workflows/release.yml` numa tag `v*`: portão `verify-kit` → verificação da seção do CHANGELOG → `npm publish --provenance` (ignorado quando a versão já existe) |
|
|
189
|
+
| GitHub Release | a mesma tag | o mesmo workflow a cria de forma idempotente |
|
|
190
|
+
| Selos de descoberta (versão npm, tag do GitHub, DSH Market, dsh-doctor) | consultas ao vivo | nada a publicar — leem o npm/o registro a cada render |
|
|
191
|
+
| Espelho do Gitee | este repositório | **espelho agendado somente leitura**, não um destino de publicação: o workflow `gitee-sync` da família o alinha ao tip do GitHub com `--force-with-lease`, então `main` costuma ficar uma execução atrás. **Aqui nunca se envia tag ao Gitee**; compare com `git ls-remote` antes de reportar o espelho desatualizado |
|
|
192
|
+
| DSH Desktop Market / dsh1024 | npm | o mercado instala o pacote do npm, então uma versão publicada é instalável de imediato; não há envio por versão |
|
|
182
193
|
## Keeping it fresh
|
|
183
194
|
|
|
184
195
|
```sh
|
package/README-zh.md
CHANGED
|
@@ -181,6 +181,17 @@ pwsh -File scripts/install-skill.ps1 -Target ~/.deepseek/skills/dsh-plugin-guide
|
|
|
181
181
|
- **`downloads/` 由脚本生成、不入库。** 原始快照(社区仓库归档、Discussions、文章)使用前需用脚本生成。
|
|
182
182
|
- **`awesome-dsh-plugins` 内容仅本地。** 其上游声明内部使用约束,故不随仓库再分发。
|
|
183
183
|
|
|
184
|
+
## Release channels
|
|
185
|
+
|
|
186
|
+
所有渠道都从本仓库或 npm 解析,因此**推一个 tag 就能到所有渠道**;只有 Gitee 镜像是定时拷贝:
|
|
187
|
+
|
|
188
|
+
| 渠道 | 真源 | 更新方式 |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| npm(`dsh-plugin-guide`) | 本仓库的 `package.json` | `v*` tag 触发 `.github/workflows/release.yml`:`verify-kit` 门禁 → 校验 CHANGELOG 有该版本段 → `npm publish --provenance`(版本已存在则跳过) |
|
|
191
|
+
| GitHub Release | 同一个 tag | 同一 workflow 幂等创建 |
|
|
192
|
+
| 展示徽章(npm 版本、GitHub tag、DSH Market、dsh-doctor) | 实时查询 | 无需发布——每次渲染都读 npm/registry |
|
|
193
|
+
| Gitee 镜像 | 本仓库 | **只读定时镜像**,不是发布目标:由家族仓的 `gitee-sync` workflow 用 `--force-with-lease` 强制对齐 GitHub tip,所以 `main` 通常落后一次定时运行。**这里从不对 Gitee 推 tag**;报"镜像过期"前先用 `git ls-remote` 与 GitHub 对比 |
|
|
194
|
+
| DSH Desktop Market / dsh1024 | npm | 市场装的就是 npm 包,发布即可安装,无需逐版本投稿 |
|
|
184
195
|
## Keeping it fresh
|
|
185
196
|
|
|
186
197
|
```sh
|
package/README.md
CHANGED
|
@@ -189,6 +189,18 @@ The skill bundle exposes no Schemastery `Config` — it registers the knowledge
|
|
|
189
189
|
- **`downloads/` is generated, not committed.** Raw snapshots (community repo archives, Discussions, articles) must be generated with the scripts before use.
|
|
190
190
|
- **`awesome-dsh-plugins` content is local-only.** Its upstream declares an internal-use constraint, so it is not redistributed with the repo.
|
|
191
191
|
|
|
192
|
+
## Release channels
|
|
193
|
+
|
|
194
|
+
Every channel resolves from this repository or from npm, so one tag reaches all of them; only the Gitee mirror is a scheduled copy:
|
|
195
|
+
|
|
196
|
+
| Channel | Source of truth | How it updates |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| npm (`dsh-plugin-guide`) | this repo's `package.json` | `.github/workflows/release.yml` on a `v*` tag: `verify-kit` gate → CHANGELOG section check → `npm publish --provenance` (skipped when the version already exists) |
|
|
199
|
+
| GitHub Release | the same tag | the same workflow creates it idempotently from the tag |
|
|
200
|
+
| Discovery badges (npm version, GitHub tag, DSH Market, dsh-doctor) | live lookups | nothing to publish — they read npm/the registry on each render |
|
|
201
|
+
| Gitee mirror | this repo | a **read-only scheduled mirror**, not a publish target: it is force-aligned to the GitHub tip (`--force-with-lease`) by the family's `gitee-sync` workflow, so `main` is normally one scheduled run behind. Gitee never receives a tag push from here; compare `git ls-remote` against GitHub before reporting the mirror stale |
|
|
202
|
+
| DSH Desktop Market / dsh1024 | npm | the market installs the npm package, so a published version is immediately installable; no per-version submission |
|
|
203
|
+
|
|
192
204
|
## Keeping it fresh
|
|
193
205
|
|
|
194
206
|
```sh
|
package/dist/dsh-plugin-dev.js
CHANGED
|
@@ -140,9 +140,16 @@ function isDir(path) {
|
|
|
140
140
|
* `{{dshPeerRange}}` placeholder. Keep it identical to the canonical range in
|
|
141
141
|
* `dsh-plugin-kit/data/peer-range.json`: one clause per prerelease tuple is
|
|
142
142
|
* required by semver's prerelease rule, and the `-0` floor covers the whole
|
|
143
|
-
*
|
|
143
|
+
* tuple (never a bare `>=0.2.0` form, which semver's prerelease rule would
|
|
144
|
+
* reject for `0.2.1-alpha.1`).
|
|
145
|
+
*
|
|
146
|
+
* The 0.2 line is load-bearing: `dsh` enforces peer ranges at install time and
|
|
147
|
+
* rejects any plugin whose declared range excludes the running runtime, so a
|
|
148
|
+
* range that stops at 0.1.x makes every scaffolded plugin uninstallable on a
|
|
149
|
+
* 0.2.x harness (`installation rejected: ... is incompatible with dsh
|
|
150
|
+
* 0.2.1-alpha.1`).
|
|
144
151
|
*/
|
|
145
|
-
const DSH_PEER_RANGE = ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0";
|
|
152
|
+
const DSH_PEER_RANGE = ">=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0 || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0";
|
|
146
153
|
/** Resolve the templates root: env override first, then candidate walks. */
|
|
147
154
|
function resolveTemplatesRoot() {
|
|
148
155
|
const env = process.env.DSH_PLUGIN_DEV_TEMPLATES;
|
|
@@ -1605,6 +1612,22 @@ async function initGit(targetDir) {
|
|
|
1605
1612
|
|
|
1606
1613
|
//#endregion
|
|
1607
1614
|
//#region src/cli/commands/verify.ts
|
|
1615
|
+
/**
|
|
1616
|
+
* The verified harness line `verify` installs into its throwaway profile.
|
|
1617
|
+
*
|
|
1618
|
+
* Keep these in lockstep with `.github/workflows/compat.yml`, which installs the
|
|
1619
|
+
* same versions. `dsh` rejects an incompatible plugin/base pair at install time,
|
|
1620
|
+
* so a stale pin turns `verify` into a failure that names the *base bundle*
|
|
1621
|
+
* rather than the plugin under test:
|
|
1622
|
+
*
|
|
1623
|
+
* dsh: installation rejected: Plugin @deepseek-ai/dsh-headless@<old> is
|
|
1624
|
+
* incompatible with dsh <new>
|
|
1625
|
+
*
|
|
1626
|
+
* `tests/verify.test.ts` asserts these match compat.yml, so the two cannot drift
|
|
1627
|
+
* apart silently again. Override per run with `--base` / `--headless`.
|
|
1628
|
+
*/
|
|
1629
|
+
const VERIFY_BASE_SPEC = "@deepseek-ai/dsh-base@0.2.1-alpha.1";
|
|
1630
|
+
const VERIFY_HEADLESS_SPEC = "@deepseek-ai/dsh-headless@0.2.1-alpha.1";
|
|
1608
1631
|
const PROFILE_WORKSPACE = `# scratch profile allowlist (mirrors the repo's compat workflow)
|
|
1609
1632
|
packages:
|
|
1610
1633
|
- .
|
|
@@ -1794,21 +1817,38 @@ function suggestionsFor(step, result) {
|
|
|
1794
1817
|
const base = [`tail of "${step}":\n${tailOf(result)}`];
|
|
1795
1818
|
if (result.timedOut) base.push(`command timed out; raise --timeout or --smoke-timeout`);
|
|
1796
1819
|
if (step === "install") {
|
|
1797
|
-
base.push("ensure the dsh CLI is @deepseek-ai/dsh@0.1
|
|
1820
|
+
base.push("ensure the dsh CLI is @deepseek-ai/dsh@0.2.1-alpha.1 (older builds such as the rc.6 line do not satisfy the compat pin)");
|
|
1798
1821
|
base.push("confirm the profile allowlist matches the repo compat workflow (native builds allowlisted)");
|
|
1799
1822
|
}
|
|
1800
1823
|
if (step === "headless") base.push("a hang usually means an injected service stayed pending; the smoke timeout surfaces exactly that");
|
|
1801
1824
|
return base;
|
|
1802
1825
|
}
|
|
1826
|
+
/** Error signatures a headless smoke prints only *after* the tree loaded. */
|
|
1827
|
+
const SMOKE_LOADED_SIGNATURES = [
|
|
1828
|
+
"MISSING_CREDENTIAL",
|
|
1829
|
+
"TRANSPORT:",
|
|
1830
|
+
"transport failed",
|
|
1831
|
+
"UNAUTHORIZED",
|
|
1832
|
+
"invalid api key",
|
|
1833
|
+
"API key is invalid"
|
|
1834
|
+
];
|
|
1803
1835
|
/**
|
|
1804
|
-
* True when a headless smoke run proves the plugin tree loaded
|
|
1805
|
-
*
|
|
1836
|
+
* True when a headless smoke run proves the plugin tree loaded. Three accepted
|
|
1837
|
+
* outcomes, all of which require that the tree mounted and the turn dispatched:
|
|
1838
|
+
* a keyless run printing `MISSING_CREDENTIAL`, a keyed run printing `ok`, or a
|
|
1839
|
+
* keyed run whose provider call failed at the transport/auth layer.
|
|
1840
|
+
*
|
|
1841
|
+
* The third case is not a fallback for a broken plugin: a plugin that never
|
|
1842
|
+
* mounted produces no provider traffic at all, so it prints none of these
|
|
1843
|
+
* signatures and still fails this check.
|
|
1844
|
+
*
|
|
1806
1845
|
* @param stdout - captured stdout.
|
|
1807
1846
|
* @param stderr - captured stderr.
|
|
1808
1847
|
*/
|
|
1809
1848
|
function isSmokeOk(stdout, stderr) {
|
|
1810
1849
|
const text = `${stdout}\n${stderr}`;
|
|
1811
|
-
|
|
1850
|
+
if (SMOKE_LOADED_SIGNATURES.some((signature) => text.includes(signature))) return true;
|
|
1851
|
+
return /(^|[^a-z])ok([^a-z]|$)/i.test(text);
|
|
1812
1852
|
}
|
|
1813
1853
|
/** Resolve the dsh binary: explicit flag/env first, then PATH `dsh`. */
|
|
1814
1854
|
function resolveDsh(flag) {
|
|
@@ -1915,8 +1955,8 @@ async function main(argv, cwd = process.cwd()) {
|
|
|
1915
1955
|
dshBin: resolveDsh(flagString(flags, "dsh")),
|
|
1916
1956
|
pnpmBin: resolvePnpm(flagString(flags, "pnpm")),
|
|
1917
1957
|
profile: flagString(flags, "profile", "compat") ?? "compat",
|
|
1918
|
-
base: flagString(flags, "base", "@deepseek-ai/dsh-base@0.1
|
|
1919
|
-
headless: flagString(flags, "headless", "@deepseek-ai/dsh-headless@0.1
|
|
1958
|
+
base: flagString(flags, "base", "@deepseek-ai/dsh-base@0.2.1-alpha.1") ?? "@deepseek-ai/dsh-base@0.2.1-alpha.1",
|
|
1959
|
+
headless: flagString(flags, "headless", "@deepseek-ai/dsh-headless@0.2.1-alpha.1") ?? "@deepseek-ai/dsh-headless@0.2.1-alpha.1",
|
|
1920
1960
|
timeoutMs: timeoutMs(flags, "timeout", "DSH_PLUGIN_DEV_TIMEOUT", DEFAULT_TIMEOUT_MS),
|
|
1921
1961
|
smokeTimeoutMs: timeoutMs(flags, "smoke-timeout", "DSH_PLUGIN_DEV_SMOKE_TIMEOUT", DEFAULT_SMOKE_TIMEOUT_MS)
|
|
1922
1962
|
});
|
package/guide/migration-0.2.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
| npm `alpha` / `next` | `alpha`=`0.2.1-alpha.1`、`next`=`0.2.0-rc.2` |
|
|
21
21
|
| 区间内发布线(9 个) | `0.1.6-alpha.2`(09-17) → `0.1.7-alpha.1`(09-22) → `0.1.5-rc.3`(09-22) → `0.1.7-alpha.2`(09-22) → `0.1.7-rc.1`(09-23) → `0.1.7-rc.2`(09-24) → `0.2.0-rc.1`(09-28) → `0.2.0-rc.2`(09-29) → `0.2.1-alpha.1`(10-03) |
|
|
22
22
|
| Node 门槛 | `^22.19.0 \|\| >=24.0.0`,**区间内未变**(无 `.nvmrc`/`.node-version` 文件) |
|
|
23
|
-
| `dsh.version` 字段 | **不存在**。DSH 的包清单只有 `dsh.bundle` / `dsh.profile` / `dsh.client`
|
|
23
|
+
| `dsh.version` 字段 | **不存在**。DSH 的包清单只有 `dsh.bundle` / `dsh.profile` / `dsh.client` 三组键 |
|
|
24
24
|
| 官方发布说明在哪 | 仓库里**没有 `CHANGELOG.md`**、也没有 `docs/persistence-changes/releases/*` 新条目,所以逐版本说明只在 GitHub **Releases** 的 body 里(`gh release view <tag>` 或 Releases API)。本文件的每一条都对照过 tag `dsh-v0.1.7-alpha.1` / `dsh-v0.2.0-rc.2` / `dsh-v0.2.1-alpha.1` 的发布说明 |
|
|
25
25
|
|
|
26
26
|
**官方 0.1.7-alpha.1 发布说明里点名的四条插件必改**(原文"其他变更"段):Session 日志升级 V4、工作区文件读取统一为 `readBytes`(旧接口必须迁移)、官方 DeepSeek 适配器**只用 Messages API**(移除 Chat Completions 与 `protocol` 选项)、设置改由当前 Profile 的插件配置保存(旧 `settings.yaml` 只尝试导入一次)。同版本还改了:Agent 预设改由插件组合包声明/安装(旧目录预设需迁移)、组合包支持按序多 patch、插件可声明"无需重载的配置字段"、新增 `--dump-config-schema`、插件可用 locale 声明多语言标题描述 + `package.json` 图标。
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
**npm 陷阱(最贵的一条)**:库包(`dsh-tools`、`dsh-base`、`dsh-headless`、`dsh-session-persistence-jsonl`、`dsh-session`、`dsh-settings`、`dsh-workflow` …)的 `latest` 至今钉在 **`0.0.1-rc.1`**。写 `"@deepseek-ai/dsh-session": "latest"` 会装到远古版本;必须写死版本或钉 `next`/`alpha`。唯一的例外是 CLI 包 `@deepseek-ai/dsh`(`latest`=`0.2.0-rc.2`)。
|
|
31
31
|
|
|
32
|
-
**跨线 peer 范围**:`^0.1.x` 与 `^0.2.x`
|
|
32
|
+
**跨线 peer 范围**:`^0.1.x` 与 `^0.2.x` 互不覆盖,要同时支持两条线必须写两侧范围。⚠️ **peer 范围会被安装期强制**(本节早期版本写成"DSH 不校验范围",已更正):`dsh plugin add` 会拿运行时的版本去比插件声明的 peer 范围,不匹配就**拒绝安装**并把声明的范围原样打印出来。跨线支持因此必须**在范围里同时列出两侧的 prerelease 元组**(每个元组一个 clause),否则 0.2.x 宿主装不上——详见 [plugin-dev-guide.md](plugin-dev-guide.md) §7.3 的 `dsh-plugin` 段。范围之外仍要配**运行时特性探测**,因为范围只能表达"能不能装",表达不了"某个 API 在不在"。
|
|
33
33
|
|
|
34
34
|
## 2. 会话格式 V3 → V4(影响最深,涉及历史日志)
|
|
35
35
|
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
- `dsh.bundle.patch` 现在接受**有序数组**:`{ "patch": ["./base.patch.yml", "./web.patch.yml"] }`,按序作为同一层应用,且每个文件里的相对插件路径**相对该文件所在目录**解析。校验失败信息:`dsh.bundle.patch must be a file path or a list of file paths`。
|
|
86
86
|
- 层顺序未变:`dsh.profile.bundles` 顺序 → profile 自己的 `cordis.patch.yml` → `$DSH_HOME/cordis.patch.yml` → 各 `--patch`(argv 顺序)。**按 id 覆盖时整段 `config` 被替换,不做深合并。**
|
|
87
|
-
- 包清单还可以声明 `dsh.manifestVersion`(格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh
|
|
87
|
+
- 包清单还可以声明 `dsh.manifestVersion`(格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh`(作者声明的兼容范围,**当前安装器/加载器都不强制**——真正会被强制的是 `@deepseek-ai/dsh-*` 的 **peerDependencies**,安装期即拒绝不匹配者,见 [plugin-dev-guide.md](plugin-dev-guide.md) §7.3)。类型定义见 `@deepseek-ai/dsh-package-manifest`。
|
|
88
88
|
- 展示元数据契约(Plugin Manager / Settings 在**不激活插件**的前提下读取):包根读 `package.json` 的 `name`/`description`/`icon`,或导出 `./locale/*.json` 的 `meta.title`/`meta.description` 与 `./icon`;图标支持 SVG/PNG/JPEG/WebP,**≤ 256 KiB 且必须落在包内**(绝对路径、URL、指向包外的 symlink 都被拒)。子路径插件**永远不读** `<subpath>/package.json`——标题/描述走 `locale/*.json` 的 `meta.*`,图片走导出的 `<subpath>/icon`。
|
|
89
89
|
- `dsh.client` 键**未变**:`platform`(web)、`inject`(仅排序,不是 cordis 服务注入)、`immediately`(一阶段预取)、`external`(基线与注入边之外的精确模块请求)。但客户端半侧只挂在**说明符恰为裸包名**的那一行上:把一个包拆成多行的组合包,其 UI 只随根行启停。
|
|
90
90
|
|
|
@@ -76,7 +76,7 @@ export function apply(ctx: Context) {
|
|
|
76
76
|
- **bundle(分发层)**:npm 包,`package.json` 声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`;patch 里是"插入/覆盖插件行"的 YAML 数组。**`patch` 也可以是有序数组**:`{ "patch": ["./base.patch.yml", "./web.patch.yml"] }`,按序作为同一层应用,且每个文件里的相对插件路径**相对该文件所在目录**解析(校验失败信息:`dsh.bundle.patch must be a file path or a list of file paths`)。
|
|
77
77
|
- **profile(可运行组合)**:`$DSH_HOME/profiles/<name>/`,`package.json` 声明 `"dsh": { "profile": { "bundles": [...] } }`;由 `dsh plugin` / `plugin_manager` 维护,手写不允许。
|
|
78
78
|
- **client(浏览器半侧)**:`package.json` 声明 `"dsh": { "client": { "platform": "web", "inject": [...], "immediately": true, "external": [...] } }` 并导出编译好的 `./client`。`inject` **只决定激活顺序**(不是 cordis 服务注入),`immediately` 标记一阶段预取,`external` 列出基线之外的精确模块请求。客户端半侧只挂在**说明符恰为裸包名**的那一行上。
|
|
79
|
-
- 另有两个可选作者字段:`dsh.manifestVersion`(清单格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh
|
|
79
|
+
- 另有两个可选作者字段:`dsh.manifestVersion`(清单格式标识,当前 `1`,与 npm 版本、会话格式版本无关)与 `engines.dsh`(兼容范围声明,**当前安装器/加载器都不校验**;会被校验的是 `@deepseek-ai/dsh-*` 的 peerDependencies,见 §7.3)。类型定义来自 `@deepseek-ai/dsh-package-manifest`。
|
|
80
80
|
|
|
81
81
|
bundle 最小结构:
|
|
82
82
|
|
|
@@ -394,6 +394,7 @@ Plugin Manager 与 Settings 会**在不激活插件的前提下**读展示信息
|
|
|
394
394
|
- `files` 必须包含 patch、locale、图标与**每个运行时文件**——但**本地目录安装是链接 checkout,`files` 不过滤链接目录**,要直接核对文件;
|
|
395
395
|
- **子路径插件(`my-plugins/search`)不是包,永远不读 `package.json`**:标题/描述走 `my-plugins/search/locale/*.json` 的 `meta.*`,图片走导出的 `my-plugins/search/icon`。
|
|
396
396
|
- 可选作者字段:`dsh.manifestVersion`(当前 `1`)与 `engines.dsh`(**当前不校验**,只作声明)。类型来自 `@deepseek-ai/dsh-package-manifest`。
|
|
397
|
+
- **真正会被强制的是 peerDependencies**(见 §7.3):`dsh plugin add` 会用运行时的版本比对插件声明的 `@deepseek-ai/dsh-*` 范围,不匹配即**拒绝安装**。
|
|
397
398
|
|
|
398
399
|
### 7.1 从 GitHub 安装的 build-script 坑
|
|
399
400
|
|
|
@@ -420,7 +421,28 @@ bundle 挂一个普通 provider 插件:`inject = ['cmdlineArgs']`,用 `@deep
|
|
|
420
421
|
- **cordis 双副本 / 双 Cordis 分裂**:插件构建时若从 `.pnpm` 副本解析 cordis,与 harness 的 vendored 副本是"两个模块",`declare module` 增强合并不了 → 报 `Property 'tools' does not exist on type 'Context'`。构建期把 cordis 解析到 harness 的 `vendor/cordis`;npm 安装路径下 peer 必须与宿主同一身份——**scoped `@deepseek-ai/cordis` 与 unscoped `cordis` 混用同样分裂**(dsh-tools 的类型只增强 scoped 版本)。独立包把 cordis 设为 peerDependency(+ dev),版本对齐宿主。
|
|
421
422
|
- **官方 `@deepseek-ai/*` 包曾未发布公共 npm**(rc 早期):社区 bundle 的 `dependencies` 留空,靠 profile 的 pnpm 闭包 flat fallback(`$DSH_HOME/profiles/node_modules`)注入;声明了反而解析失败。rc.6 起公开包可用(from-scratch 教程锁 `0.1.0-rc.6`、cordis `4.0.1`),两条时间线的资料都要知道,按当时宿主版本取舍。
|
|
422
423
|
- **npm `latest` 标签是过期版本**:库包(`dsh-tools`、`dsh-base`、`dsh-headless`、`dsh-session-persistence-jsonl`、`dsh-settings`、`dsh-workflow` …)的 `latest` **至今**钉在 `0.0.1-rc.1`,所以写 `"latest"` 会装到远古版本,必须写死版本或钉 `next`/`alpha`。**2026-10-06 复核**:`@deepseek-ai/dsh` `latest`=`next`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.1`;dsh-tools / dsh-base / dsh-headless / dsh-session-persistence-jsonl 的 `latest` 仍 `0.0.1-rc.1`、`next`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.1`;`@deepseek-ai/cordis` `latest`=4.0.4 并有新标签 `dsh-0-2-1-alpha-1`=4.0.5-alpha.1;`@deepseek-ai/schemastery` `latest`=3.18.4;`create-dsh-plugin` `latest`=0.2.3;dsh-core、dsh-sdk 仍 404。历史两次复核(2026-08-14 与 09-04)的数值保留在上一条记录里,可见"`latest` 陷阱"从 rc 早期持续到 0.2 世代。**无作用域 `dsh` 包是无关项目 node-dsh**("A shell written in JavaScript")——官方 CLI 包是 `@deepseek-ai/dsh`,别装错。
|
|
423
|
-
- **profile 内安装的 `@deepseek-ai/*` 会静默遮蔽运行时自带版本**(解析优先级 `profile > runtime`):把共享实例的 dsh 包写进 **dependencies** 是错的做法——pnpm 会把它装进 profile,盖掉宿主自带的版本,然后核心服务注册失败(实测 `settings service is absent: mount @deepseek-ai/dsh-settings with @deepseek-ai/dsh-config-editor`,桌面端每次崩溃)。**共享实例的 dsh 包只放 `peerDependencies` + `devDependencies`**;独立版本演进或纯工具型包才放 `dependencies
|
|
424
|
+
- **profile 内安装的 `@deepseek-ai/*` 会静默遮蔽运行时自带版本**(解析优先级 `profile > runtime`):把共享实例的 dsh 包写进 **dependencies** 是错的做法——pnpm 会把它装进 profile,盖掉宿主自带的版本,然后核心服务注册失败(实测 `settings service is absent: mount @deepseek-ai/dsh-settings with @deepseek-ai/dsh-config-editor`,桌面端每次崩溃)。**共享实例的 dsh 包只放 `peerDependencies` + `devDependencies`**;独立版本演进或纯工具型包才放 `dependencies`。
|
|
425
|
+
- ⚠️ **peer 范围会被安装期强制**(本节早期版本写成"DSH 不校验 peer 版本范围",**已更正**):`dsh plugin add` 用运行时版本比对插件声明的 `@deepseek-ai/dsh-*` 范围,不匹配即**拒绝安装**,打印声明的范围并给出豁免命令。实测原文:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
dsh: installation rejected: Plugin dsh-nurse-record-check@0.1.0 is incompatible with
|
|
429
|
+
dsh 0.2.1-alpha.1: peerDependencies {"@deepseek-ai/dsh-tools":">=0.1.2-rc.1 <0.2.0 || …"}.
|
|
430
|
+
To accept this risk explicitly, grant the exact-version exemption for
|
|
431
|
+
dsh-nurse-record-check@0.1.0 on dsh 0.2.1-alpha.1 with `dsh plugin allow-version` or the
|
|
432
|
+
plugin manager, then retry the installation or restart dsh.
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
两个后果:① 范围**少列一个 0.2.x 的 prerelease 元组**,插件在 0.2.x 宿主上就装不上(拦在安装期,不是运行期);② 用**旧版本宿主**打的 tarball 用新版本 `verify` 跑,报错会指向 base bundle 而不是你的插件(`dsh-plugin-dev verify` 的 `--base`/`--headless` 默认值因此必须与 compat workflow 同步)。
|
|
436
|
+
- **豁免机制**(被拒后唯一的出路,可用 `dsh plugin --profile <p> --help` 复核):
|
|
437
|
+
|
|
438
|
+
```sh
|
|
439
|
+
dsh plugin --profile <profile> version-exemptions # 列出已授予的豁免
|
|
440
|
+
dsh plugin --profile <profile> allow-version <pkg@version> \
|
|
441
|
+
--dsh-version <runtime> --accept-risk # 授予精确版本豁免
|
|
442
|
+
dsh plugin --profile <profile> revoke-version <pkg@version> # 撤销
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
豁免是**按 `<包@精确版本> × <宿主版本>` 的精确配对**(不是范围),所以宿主升级后要重新授予。`plugin_manager` 工具面里有对应入口,Web 侧边栏与插件管理页也能授予。**给用户写 README 排错节时把这三条命令抄进去**——这是被拒绝安装时用户唯一能自救的手段。
|
|
424
446
|
- **`file:` 安装载入的是副本**:`dsh plugin add file:../my-plugin` 之后,profile 的 `node_modules` 下是**拷贝**(`nodeLinker: hoisted` + 锁文件只记 `version: file:<path>`),所以**改源码不生效、`update` 是 no-op**——只有 `remove` + `add` 才会重装。调试期要频繁改就用 `--patch` 直挂源码目录,或每次显式重装。
|
|
425
447
|
|
|
426
448
|
**tsconfig 三件套 + 构建陷阱**
|
|
@@ -482,7 +504,7 @@ bundle 挂一个普通 provider 插件:`inject = ['cmdlineArgs']`,用 `@deep
|
|
|
482
504
|
## 9.1 版本兼容与迁移(0.2 世代必读)
|
|
483
505
|
|
|
484
506
|
- **先认版本**:`dsh --version`。`0.1.7-alpha.1` 起包含本世代全部破坏性变更(会话格式 V4)。npm 上 `latest`=`0.2.0-rc.2`、`alpha`=`0.2.1-alpha.1`,而 master 最新 tag 是 `0.2.1-alpha.1`——**三个"最新"不是一个东西**。
|
|
485
|
-
- **peer
|
|
507
|
+
- **peer 范围要写全两侧的 prerelease 元组**(如 `>=0.1.2-rc.1 <0.2.0 || … || >=0.2.0-0 <0.3.0 || >=0.2.1-0 <0.3.0`)。⚠️ **这个范围会被安装期强制**,不是文档意图:`dsh plugin add` 用运行时版本比对它,不匹配就拒绝安装并打印你声明的范围。所以少一个 0.2.x clause,插件在 0.2.x 宿主上**根本装不上**(拦在安装期,不是运行期)。范围之外仍要靠运行时特性探测——范围只表达"能不能装",表达不了"某个 API 在不在"。
|
|
486
508
|
- **共享实例的 dsh 包只放 peer + dev**,绝不放 dependencies(否则 profile 内副本会遮蔽运行时版本)。
|
|
487
509
|
- **升级后必做**:`dsh --profile <name> --dump-config` 看有没有 `patch: entry <id> not found`(上游删行/改名后,你的 override 会**静默失效**);再跑一次真实能力。
|
|
488
510
|
- **逐项迁移清单**:见 [migration-0.2.md](migration-0.2.md)(含会话格式 V4、settings 重写、invariants 删除、`readBytes`、客户端 slot/Remote 改名、`agent.inject()` 的 source 包装退役、preset 分叉等全部条目与官方原文出处)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-guide",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
4
4
|
"description": "The dsh-plugin-guide knowledge base plus the dsh-plugin-dev CLI toolchain: official docs, Cordis primer, community deep-dives, and battle-tested pitfalls registered as an on-demand agent skill, with a scaffolder, static checker, and pack verifier for building DSH plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"icon.svg",
|
|
24
24
|
"SKILL.md",
|
|
25
25
|
"guide",
|
|
26
|
-
"references",
|
|
26
|
+
"references",
|
|
27
|
+
"scripts",
|
|
27
28
|
"LICENSE",
|
|
28
29
|
"NOTICE.md",
|
|
29
30
|
"README.md",
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@deepseek-ai/dsh-attachment": "0.2.1-alpha.1",
|
|
72
73
|
"@types/node": "^22.20.1",
|
|
74
|
+
"@types/semver": "^7.8.0",
|
|
75
|
+
"semver": "^7.8.5",
|
|
73
76
|
"vitest": "^3.2.7"
|
|
74
77
|
},
|
|
75
78
|
"peerDependencies": {
|
|
@@ -110,8 +113,14 @@
|
|
|
110
113
|
"filesystem:read"
|
|
111
114
|
],
|
|
112
115
|
"compatibility": {
|
|
113
|
-
"dshVersions": [
|
|
114
|
-
"0.2
|
|
116
|
+
"dshVersions": [
|
|
117
|
+
"0.1.2-rc.1",
|
|
118
|
+
"0.1.5-rc.2",
|
|
119
|
+
"0.1.7-alpha.2",
|
|
120
|
+
"0.1.7-rc.1",
|
|
121
|
+
"0.1.7-rc.2",
|
|
122
|
+
"0.2.1-alpha.1"
|
|
123
|
+
]
|
|
115
124
|
},
|
|
116
125
|
"capability": {
|
|
117
126
|
"id": "dsh-plugin-guide",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// 3. the scaffolder produces TS and JS skeletons inside a mkdtemp sandbox
|
|
6
6
|
// (never touching a real home directory), which are then removed.
|
|
7
7
|
import { spawnSync } from 'node:child_process'
|
|
8
|
-
import { existsSync, mkdtempSync, rmSync, readdirSync } from 'node:fs'
|
|
8
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, readdirSync } from 'node:fs'
|
|
9
9
|
import { tmpdir } from 'node:os'
|
|
10
10
|
import { dirname, join, resolve } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
@@ -59,6 +59,18 @@ try {
|
|
|
59
59
|
check(result.status === 0 && hasEntry && hasPatch, `${lang} scaffold produces entry + cordis.patch.yml (exit ${result.status})`)
|
|
60
60
|
check(readmeCount === 5, `${lang} scaffold produces 5 README languages (${readmeCount})`)
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
// 3b. Extension contract for the TS scaffold. `platform: node` makes tsdown
|
|
64
|
+
// force `.mjs`/`.cjs` unless `fixedExtension` is disabled, while the scaffolded
|
|
65
|
+
// package.json declares `lib/index.js` + `lib/index.d.ts`. When those two
|
|
66
|
+
// disagree the build succeeds and `main` resolves to nothing, so the plugin
|
|
67
|
+
// installs and then silently never loads — with no error anywhere. Asserted
|
|
68
|
+
// offline (no install, no build) so the gate stays network-free.
|
|
69
|
+
const tsConfig = readFileSync(join(root, 'templates', 'ts', 'tsdown.config.mjs'), 'utf8')
|
|
70
|
+
check(
|
|
71
|
+
/fixedExtension\s*:\s*false/.test(tsConfig),
|
|
72
|
+
'ts template disables tsdown fixedExtension so the build emits the declared lib/index.js',
|
|
73
|
+
)
|
|
62
74
|
} finally {
|
|
63
75
|
rmSync(sandbox, { recursive: true, force: true })
|
|
64
76
|
}
|
|
@@ -12,4 +12,10 @@ export default defineConfig({
|
|
|
12
12
|
clean: true,
|
|
13
13
|
sourcemap: false,
|
|
14
14
|
outDir: 'lib',
|
|
15
|
+
// With `platform: node` tsdown forces `.mjs`/`.cjs`; disable that so ESM output
|
|
16
|
+
// follows the package `"type": "module"` and emits `lib/index.js` +
|
|
17
|
+
// `lib/index.d.ts` — the paths this template's package.json declares. Without
|
|
18
|
+
// this flag the build emits `lib/index.mjs`/`lib/index.d.mts`, `main` resolves
|
|
19
|
+
// to nothing, and the plugin silently never loads.
|
|
20
|
+
fixedExtension: false,
|
|
15
21
|
})
|