@tokensapi/dsh-plugin-check 0.3.1 → 0.3.3

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.md CHANGED
@@ -1,79 +1,95 @@
1
- # dsh-plugin-check
2
-
3
- TokensCowork / DSH 插件合格性体检。给所有插件开发者的统一标准:一条命令,当场知道你的插件**能否安全装入宿主、会不会把用户的 Cowork 搞崩**。同一套规则同时用于:开发者本地自查、插件仓库 CI、插件市场上架门禁。
4
-
5
- ## 用法
6
-
7
- ```bash
8
- # 在插件目录里
9
- npx @tokensapi/dsh-plugin-check
10
-
11
- # 指定目标运行时(启用兼容范围判定;当前产品运行时版本见市场公告)
12
- npx @tokensapi/dsh-plugin-check --runtime 0.1.3-alpha.1
13
-
14
- # 只跑离线清单体检(秒级,无网络)
15
- npx @tokensapi/dsh-plugin-check --skip-smoke
16
-
17
- # 机器可读输出(CI / 市场门禁)
18
- npx @tokensapi/dsh-plugin-check --json
19
-
20
- # 体检一个已发布的 npm 包(市场上架审核用)
21
- npx @tokensapi/dsh-plugin-check --package @scope/name@1.2.3 --runtime 0.1.3-alpha.1
22
- ```
23
-
24
- 退出码:`0` 合格,`1` 存在 error(`--strict` 时 warning 也计入)。冒烟失败时加 `--keep-workspace` 保留临时工作区排查。
25
-
26
- ## 体检分两个阶段
27
-
28
- **阶段① 清单体检**(纯静态、离线、秒级):检查 package.json 与 cordis 补丁的声明是否合规。
29
- **阶段② 隔离启动冒烟**(联网、约 1–3 分钟):在临时工作区按你声明的 peer 范围装出真实运行时,然后逐补丁行做真实网关启动时做的事——解析入口 → `import` → `new Context()` + `ctx.plugin()` 应用。三段都过,你的插件就不会以"装上即崩"的方式杀死用户的 Cowork。全程 `--ignore-scripts`,你的代码只在一次性子进程里运行,超时即杀。
30
-
31
- 清单存在 error 时冒烟不执行——先修再跑。
32
-
33
- ## 规则清单
34
-
35
- 每条规则都对应一种真实发生过的宿主故障。
36
-
37
- | 规则 | 级别 | 内容 | 拦的是什么事故 |
38
- |---|---|---|---|
39
- | M1-manifest | error/warning | package.json 合法,name/version 齐全;预发布版本给出上架提示 | 无法入库 |
40
- | M2-lifecycle | error | 禁止 `preinstall/install/postinstall/prepare` 脚本(构建用 `prepack`) | 安装即执行任意代码;市场受控安装的既有红线 |
41
- | M3-core-peer | error | `@deepseek-ai/cordis*`、`@deepseek-ai/dsh*` 只能是 peerDependencies | 双内核实例 → Symbol 不等 → 服务注册对不上 → **Cowork 启动失败** |
42
- | M4-bundle | error | `dsh.bundle.patch` 必填、文件存在、能解析出插件行 | 宿主无法把插件挂进加载树 |
43
- | M5-row-scope | error | 补丁行只能指向本包(或其子路径) | 插件行劫持挂载其他包 |
44
- | M6-engine | error/warning | `dsh.engine` 声明目标运行时 SemVer 范围;与 `--runtime` 不相交为 error(未声明当前为 warning,将转必填) | 装进不适配的宿主版本 |
45
- | M7-peer-range | warning | 各 `dsh-*` peer 范围应包含目标运行时 | 宿主升级后接口错配,运行时断裂 |
46
- | M8-node-engines | warning | 建议声明 `engines.node`(宿主为 Node 22+) | 语法/API 不可用 |
47
- | M9-license | warning | 建议声明 license | 分发合规 |
48
- | M10-secrets | error/warning | 发布内容不得包含 `.env`、私钥等凭据样式文件 | 凭据泄露 |
49
- | S1-pack | error | `npm pack --ignore-scripts` 必须成功 | 包本身发布不出来 |
50
- | S2-install | error | 按你声明的依赖必须装得出来 | 用户受控安装会同样失败 |
51
- | S3/S4-apply | error | 每个补丁行:入口可解析、`import` 不抛、`ctx.plugin()` 应用不抛(声明 `inject` 等待服务注入属正常,不算失败) | **启动链击穿**——一行 import 失败会拖死整棵插件树 |
52
-
53
- ## `dsh.engine` 字段
54
-
55
- package.json 声明你适配的 DSH 运行时范围:
56
-
57
- ```json
58
- {
59
- "dsh": {
60
- "engine": ">=0.1.3-alpha.1 <0.2.0",
61
- "bundle": { "patch": "./cordis.patch.yml" }
62
- }
63
- }
64
- ```
65
-
66
- 宿主升级越过你的范围时,市场会把你的插件标记为"待适配"而不是硬载崩溃。
67
-
68
- ## CI 集成示例
69
-
70
- ```yaml
71
- # .github/workflows/check.yml
72
- - run: npx @tokensapi/dsh-plugin-check --runtime 0.1.3-alpha.1 --strict
73
- ```
74
-
75
- ## 上架流程中的位置
76
-
77
- 1. 开发者:发布 npm 版本前本地跑体检,全绿再发。
78
- 2. 管理员:在仓库 Actions 的 **Plugin Check** workflow 填「包名@版本」手动触发,体检报告生成在该次运行的 Summary。
79
- 3. 上架:后台 publish 对话框把这次运行的链接填入「检查记录链接」——链接的红/绿即审核证据,`reviewed_version` 随上架落库。存在 error 的版本不满足上架条件。
1
+ # dsh-plugin-check
2
+
3
+ TokensCowork / DSH 插件合格性体检。给所有插件开发者的统一标准:一条命令,当场知道你的插件**能否安全装入宿主、会不会把用户的 Cowork 搞崩**。同一套规则同时用于:开发者本地自查、插件仓库 CI、插件市场上架门禁。
4
+
5
+ ## 用法
6
+
7
+ ```bash
8
+ # 在插件目录里
9
+ npx @tokensapi/dsh-plugin-check
10
+
11
+ # 指定目标运行时(启用兼容范围判定;当前产品运行时版本见市场公告)
12
+ npx @tokensapi/dsh-plugin-check --runtime 0.1.3-alpha.1
13
+
14
+ # 只跑离线清单体检(秒级,无网络)
15
+ npx @tokensapi/dsh-plugin-check --skip-smoke
16
+
17
+ # 机器可读输出(CI / 市场门禁)
18
+ npx @tokensapi/dsh-plugin-check --json
19
+
20
+ # 体检一个已发布的 npm 包(市场上架审核用)
21
+ npx @tokensapi/dsh-plugin-check --package @scope/name@1.2.3 --runtime 0.1.3-alpha.1
22
+ ```
23
+
24
+ 退出码:`0` 合格,`1` 存在 error(`--strict` 时 warning 也计入)。冒烟失败时加 `--keep-workspace` 保留临时工作区排查。
25
+
26
+ ## 体检分两个阶段
27
+
28
+ **阶段① 清单体检**(纯静态、离线、秒级):检查 package.json 与 cordis 补丁的声明是否合规。
29
+ **阶段② 隔离启动冒烟**(联网、约 1–3 分钟):在临时工作区按你声明的 peer 范围装出真实运行时,然后逐补丁行做真实网关启动时做的事——解析入口 → `import` → `new Context()` + `ctx.plugin()` 应用。三段都过,你的插件就不会以"装上即崩"的方式杀死用户的 Cowork。全程 `--ignore-scripts`,你的代码只在一次性子进程里运行,超时即杀。
30
+
31
+ 清单存在 error 时冒烟不执行——先修再跑。
32
+
33
+ ## 规则清单
34
+
35
+ 每条规则都对应一种真实发生过的宿主故障。
36
+
37
+ | 规则 | 级别 | 内容 | 拦的是什么事故 |
38
+ |---|---|---|---|
39
+ | M1-manifest | error/warning | package.json 合法,name/version 齐全;预发布版本给出上架提示 | 无法入库 |
40
+ | M2-lifecycle | error | 禁止 `preinstall/install/postinstall/prepare` 脚本(构建用 `prepack`) | 安装即执行任意代码;市场受控安装的既有红线 |
41
+ | M3-core-peer | error | `@deepseek-ai/cordis*`、`@deepseek-ai/dsh*` 只能是 peerDependencies | 双内核实例 → Symbol 不等 → 服务注册对不上 → **Cowork 启动失败** |
42
+ | M4-bundle | error | `dsh.bundle.patch` 必填、文件存在、能解析出插件行 | 宿主无法把插件挂进加载树 |
43
+ | M5-row-scope | error | 补丁行只能指向本包(或其子路径) | 插件行劫持挂载其他包 |
44
+ | M6-engine | error/warning | `dsh.engine` 声明目标运行时 SemVer 范围;与 `--runtime` 不相交为 error(未声明当前为 warning,将转必填) | 装进不适配的宿主版本 |
45
+ | M7-peer-range | warning | 各 `dsh-*` peer 范围应包含目标运行时 | 宿主升级后接口错配,运行时断裂 |
46
+ | M8-node-engines | warning | 建议声明 `engines.node`(宿主为 Node 22+) | 语法/API 不可用 |
47
+ | M9-license | warning | 建议声明 license | 分发合规 |
48
+ | M10-secrets | error/warning | 发布内容不得包含 `.env`、私钥等凭据样式文件 | 凭据泄露 |
49
+ | S1-pack | error | `npm pack --ignore-scripts` 必须成功 | 包本身发布不出来 |
50
+ | S2-install | error/warning | 按你声明的依赖必须装得出来;宿主内核包(`@deepseek-ai/*`)的内部版本在公开源取不到时降级为 warning 并跳过冒烟 | 用户受控安装会同样失败 |
51
+ | S3/S4-apply | error | 每个补丁行:入口可解析、`import` 不抛、`ctx.plugin()` 应用不抛(声明 `inject` 等待服务注入属正常,不算失败) | **启动链击穿**——一行 import 失败会拖死整棵插件树 |
52
+
53
+ ## 会话内体检(装进 Cowork)
54
+
55
+ 本包同时是一个 Cowork 插件。装进宿主后会注册 `plugin_check` 工具,在会话里直接问"这个插件能装吗",不必记命令行:
56
+
57
+ ```
58
+ # 正在开发、还没发布的插件 —— 直接读工作区
59
+ plugin_check(path="D:/code/my-plugin")
60
+
61
+ # 已经发布到 npm 的包
62
+ plugin_check(package="@scope/name", version="1.2.3")
63
+ ```
64
+
65
+ **发版前用 `path`**:读的是你本地的 `package.json` 与 `cordis.patch.yml`,不需要先 `npm publish`,改一行问一次都行。已发布的包用 `package`,跑的是**清单规则 + 包内补丁行检查**:工具会把已发布的 tarball 取下来,解出 `cordis.patch.yml` 真读补丁行——只看 registry 清单会漏掉"补丁行 `name` 写成 cordis 插件名而不是 npm 包名"这类装上即让整棵插件树崩溃的缺陷。取不到包时如实标注补丁未检查(warning),不会因网络问题把插件判成不合格。
66
+
67
+ 隔离启动冒烟要装依赖、起子进程,不在宿主进程里执行;完整体检仍走 CLI 或 Plugin Check workflow。
68
+
69
+ ## `dsh.engine` 字段
70
+
71
+ package.json 声明你适配的 DSH 运行时范围:
72
+
73
+ ```json
74
+ {
75
+ "dsh": {
76
+ "engine": ">=0.1.3-alpha.1 <0.2.0",
77
+ "bundle": { "patch": "./cordis.patch.yml" }
78
+ }
79
+ }
80
+ ```
81
+
82
+ 宿主升级越过你的范围时,市场会把你的插件标记为"待适配"而不是硬载崩溃。
83
+
84
+ ## CI 集成示例
85
+
86
+ ```yaml
87
+ # .github/workflows/check.yml
88
+ - run: npx @tokensapi/dsh-plugin-check --runtime 0.1.3-alpha.1 --strict
89
+ ```
90
+
91
+ ## 上架流程中的位置
92
+
93
+ 1. 开发者:发布 npm 版本前本地跑体检,全绿再发。
94
+ 2. 管理员:在仓库 Actions 的 **Plugin Check** workflow 填「包名@版本」手动触发,体检报告生成在该次运行的 Summary。
95
+ 3. 上架:后台 publish 对话框把这次运行的链接填入「检查记录链接」——链接的红/绿即审核证据,`reviewed_version` 随上架落库。存在 error 的版本不满足上架条件。
@@ -1,78 +1,113 @@
1
- /* ============================================================
2
- * Cowork 插件入口:会话内插件体检工具
3
- * ============================================================
4
- * 装进 TokensCowork 后注册 plugin_check 工具:对任意 npm 插件包
5
- * 跑清单规则(与 CLI 的清单阶段同源),模型或用户在会话里即可
6
- * 判断"这个插件装进来会不会有问题"。含隔离启动冒烟的完整体检
7
- * 仍走 CLI:npx @tokensapi/dsh-plugin-check --package <name@ver>。
8
- * ============================================================ */
9
- import { defineTool } from '@deepseek-ai/dsh-tools'
10
- import { checkPublishedManifest } from './registry-check.mjs'
11
-
12
- /** 缺省比对的运行时版本;宿主升级后可通过插件配置覆盖。 */
13
- const DEFAULT_RUNTIME = '0.1.3-alpha.1'
14
- const REGISTRY = 'https://registry.npmjs.org'
15
- const FETCH_TIMEOUT_MS = 15_000
16
-
17
- export const name = 'tokens-plugin-check'
18
- export const inject = ['tools']
19
-
20
- async function fetchManifest(packageName, version) {
21
- const controller = new AbortController()
22
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
23
- try {
24
- const response = await fetch(
25
- `${REGISTRY}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`,
26
- { signal: controller.signal, headers: { accept: 'application/json' } },
27
- )
28
- if (!response.ok) {
29
- throw new Error(response.status === 404 ? `npm 上未找到 ${packageName}@${version}` : `npm 查询失败(HTTP ${response.status})`)
30
- }
31
- return await response.json()
32
- } finally {
33
- clearTimeout(timer)
34
- }
35
- }
36
-
37
- /**
38
- * @param {import('@deepseek-ai/cordis').Context} ctx
39
- * @param {{ runtime?: string }} [config]
40
- */
41
- export function apply(ctx, config = {}) {
42
- const runtime = typeof config.runtime === 'string' && config.runtime !== '' ? config.runtime : DEFAULT_RUNTIME
43
- ctx.tools.register(defineTool({
44
- name: 'plugin_check',
45
- description: 'Check whether a published DSH/TokensCowork plugin npm package is safe to install: '
46
- + 'forbidden lifecycle scripts, dual-kernel core dependencies, missing dsh bundle patch, '
47
- + 'and runtime/peer compatibility against the current DSH runtime. '
48
- + 'Returns ok=false with per-rule findings when the plugin would break the host.',
49
- parameters: {
50
- package: {
51
- type: 'string',
52
- required: true,
53
- description: 'npm package name, e.g. "@tokensapi/dsh-progressive-tools".',
54
- },
55
- version: {
56
- type: 'string',
57
- description: 'Exact version to check. Defaults to the latest dist-tag.',
58
- },
59
- },
60
- output: {
61
- schema: { type: 'json' },
62
- render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
63
- },
64
- isConcurrencySafe: () => true,
65
- async execute(args) {
66
- const manifest = await fetchManifest(args.package, args.version ?? 'latest')
67
- const findings = checkPublishedManifest(manifest, runtime)
68
- return {
69
- package: manifest.name,
70
- version: manifest.version,
71
- runtime,
72
- ok: !findings.some(finding => finding.level === 'error'),
73
- findings,
74
- note: '以上为清单规则;含隔离启动冒烟的完整体检请运行 npx @tokensapi/dsh-plugin-check --package <name@version>',
75
- }
76
- },
77
- }))
78
- }
1
+ /* ============================================================
2
+ * Cowork 插件入口:会话内插件体检工具
3
+ * ============================================================
4
+ * 装进 TokensCowork 后注册 plugin_check 工具:对本地插件目录或
5
+ * 任意 npm 插件包
6
+ * 跑清单规则 + 包内补丁行检查(与 CLI 的清单阶段同源),模型或
7
+ * 用户在会话里即可判断"这个插件装进来会不会有问题"。补丁行会
8
+ * tarball 里真读出来 —— 只看 registry 清单会漏掉"补丁行 name
9
+ * 写成插件名"这类装上即崩的缺陷。含隔离启动冒烟的完整体检仍走
10
+ * CLI:npx @tokensapi/dsh-plugin-check --package <name@ver>。
11
+ * ============================================================ */
12
+ import { resolve } from 'node:path'
13
+ import { defineTool } from '@deepseek-ai/dsh-tools'
14
+ import { checkManifest } from './manifest-check.mjs'
15
+ import { checkPublishedPackage } from './registry-check.mjs'
16
+
17
+ /** 缺省比对的运行时版本;宿主升级后可通过插件配置覆盖。 */
18
+ const DEFAULT_RUNTIME = '0.1.3-alpha.1'
19
+ const REGISTRY = 'https://registry.npmjs.org'
20
+ const FETCH_TIMEOUT_MS = 15_000
21
+
22
+ export const name = 'tokens-plugin-check'
23
+ export const inject = ['tools']
24
+
25
+ async function fetchManifest(packageName, version) {
26
+ const controller = new AbortController()
27
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
28
+ try {
29
+ const response = await fetch(
30
+ `${REGISTRY}/${encodeURIComponent(packageName)}/${encodeURIComponent(version)}`,
31
+ { signal: controller.signal, headers: { accept: 'application/json' } },
32
+ )
33
+ if (!response.ok) {
34
+ throw new Error(response.status === 404 ? `npm 上未找到 ${packageName}@${version}` : `npm 查询失败(HTTP ${response.status})`)
35
+ }
36
+ return await response.json()
37
+ } finally {
38
+ clearTimeout(timer)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * @param {import('@deepseek-ai/cordis').Context} ctx
44
+ * @param {{ runtime?: string }} [config]
45
+ */
46
+ export function apply(ctx, config = {}) {
47
+ const runtime = typeof config.runtime === 'string' && config.runtime !== '' ? config.runtime : DEFAULT_RUNTIME
48
+ ctx.tools.register(defineTool({
49
+ name: 'plugin_check',
50
+ description: 'Check whether a DSH/TokensCowork plugin is safe to install: forbidden lifecycle '
51
+ + 'scripts, dual-kernel core dependencies, missing or out-of-scope cordis patch rows, and '
52
+ + 'runtime/peer compatibility against the current DSH runtime. Returns ok=false with per-rule '
53
+ + 'findings when the plugin would break the host. '
54
+ + 'Pass "path" for a plugin the user is developing locally (checks the working tree, no publish '
55
+ + 'needed) — prefer this whenever the user asks about their own plugin before releasing it. '
56
+ + 'Pass "package" for an already published npm package. Exactly one of the two is required.',
57
+ parameters: {
58
+ path: {
59
+ type: 'string',
60
+ description: 'Local plugin directory containing package.json. Use for unpublished/in-development plugins.',
61
+ },
62
+ package: {
63
+ type: 'string',
64
+ description: 'Published npm package name, e.g. "@tokensapi/dsh-progressive-tools".',
65
+ },
66
+ version: {
67
+ type: 'string',
68
+ description: 'Exact version to check with "package". Defaults to the latest dist-tag.',
69
+ },
70
+ },
71
+ output: {
72
+ schema: { type: 'json' },
73
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
74
+ },
75
+ isConcurrencySafe: () => true,
76
+ async execute(args) {
77
+ const hasPath = typeof args.path === 'string' && args.path !== ''
78
+ const hasPackage = typeof args.package === 'string' && args.package !== ''
79
+ if (hasPath === hasPackage) {
80
+ throw new Error('plugin_check 需要且只需要一个目标:本地目录传 path,已发布包传 package')
81
+ }
82
+ if (hasPath) {
83
+ // 本地工作区体检:发版前就能判,不需要先 publish。隔离冒烟要装
84
+ // 依赖起子进程,宿主进程里不做,如实告知走 CLI。
85
+ const local = checkManifest(resolve(args.path), { runtime })
86
+ return {
87
+ path: resolve(args.path),
88
+ package: local.manifest?.name,
89
+ version: local.manifest?.version,
90
+ runtime,
91
+ ok: !local.findings.some(finding => finding.level === 'error'),
92
+ inspectedPatch: true,
93
+ findings: local.findings,
94
+ note: '以上为清单规则 + 补丁行检查(读的是本地工作区);隔离启动冒烟请在插件目录运行 '
95
+ + 'npx @tokensapi/dsh-plugin-check --runtime ' + runtime,
96
+ }
97
+ }
98
+ const manifest = await fetchManifest(args.package, args.version ?? 'latest')
99
+ const { findings, inspectedPatch } = await checkPublishedPackage(manifest, runtime)
100
+ return {
101
+ package: manifest.name,
102
+ version: manifest.version,
103
+ runtime,
104
+ ok: !findings.some(finding => finding.level === 'error'),
105
+ inspectedPatch,
106
+ findings,
107
+ note: inspectedPatch
108
+ ? '以上为清单规则 + 包内补丁行检查;含隔离启动冒烟的完整体检请运行 npx @tokensapi/dsh-plugin-check --package <name@version>'
109
+ : '未读到包内补丁文件,补丁行未检查;请运行 npx @tokensapi/dsh-plugin-check --package <name@version> 做完整体检',
110
+ }
111
+ },
112
+ }))
113
+ }