Merge pull request #5735 from deepseek-harness/worktree/ecosystem-declarations
docs(plugin-market): require declared engine ranges and reuse npm metadata
This commit is contained in:
commit
aafc0bfb6b
3 files changed
+88
-60
No files matched your search
@@ -9,8 +9,8 @@
|
||||
en: ce5e4a362f558af6
|
||||
zh: 29d93e0655b99217
|
||||
/agent-note-plugin-market-index-format/proposal:
|
||||
en: 7b728d6cbf6f16d6
|
||||
zh: 73e79d35b0dbfbad
|
||||
en: ed9148e95b2ad7c2
|
||||
zh: 25a7add3c56fd681
|
||||
/agent-note-plugin-market-index-format/proposal/example:
|
||||
en: 1b1c960c966cf912
|
||||
zh: 97be87cb14893c26
|
||||
@@ -18,29 +18,29 @@
|
||||
en: 4fac5a9b8261342d
|
||||
zh: f9168a8a4209feab
|
||||
/agent-note-plugin-market-index-format/proposal/plugin-fields:
|
||||
en: f728927a89754892
|
||||
zh: 3bc3b18cac60cdc1
|
||||
en: dbb59e8f9807b1d5
|
||||
zh: 030754d30a3c1861
|
||||
/agent-note-plugin-market-index-format/proposal/version-fields:
|
||||
en: d82d84e9f15f0a8c
|
||||
zh: fa65770cd7607c15
|
||||
en: d6c0fa99e1cbb068
|
||||
zh: 3d2a7b85299c679a
|
||||
/agent-note-plugin-market-index-format/proposal/install-arguments:
|
||||
en: c748cdc4d8ce349a
|
||||
zh: 5740fa0d6106e637
|
||||
/agent-note-plugin-market-index-format/proposal/dsh-compatibility:
|
||||
en: 323c997bccd7cf4a
|
||||
zh: fd588d37925d3732
|
||||
en: 00fe507b9b5dfea7
|
||||
zh: 55de6aaeb3865e84
|
||||
/agent-note-plugin-market-index-format/proposal/generator-obligations:
|
||||
en: 2355bd40a77e615c
|
||||
zh: a83fa20f47b49e44
|
||||
en: 8e848a102fe9d86e
|
||||
zh: feff226ef8321e00
|
||||
/agent-note-plugin-market-index-format/proposal/reader-rules:
|
||||
en: 40613286c82370b2
|
||||
zh: f6b2452c75e9fc71
|
||||
en: 92903cbe87d9c0ca
|
||||
zh: 54af6cb33e9da252
|
||||
/agent-note-plugin-market-index-format/alternatives-considered:
|
||||
en: d102d17d4203c592
|
||||
zh: b98f970545e3e07f
|
||||
en: 3d7cf519bc8e0593
|
||||
zh: 328dc0747836eaa8
|
||||
/agent-note-plugin-market-index-format/acceptance-criteria:
|
||||
en: f364af5c0343a23e
|
||||
zh: 8b8c2a3ec3f6a57f
|
||||
en: 753abee6adf39812
|
||||
zh: 1102bcfdd48f7e90
|
||||
/agent-note-plugin-market-index-format/risks:
|
||||
en: e9ef08d42066f51a
|
||||
zh: 7b2ee5cda4c800ba
|
||||
en: 41170d483b17df80
|
||||
zh: 1bfd8f44e0f09036
|
||||
@@ -16,6 +16,8 @@ An index must therefore carry everything a list page displays: localized title a
|
||||
|
||||
Separate the **index** from the **index generator**. DSH defines and reads one JSON index format. How an index is produced, and from which source list, belongs to the generator. DSH's reader, its Discover page, and a reference generator are separate changes that consume this format. Indexes list [profile plugin bundles](../../implemented/architecture/2026-08-05-profile-plugin-bundles.md) and install them through [guided plugin installation](../../implemented/architecture/2026-09-15-guided-plugin-installation.md).
|
||||
|
||||
The market reuses npm's package names, versions, ranges, distribution tags, and person declarations. Readers and generators use maintained npm parsers and validators. Additional fields or changed meanings require a plugin-market requirement that npm does not meet.
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
@@ -48,18 +50,19 @@ Separate the **index** from the **index generator**. DSH defines and reads one J
|
||||
"labels": ["official"],
|
||||
"sortKeys": { "downloads": 12034, "rating": 4.6 }
|
||||
},
|
||||
"dist-tags": { "latest": "0.2.0-rc.1", "next": "0.2.0-rc.2" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0-rc.2",
|
||||
"publishTimestamp": 1790476800,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0-rc.2" }
|
||||
"engines": { "dsh": ">=0.2.0 <0.3.0" }
|
||||
},
|
||||
{
|
||||
"version": "0.2.0-rc.1",
|
||||
"publishTimestamp": 1789872000,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0-rc.1" }
|
||||
"engines": { "dsh": "0.2.0 - 0.2.4 || >=0.3.0 <0.4.0" }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -67,6 +70,7 @@ Separate the **index** from the **index generator**. DSH defines and reads one J
|
||||
"name": "dsh-plugin-foo",
|
||||
"title": "Foo",
|
||||
"icon": "https://example.com/foo.png",
|
||||
"dist-tags": { "latest": "1.0.0" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
@@ -76,7 +80,8 @@ Separate the **index** from the **index generator**. DSH defines and reads one J
|
||||
"url": "https://github.com/author/dsh-plugins",
|
||||
"commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4",
|
||||
"path": "packages/foo"
|
||||
}
|
||||
},
|
||||
"engines": { "dsh": "*" }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -105,7 +110,7 @@ Category, label, and sort-key ids are non-empty strings. Readers look ids up as
|
||||
|
||||
| Field | Required | Rule |
|
||||
|---|---|---|
|
||||
| `name` | yes | Package name matching `^(@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*$`, at most 214 characters, unique within the index. |
|
||||
| `name` | yes | Package name accepted by npm's maintained package-name validator for new packages; unique within the index. |
|
||||
| `title` | no | `LocalizedText`; a reader displays `name` when absent. |
|
||||
| `description` | no | `LocalizedText`. |
|
||||
| `icon` | no | Either `data:<type>;base64,<payload>`, where `<type>` is `image/svg+xml`, `image/png`, `image/jpeg`, or `image/webp`, `<payload>` is canonically padded base64, and the decoded payload is at most 256 KiB; or a URL reference whose resolution against the index URL is an `https:` URL or has the index URL's origin. |
|
||||
@@ -115,17 +120,18 @@ Category, label, and sort-key ids are non-empty strings. Readers look ids up as
|
||||
| `metadata.category` | no | One declared category id. |
|
||||
| `metadata.labels` | no | Array of distinct declared label ids. |
|
||||
| `metadata.sortKeys` | no | Object mapping a declared sort-key id → finite number. |
|
||||
| `versions` | yes | Non-empty array of versions; see below. Its order has no meaning. |
|
||||
| `dist-tags` | no | Object mapping npm distribution-tag names to exact listed version strings; absent means no recorded channels. |
|
||||
| `versions` | yes | Non-empty array of versions; see below. Array order does not select the installation version. |
|
||||
|
||||
### Version fields
|
||||
|
||||
| Field | Required | Rule |
|
||||
|---|---|---|
|
||||
| `version` | yes | Exact version in the SemVer 2.0.0 grammar, with optional prerelease and build metadata and without a `v` prefix or whitespace; unique within the plugin. |
|
||||
| `version` | yes | Exact version parsed by npm's `node-semver`; preserve the complete string, including Git build metadata. The exact string is unique within the plugin. |
|
||||
| `publishTimestamp` | yes | Integer Unix timestamp in seconds, in `[0, 10^11)`. |
|
||||
| `source` | yes | Where `version` is installed from; see below. |
|
||||
| `engines` | no | Object. |
|
||||
| `engines.dsh` | no | Non-empty SemVer range string that does not start with `workspace:`; the DSH versions this version runs on. Absent means any DSH version. |
|
||||
| `engines` | yes | Object containing `dsh`. |
|
||||
| `engines.dsh` | yes | Non-blank valid npm SemVer range declared in the source manifest; copied unchanged. `*` explicitly declares support for any DSH version. |
|
||||
|
||||
A relative `icon` reference resolves against the index URL, as an HTML image reference resolves against its page. For an index at `https://market.example/dsh/index.json`, `"icon": "icons/foo.svg"` names `https://market.example/dsh/icons/foo.svg`. A generator can therefore publish the index and its icon files as one directory on any static host without knowing the final URL. Absolute icon URLs on other origins must use `https:`; the same-origin allowance lets an `http:` index on a loopback or intranet host serve its own icons. Readers resolve the reference when they read the index, so clients receive absolute URLs.
|
||||
|
||||
@@ -143,9 +149,9 @@ A client installs one version with exactly these pnpm arguments:
|
||||
|
||||
### DSH compatibility
|
||||
|
||||
DSH's installation check reads `engines.dsh` from the package manifest. When it is present, the package is compatible exactly when the running DSH version satisfies that range, prereleases included; an invalid range is incompatible. Only a manifest without `engines.dsh` falls back to its `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*` `peerDependencies`. Exact `name@version` exemptions apply to both rules.
|
||||
Every market-listed version must declare valid `engines.dsh`. The reader and market-origin installation evaluate that range against the running DSH version with npm's `node-semver` and `includePrerelease: true`. There is no peer fallback or range synthesis. npm package peer resolution remains outside market host compatibility.
|
||||
|
||||
The fallback tests every DSH peer range against the same running DSH version, so their conjunction is one SemVer range with the same result. SemVer ranges have no parentheses, so the conjunction joins one `||` alternative from each range with spaces, for every combination, and joins the combinations with `||`. An index therefore carries a single `engines.dsh`.
|
||||
Installation rechecks the source manifest before accepting a market-origin package. An exact local `name@version` exemption may permit a valid range that rejects the runtime; it cannot make a missing or invalid declaration eligible. Exemptions are not published in the index.
|
||||
|
||||
### Generator obligations
|
||||
|
||||
@@ -153,19 +159,25 @@ A reader cannot verify these rules; a generator that violates them publishes a m
|
||||
|
||||
- `name` and each `version` equal the package manifest of the installed source.
|
||||
- `publishTimestamp` is the registry publish time of `version` for npm sources and the committer time of `commit` for Git sources.
|
||||
- `engines.dsh` copies the manifest's `engines.dsh`. Without one, it is the conjunction, as defined in DSH compatibility, of the manifest's DSH peer ranges after publication rewriting, omitting `workspace:*`, `workspace:^`, and `workspace:~`, because installation treats them as the running DSH version; with no remaining range, `engines.dsh` is absent. Installation finds any other `workspace:` range incompatible with every DSH version, so generators do not list such a version.
|
||||
- `engines.dsh` copies the manifest's declared range unchanged. A version with a missing, blank, or invalid range is omitted with a diagnostic. A plugin with no eligible version is omitted.
|
||||
- `dist-tags` snapshots npm's [distribution tags](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/), retaining only targets present among the eligible listed versions. For Git sources, the publisher or market supplies the same exact tag-to-version mapping.
|
||||
- Each listed version declares `dsh.bundle`. Installation independently refuses a package without a bundle patch.
|
||||
- `title`, `description`, `icon`, and `author` come from the highest listed version's locale `meta` fields, manifest fields, and icon, using the rules for installed plugin display metadata. `homepage` copies that version's manifest `homepage` and is absent without one.
|
||||
- `title`, `description`, and `icon` come from the highest listed version's locale metadata, manifest fields, and icon under the installed-plugin display rules. Use npm's `node-semver.rsort()` order, retaining input order for ties. This metadata choice does not select the installation version. `homepage` copies that version's manifest `homepage` and is absent without one.
|
||||
- `author` comes from the same highest listed version's npm author declaration. Copy an object's `name`; parse shorthand strings with a maintained npm person normalizer. Omit an absent or whitespace-only name and contact fields. Normalize a fresh projection, leaving the source manifest untouched.
|
||||
|
||||
### Reader rules
|
||||
|
||||
A reader rejects the whole index when the document violates any rule in Root fields, Plugin fields, Version fields, or the `source` forms, including an undeclared category, label, or sort-key reference, a duplicate `name` or `version`, or a `format` other than `1`. Each such defect is a generator bug, and dropping entries silently would hide it. Readers may bound the document size.
|
||||
A reader rejects the whole index when the document violates any rule in Root fields, Plugin fields, Version fields, or the `source` forms, including an undeclared category, label, or sort-key reference, an unlisted distribution-tag target, a duplicate `name` or exact `version` string, or a `format` other than `1`. Each such defect is a generator bug, and dropping entries silently would hide it. Readers may bound the document size.
|
||||
|
||||
A reader ignores members that this note does not define, at every level; an unknown `source.type` value is rejected. Format 1 can then gain optional members without breaking existing readers; a change that existing readers would misinterpret uses a new `format` value.
|
||||
|
||||
Tag names must parse as registry tag specs under npm's maintained `npm-package-arg`; targets are exact listed version strings. npm sources use canonical published identifiers (`node-semver.valid(version) === version`); Git sources retain build metadata and pin their exact source by commit.
|
||||
|
||||
Categories, labels, and sort keys are opaque: a reader filters and sorts by them, displays their `title`, and never interprets an id. Readers display declarations in the order of their localized `title`. When sorting plugins, a plugin without a value for the selected sort key follows all plugins with a value in either direction, and equal values keep the index order. Sorting by publish time uses a plugin's greatest `publishTimestamp`.
|
||||
|
||||
A reader evaluates each version's compatibility from `engines.dsh` with the rule in DSH compatibility. A reader orders versions by SemVer precedence. A plugin's default version is its highest compatible version without a prerelease tag; a plugin with no such version at all uses its highest compatible prerelease. A plugin without a compatible default version displays as incompatible, and a client may still offer its other versions individually.
|
||||
A reader evaluates the required `engines.dsh` range under DSH compatibility. Versions use npm's `node-semver.rsort()` order, retaining index order for ties. Exact tag targets, not this display order, select installation versions.
|
||||
|
||||
The initial channel is `latest`. Its exact target is the publisher- or market-selected version. Clients install that recorded version or commit, never `name@latest`. Missing or incompatible `latest` requires explicit version or channel selection; clients do not switch automatically. Installation confirmation identifies the version, selected channel if any, prerelease status, index URL, and source.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -183,9 +195,9 @@ A reader evaluates each version's compatibility from `engines.dsh` with the rule
|
||||
|
||||
**Inline icons only.** A list without extra requests needs inline icons, but 256 KiB of raw bytes is about 341 KiB as base64, so a 100-plugin index could reach about 35 MB. URL icons trade extra requests, and disclosure of the user's IP address to the image host, for a small index.
|
||||
|
||||
**Copying DSH `peerDependencies` into the index.** It maps several package names to ranges that all constrain one DSH version, and names the constraint after npm peer resolution. `engines.dsh` states the same constraint once, and installation reads it first.
|
||||
**Inferring host compatibility from peer dependencies.** The market defines a new standard requiring an author-declared `engines.dsh`. Existing packages must add that declaration before listing; no fallback, workspace mapping, or range intersection is needed.
|
||||
|
||||
**A structured `author` such as npm's `{ name, email, url }`.** Clients only display an author, and an email member would spread addresses into every index.
|
||||
**Copying the complete npm person object into the display field.** The list needs a name. The generator supports npm's original declaration while omitting unused contact fields.
|
||||
|
||||
**Opaque sort values for time, including ISO date strings.** Publish time has fixed semantics that clients format and sort, so it is a typed field. Without time, sort values can be numbers only, which needs no per-key value-type rule.
|
||||
|
||||
@@ -193,9 +205,9 @@ A reader evaluates each version's compatibility from `engines.dsh` with the rule
|
||||
|
||||
**Declaration order as display order.** JavaScript reorders integer-like object keys, so declaration order would need either an id pattern or array declarations. Clients sort declarations by their localized title instead.
|
||||
|
||||
**Allowing `workspace:` ranges.** Installation maps `workspace:*`, `workspace:^`, and `workspace:~` to the running DSH version, so every runtime would appear compatible; omitting them states the same result explicitly.
|
||||
**Selecting the greatest compatible version.** npm's `latest` can name an older release. Inferring a target from SemVer ranking loses the recorded channel choice and is ambiguous for equally ranked builds.
|
||||
|
||||
**Prereleases as default versions.** Installation accepts prereleases, but offering a release candidate by default to users of a plugin with stable releases would install untested builds without a choice.
|
||||
**Automatic prerelease fallback.** Missing or incompatible channel targets do not mean that the user selected a prerelease.
|
||||
|
||||
**Rejecting unknown members.** Every optional addition would then require a new `format` value and break every deployed reader.
|
||||
|
||||
@@ -204,13 +216,15 @@ A reader evaluates each version's compatibility from `engines.dsh` with the rule
|
||||
- A validator accepts the example above and rejects one violation of each rule in Root fields, Plugin fields, Version fields, and the `source` forms, with a diagnostic naming the JSON path.
|
||||
- Install arguments built from every example version are accepted by DSH's install-spec parser and by pnpm.
|
||||
- A validator ignores unknown members at every level.
|
||||
- DSH's installation check prefers `engines.dsh` over DSH `peerDependencies`, and compatibility computed from an index version matches it for the same manifest.
|
||||
- Generator and validator refuse missing, blank, and invalid `engines.dsh`; omitted versions leave no dangling tag target. Index and market-origin installation agree on valid ranges, including hyphen ranges, prereleases, and valid ranges that reject the runtime. Exemptions never bypass the declaration requirement.
|
||||
- Exact tag targets select the same source after equally ranked Git build versions are reordered. Missing targets are rejected; absent or incompatible `latest` causes no automatic switch.
|
||||
- npm author objects and shorthand strings produce the declared name without contact fields or mutation of source identity.
|
||||
|
||||
## Risks
|
||||
|
||||
- An index is unsigned. A market can use a misleading `title`, such as one claiming to be official; clients must show the index URL and each version's `source`.
|
||||
- An index is unsigned. Titles, authors, labels, and scores are market claims; an `official` label is not DSH verification. Clients must show the index URL and each version's `source`.
|
||||
- A Git version's `name`, `version`, and `engines.dsh` are claims about the repository. A repository can claim any name, including an `@deepseek-ai/dsh-` name, and the installed manifest decides what is actually installed.
|
||||
- An npm version's `source.registry` is equally a claim: it directs the install of any `name`, including an `@deepseek-ai/dsh-` name, to a registry the market chooses, and that registry decides what is installed.
|
||||
- URL icons disclose the user's IP address to image hosts.
|
||||
- Pinned versions stay stale until the index is regenerated.
|
||||
- Pinned versions and distribution tags stay stale until the index is regenerated.
|
||||
- Market text reaches users, and models through any client tool that lists plugins, without review by DSH.
|
||||
@@ -16,6 +16,8 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
|
||||
将**索引**与**索引生成器**分开。DSH 定义并读取一种 JSON 索引格式。索引如何生成、基于哪份源列表生成,由生成器负责。DSH 的读取器、Discover 页面和参考生成器是使用此格式的独立变更。索引列出[profile 插件组合包](../../implemented/architecture/2026-08-05-profile-plugin-bundles.zh.md),并通过[引导式插件安装](../../implemented/architecture/2026-09-15-guided-plugin-installation.zh.md)安装它们。
|
||||
|
||||
市场复用 npm 的包名、版本、范围、发布标签和 person 声明。读取器与生成器使用维护中的 npm 解析器和校验器。新增字段或改变含义需要 npm 无法满足的插件市场需求。
|
||||
|
||||
### 示例
|
||||
|
||||
```json
|
||||
@@ -48,18 +50,19 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
"labels": ["official"],
|
||||
"sortKeys": { "downloads": 12034, "rating": 4.6 }
|
||||
},
|
||||
"dist-tags": { "latest": "0.2.0-rc.1", "next": "0.2.0-rc.2" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.2.0-rc.2",
|
||||
"publishTimestamp": 1790476800,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0-rc.2" }
|
||||
"engines": { "dsh": ">=0.2.0 <0.3.0" }
|
||||
},
|
||||
{
|
||||
"version": "0.2.0-rc.1",
|
||||
"publishTimestamp": 1789872000,
|
||||
"source": { "type": "npm" },
|
||||
"engines": { "dsh": "0.2.0-rc.1" }
|
||||
"engines": { "dsh": "0.2.0 - 0.2.4 || >=0.3.0 <0.4.0" }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -67,6 +70,7 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
"name": "dsh-plugin-foo",
|
||||
"title": "Foo",
|
||||
"icon": "https://example.com/foo.png",
|
||||
"dist-tags": { "latest": "1.0.0" },
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
@@ -76,7 +80,8 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
"url": "https://github.com/author/dsh-plugins",
|
||||
"commit": "3f9c2e1d8a4b6c0e7f1a2b3c4d5e6f708192a3b4",
|
||||
"path": "packages/foo"
|
||||
}
|
||||
},
|
||||
"engines": { "dsh": "*" }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -105,7 +110,7 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `name` | 是 | 匹配 `^(@[a-z0-9][a-z0-9._~-]*/)?[a-z0-9][a-z0-9._~-]*$` 的包名,长度不超过 214 个字符,在索引内唯一。 |
|
||||
| `name` | 是 | npm 维护的包名校验器接受的新包名称;在索引内唯一。 |
|
||||
| `title` | 否 | `LocalizedText`;缺省时读取器显示 `name`。 |
|
||||
| `description` | 否 | `LocalizedText`。 |
|
||||
| `icon` | 否 | 以下两种形式之一:`data:<type>;base64,<payload>`,其中 `<type>` 为 `image/svg+xml`、`image/png`、`image/jpeg` 或 `image/webp`,`<payload>` 是按规范填充的 base64,解码后的载荷不超过 256 KiB;或者 URL 引用,它相对于索引 URL 解析的结果是 `https:` URL,或与索引 URL 同源。 |
|
||||
@@ -115,17 +120,18 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
| `metadata.category` | 否 | 一个已声明的分类 id。 |
|
||||
| `metadata.labels` | 否 | 由互不相同的已声明标签 id 组成的数组。 |
|
||||
| `metadata.sortKeys` | 否 | 对象,将已声明的排序键 id 映射为有限数值。 |
|
||||
| `versions` | 是 | 非空的版本数组;见下文。数组顺序没有含义。 |
|
||||
| `dist-tags` | 否 | 将 npm 发布标签名称映射为已列出的精确版本字符串的对象;缺省表示未记录发布渠道。 |
|
||||
| `versions` | 是 | 非空的版本数组;见下文。数组顺序不选择安装版本。 |
|
||||
|
||||
### 版本字段
|
||||
|
||||
| 字段 | 必填 | 规则 |
|
||||
|---|---|---|
|
||||
| `version` | 是 | 符合 SemVer 2.0.0 语法的精确版本,可带先行版本号和版本编译信息,不带 `v` 前缀,不含空白字符;在插件内唯一。 |
|
||||
| `version` | 是 | npm 的 `node-semver` 解析的精确版本;保留完整字符串,包括 Git 版本编译信息。精确字符串在插件内唯一。 |
|
||||
| `publishTimestamp` | 是 | 以秒为单位的整数 Unix 时间戳,取值范围为 `[0, 10^11)`。 |
|
||||
| `source` | 是 | `version` 的安装来源;见下文。 |
|
||||
| `engines` | 否 | 对象。 |
|
||||
| `engines.dsh` | 否 | 不以 `workspace:` 开头的非空 SemVer 范围字符串,表示该版本可运行的 DSH 版本。缺省表示任意 DSH 版本。 |
|
||||
| `engines` | 是 | 包含 `dsh` 的对象。 |
|
||||
| `engines.dsh` | 是 | 源 manifest 声明的非空白、有效 npm SemVer 范围;原样复制。`*` 显式声明支持任意 DSH 版本。 |
|
||||
|
||||
相对的 `icon` 引用相对于索引 URL 解析,与 HTML 图片引用相对于所在页面解析的方式相同。对于位于 `https://market.example/dsh/index.json` 的索引,`"icon": "icons/foo.svg"` 指向 `https://market.example/dsh/icons/foo.svg`。因此,生成器可以把索引和图标文件作为一个目录发布到任意静态主机,无需知道最终 URL。指向其他源的绝对图标 URL 必须使用 `https:`;同源规则允许位于 loopback 或内网主机上的 `http:` 索引提供自己的图标。读取器在读取索引时解析该引用,因此客户端收到的是绝对 URL。
|
||||
|
||||
@@ -143,9 +149,9 @@ DSH 只能从用户已知的 spec 安装第三方组合包(bundle):npm 包
|
||||
|
||||
### DSH 兼容性
|
||||
|
||||
DSH 安装检查读取包 manifest 中的 `engines.dsh`。存在该字段时,当且仅当当前运行的 DSH 版本满足该范围(包括先行版本)时包才兼容;无效范围视为不兼容。只有没有 `engines.dsh` 的 manifest 才回退到其 `@deepseek-ai/dsh` 与 `@deepseek-ai/dsh-*` 的 `peerDependencies`。精确的 `name@version` 豁免对两条规则都适用。
|
||||
市场列出的每个版本都必须声明有效的 `engines.dsh`。读取器与市场来源的安装使用 npm 的 `node-semver` 和 `includePrerelease: true`,根据当前运行的 DSH 版本求值。不存在对等依赖回退或范围合成。npm 包的对等依赖解析不属于市场宿主兼容性检查。
|
||||
|
||||
回退规则用同一个当前运行的 DSH 版本检验每个 DSH peer 范围,因此这些范围的合取是一个结果相同的 SemVer 范围。SemVer 范围没有括号,所以合取的写法是:从每个范围各取一个 `||` 分支,用空格连接,遍历所有组合,再用 `||` 连接这些组合。因此索引只携带一个 `engines.dsh`。
|
||||
安装接受市场来源的包前,会重新检查源 manifest。精确的本地 `name@version` 豁免可以允许有效范围拒绝当前运行时的情况,但不能使缺失或无效声明符合列出条件。豁免不发布在索引中。
|
||||
|
||||
### 生成器义务
|
||||
|
||||
@@ -153,19 +159,25 @@ DSH 安装检查读取包 manifest 中的 `engines.dsh`。存在该字段时,
|
||||
|
||||
- `name` 和每个 `version` 与所安装来源中的包 manifest 一致。
|
||||
- 对于 npm 来源,`publishTimestamp` 是 `version` 在 registry 中的发布时间;对于 Git 来源,它是 `commit` 的提交者时间。
|
||||
- `engines.dsh` 复制 manifest 中的 `engines.dsh`。manifest 没有该字段时,它是 manifest 中经发布改写后的 DSH peer 范围按 DSH 兼容性一节定义的合取,并省略 `workspace:*`、`workspace:^` 和 `workspace:~`,因为安装时会将它们视为当前运行的 DSH 版本;没有剩余范围时省略 `engines.dsh`。安装检查认为其他任何 `workspace:` 范围与所有 DSH 版本都不兼容,因此生成器不列出此类版本。
|
||||
- `engines.dsh` 原样复制 manifest 声明的范围。缺失、空白或无效范围的版本会被省略并给出诊断。没有合格版本的插件会被省略。
|
||||
- `dist-tags` 是 npm [发布标签](https://docs.npmjs.com/cli/v11/commands/npm-dist-tag/)的快照,只保留指向合格且已列出版本的目标。对于 Git 来源,发布者或市场提供同样的精确标签到版本映射。
|
||||
- 列出的每个版本都声明了 `dsh.bundle`。安装时也会独立拒绝没有组合包 patch 的包。
|
||||
- `title`、`description`、`icon` 和 `author` 来自所列最高版本的语言文件 `meta` 字段、manifest 字段和图标,并采用已安装插件显示元数据的规则。`homepage` 复制该版本 manifest 的 `homepage`,manifest 没有该字段时省略。
|
||||
- `title`、`description` 和 `icon` 来自所列最高版本的语言元数据、manifest 字段和图标,采用已安装插件的显示规则。使用 npm 的 `node-semver.rsort()` 顺序,相等时保留输入顺序。元数据选择不选择安装版本。`homepage` 复制该版本 manifest 的 `homepage`,manifest 没有该字段时省略。
|
||||
- `author` 来自同一最高版本的 npm 作者声明。复制对象的 `name`;使用维护中的 npm person 归一化器解析简写字符串。省略缺失或全空白的名称以及联系字段。归一化新建的投影,不修改源 manifest。
|
||||
|
||||
### 读取器规则
|
||||
|
||||
文档违反根字段、插件字段、版本字段或 `source` 形式中的任何规则时,读取器拒绝整个索引,包括引用了未声明的分类、标签或排序键,`name` 或 `version` 重复,以及 `format` 不是 `1`。每个此类缺陷都是生成器 bug,静默丢弃条目会掩盖它。读取器可以限制文档大小。
|
||||
文档违反根字段、插件字段、版本字段或 `source` 形式中的任何规则时,读取器拒绝整个索引,包括引用了未声明的分类、标签或排序键,发布标签指向未列出的目标,`name` 或精确的 `version` 字符串重复,以及 `format` 不是 `1`。每个此类缺陷都是生成器 bug,静默丢弃条目会掩盖它。读取器可以限制文档大小。
|
||||
|
||||
读取器在每一层都忽略本 Agent Note 未定义的成员;未知的 `source.type` 值会被拒绝。这样格式 1 可以增加可选成员而不破坏现有读取器;现有读取器会误解的变更使用新的 `format` 值。
|
||||
|
||||
标签名称必须能被 npm 维护的 `npm-package-arg` 解析为 registry 标签 spec;目标是已列出的精确版本字符串。npm 来源使用规范发布标识(`node-semver.valid(version) === version`);Git 来源保留版本编译信息并用提交固定精确来源。
|
||||
|
||||
分类、标签和排序键对读取器不透明:读取器按它们筛选和排序,显示其 `title`,从不解释 id 的含义。读取器按本地化 `title` 对声明排序后显示。对插件排序时,无论排序方向如何,没有所选排序键值的插件都排在所有有值的插件之后,值相同的插件保持索引顺序。按发布时间排序时使用插件最大的 `publishTimestamp`。
|
||||
|
||||
读取器按 DSH 兼容性一节的规则,根据 `engines.dsh` 评估每个版本的兼容性。读取器按 SemVer 优先级对版本排序。插件的默认版本是其不带先行版本号的最高兼容版本;完全没有此类版本的插件使用其最高兼容的先行版本。没有兼容默认版本的插件显示为不兼容,客户端仍可单独提供它的其他版本。
|
||||
读取器按 DSH 兼容性一节的规则求值必填的 `engines.dsh` 范围。版本使用 npm 的 `node-semver.rsort()` 顺序,相等时保留索引顺序。安装版本由精确的标签目标选择,而不是此显示顺序。
|
||||
|
||||
初始渠道是 `latest`。它的精确目标是发布者或市场选择的版本。客户端安装记录的版本或提交,绝不安装 `name@latest`。缺失或不兼容的 `latest` 需要显式选择版本或渠道;客户端不会自动切换。安装确认标明版本、所选渠道(如果有)、先行版本状态、索引 URL 和来源。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -183,9 +195,9 @@ DSH 安装检查读取包 manifest 中的 `engines.dsh`。存在该字段时,
|
||||
|
||||
**只允许内联图标。** 不发额外请求的列表需要内联图标,但 256 KiB 原始字节编码为 base64 后约为 341 KiB,因此 100 个插件的索引可能达到约 35 MB。URL 图标以额外请求和向图片主机暴露用户 IP 地址为代价,换取较小的索引。
|
||||
|
||||
**把 DSH `peerDependencies` 复制进索引。** 它把多个包名映射到范围,而这些范围约束的都是同一个 DSH 版本,并且用 npm peer 解析的名称来命名这一约束。`engines.dsh` 只需表达一次同样的约束,并且安装时优先读取它。
|
||||
**从对等依赖推断宿主兼容性。** 市场定义要求作者声明 `engines.dsh` 的新标准。现有包必须添加此声明才能列出;不需要回退、workspace 映射或范围求交。
|
||||
|
||||
**结构化的 `author`,例如 npm 的 `{ name, email, url }`。** 客户端只显示作者,而 email 成员会把邮箱地址扩散到每个索引中。
|
||||
**将完整 npm person 对象复制到显示字段。** 列表需要名称。生成器支持 npm 的原始声明,同时省略不用的联系字段。
|
||||
|
||||
**用不透明排序值表示时间,包括 ISO 日期字符串。** 发布时间具有固定语义,客户端会对其格式化和排序,因此它是一个有类型的字段。排除时间后,排序值可以只用数值,无需为每个键规定值类型。
|
||||
|
||||
@@ -193,9 +205,9 @@ DSH 安装检查读取包 manifest 中的 `engines.dsh`。存在该字段时,
|
||||
|
||||
**以声明顺序作为显示顺序。** JavaScript 会重排类整数的对象键,因此要保留声明顺序,就需要限定 id 格式或改用数组声明。客户端改为按本地化标题对声明排序。
|
||||
|
||||
**允许 `workspace:` 范围。** 安装时会将 `workspace:*`、`workspace:^` 和 `workspace:~` 映射为当前运行的 DSH 版本,因此所有运行时都会显示为兼容;省略这些范围会明确表达同一结果。
|
||||
**选择最高兼容版本。** npm 的 `latest` 可以指向较旧的发布版本。从 SemVer 排名推断目标会丢失记录的渠道选择,而且在构建排名相等时存在歧义。
|
||||
|
||||
**默认版本使用先行版本。** 安装检查接受先行版本,但对已有稳定版本的插件默认提供候选版本,会在用户没有选择的情况下安装未经充分测试的构建。
|
||||
**自动回退到先行版本。** 渠道目标缺失或不兼容,不表示用户选择了先行版本。
|
||||
|
||||
**拒绝未知成员。** 这样每次增加可选成员都需要新的 `format` 值,并会破坏所有已部署的读取器。
|
||||
|
||||
@@ -204,13 +216,15 @@ DSH 安装检查读取包 manifest 中的 `engines.dsh`。存在该字段时,
|
||||
- 校验器接受上面的示例,并对根字段、插件字段、版本字段和 `source` 形式中的每条规则各拒绝一个违例,诊断信息给出对应的 JSON 路径。
|
||||
- 根据每个示例版本构建的安装参数可被 DSH 的安装 spec 解析器和 pnpm 接受。
|
||||
- 校验器在每一层都忽略未知成员。
|
||||
- DSH 安装检查优先使用 `engines.dsh` 而不是 DSH `peerDependencies`,并且根据索引版本计算的兼容性与同一 manifest 的安装检查结果一致。
|
||||
- 生成器和校验器拒绝缺失、空白和无效的 `engines.dsh`;省略版本后不留下悬空的标签目标。索引和市场来源的安装对有效范围结果一致,包括连字符范围、先行版本以及拒绝当前运行时的有效范围。豁免绝不绕过声明要求。
|
||||
- 排名相等的 Git 构建版本重新排序后,精确标签目标仍选择同一来源。缺失的目标被拒绝;`latest` 缺省或不兼容时不自动切换。
|
||||
- npm 作者对象与简写字符串产生声明的名称,不包含联系字段,也不修改源身份。
|
||||
|
||||
## 风险
|
||||
|
||||
- 索引没有签名。市场可能使用误导性的 `title`,例如声称自己是官方市场;客户端必须显示索引 URL 以及每个版本的 `source`。
|
||||
- 索引没有签名。标题、作者、标签和评分是市场的声明;`official` 标签不是 DSH 验证。客户端必须显示索引 URL 以及每个版本的 `source`。
|
||||
- Git 版本的 `name`、`version` 和 `engines.dsh` 是关于仓库的声明。仓库可以声明任意名称,包括 `@deepseek-ai/dsh-` 名称,实际安装的内容由已安装的 manifest 决定。
|
||||
- npm 版本的 `source.registry` 同样是一种声明:它把任意 `name`(包括 `@deepseek-ai/dsh-` 名称)的安装引向市场选择的 registry,由该 registry 决定实际安装的内容。
|
||||
- URL 图标会向图片主机暴露用户的 IP 地址。
|
||||
- 固定版本在索引重新生成之前一直保持陈旧。
|
||||
- 固定版本和发布标签在索引重新生成之前一直保持陈旧。
|
||||
- 市场文本未经 DSH 审核即到达用户,并通过任何列出插件的客户端工具到达模型。
|
||||
Reference in new issue
Block a user