@noob-stupid/dsh-plugin-console 0.3.25 → 0.3.26
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 +60 -6
- package/README.zh.md +46 -4
- package/lib/client.js +23 -1
- package/lib/index.js +162 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,13 +20,16 @@
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
> ## 🎉 v0.3.
|
|
23
|
+
> ## 🎉 v0.3.25 released — framework-upgrade safety & plugin adapt gate (2026-09-04)
|
|
24
24
|
>
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
>
|
|
25
|
+
> **Upgrade safety trio**: framework root detection (fixes the `.pnpm` realpath
|
|
26
|
+
> mixed-version root cause), a **full-tree checkpoint** before upgrade, and
|
|
27
|
+
> **automatic full-tree rollback** on install/relaunch failure — plus a
|
|
28
|
+
> **one-click rollback** button on the framework card.
|
|
29
|
+
> **Plugin adapt gate**: plugins incompatible with the new framework are
|
|
30
|
+
> force-disabled (enable locked); 「Check update → Update & adapt」auto-verifies
|
|
31
|
+
> declarations/dependencies and unlocks on success; AI Empower pre-checks
|
|
32
|
+
> framework compatibility during planning.
|
|
30
33
|
>
|
|
31
34
|
> **Install:**
|
|
32
35
|
> ```bash
|
|
@@ -82,6 +85,7 @@ auto-rollback).
|
|
|
82
85
|
| | Benefit | Detail |
|
|
83
86
|
|---|---|---|
|
|
84
87
|
| 🤖 | **AI Empower (v0.3.24)** | Give the console a package name or GitHub repo — the local AI reads docs, drafts a **deployment plan** (install / write config / start service / health check) and executes it safely after your confirmation; server-type components get an automatic control card |
|
|
88
|
+
| 🛡️ | **Framework upgrade safety & adapt gate (v0.3.25)** | Force-disables plugins incompatible with the new framework (enable locked; 「Check update → Update & adapt」auto-verifies and unlocks); full-tree checkpoint before upgrade, automatic rollback on relaunch failure, one-click rollback to the previous version |
|
|
85
89
|
| 🚀 | **Server component cards** | Left-side floating card auto-aligned to the main panel: start / stop / status / **open Web UI** buttons, multi-server dropdown, collapsible |
|
|
86
90
|
| 🧩 | **Plugin & skill hub** | Auto-collected index of `dsh-plugin` topic repos (**500+** by stars) plus a **Skills tab** (`agent-skills` ∪ `claude-skills` ∪ `dsh-skill`, up to 300) — browse, search, one-click install, no GitHub API calls |
|
|
87
91
|
| 🤖 | **Auto-collection CI** | GitHub Actions reruns `build-index` every 6 hours (manual trigger available); authors just add the `dsh-plugin` / `agent-skills` / `claude-skills` / `dsh-skill` topic — no application needed |
|
|
@@ -246,6 +250,47 @@ Requires: DSH ≥ 0.1.0-rc.6 (web profile, with `dsh-client-modules` / `dsh-host
|
|
|
246
250
|
- **Card dismiss semantics**: terminal states (done/failed) are permanently dismissed on ✕
|
|
247
251
|
(persisted); in-progress dismissal is session-only and the card returns after a refresh.
|
|
248
252
|
|
|
253
|
+
### Framework upgrade safety & plugin adapt gate (v0.3.25)
|
|
254
|
+
|
|
255
|
+
> Origin: the 2026-09-04 DSH `0.1.1-rc.2 → 0.1.2-rc.1` incident — the upgrade script ran in
|
|
256
|
+
> the wrong working directory, overwrote the new CLI into the old `.pnpm` entry, left a
|
|
257
|
+
> mixed-version tree, and the service could not start. This version makes upgrade safety
|
|
258
|
+
> and plugin compatibility detection first-class console mechanisms.
|
|
259
|
+
|
|
260
|
+
**Upgrade safety trio**
|
|
261
|
+
|
|
262
|
+
1. **Framework root detection**: locates the real install root (the top-level `node_modules`
|
|
263
|
+
containing `.pnpm`), fixing the `require.resolve`-returns-`.pnpm`-realpath root cause
|
|
264
|
+
(relink no-op / new package overwriting the old tree); refuses to upgrade when the root
|
|
265
|
+
cannot be identified (service stays running);
|
|
266
|
+
2. **Full-tree checkpoint before upgrade**: mirrors every `@deepseek-ai` version package in
|
|
267
|
+
`.pnpm` + the top-level scope + `lock.yaml`
|
|
268
|
+
(under `~/.dsh/plugin-console/framework-backups/<version>/fw-tree/`);
|
|
269
|
+
3. **Automatic full-tree rollback + retry**: install failure or relaunch failure restores
|
|
270
|
+
the pre-upgrade tree and relaunches automatically — no more "please run it manually"
|
|
271
|
+
dead-ends; after rollback, relink/repair steps are skipped to avoid damaging the
|
|
272
|
+
restored tree a second time.
|
|
273
|
+
|
|
274
|
+
**One-click rollback**: after an upgrade the framework card shows a **「回滚到上一版」** button
|
|
275
|
+
(`/plugin-console/framework-rollback`): stop service → restore full tree → relaunch →
|
|
276
|
+
health check, state visible throughout.
|
|
277
|
+
|
|
278
|
+
**Plugin adapt gate (compatibility detection)**
|
|
279
|
+
|
|
280
|
+
- After an upgrade, plugin entries recorded in `~/.dsh/plugin-console/compat-pending.json`
|
|
281
|
+
are **force-disabled**; the enable button is locked and the server rejects `/toggle` with
|
|
282
|
+
409 (unbypassable). The enabled-button label itself shows「待适配」as the state hint;
|
|
283
|
+
- The **details** panel explains the flow: **Check update → Update & adapt**;
|
|
284
|
+
- **Post-update auto-verification**: scans the latest `package.json` for an explicit
|
|
285
|
+
`dsh.engines.framework` / `engines.dsh` declaration plus `@deepseek-ai/*` dependency
|
|
286
|
+
ranges (built-in zero-dependency semver engine — strict npm prerelease rules for
|
|
287
|
+
dependencies, loose for explicit declarations); if the version changed and no
|
|
288
|
+
incompatibility was found, the `disabled` block is **removed automatically** and the row
|
|
289
|
+
unlocks; aggregator updates validate all synced subpackages too;
|
|
290
|
+
- **AI Empower pre-check**: during planning the server pre-checks the registry's latest
|
|
291
|
+
declaration and adapt-gate hits, injects them into the subagent prompt (the plan must
|
|
292
|
+
explain the conclusion to the user) and marks it on the plan panel (red for incompatible).
|
|
293
|
+
|
|
249
294
|
### Marketplace (multi-source)
|
|
250
295
|
|
|
251
296
|
- **Source switcher**: click the login pill to switch between **GitHub / Gitee / custom
|
|
@@ -349,6 +394,10 @@ GitHub Actions (every 6h, repo token)
|
|
|
349
394
|
- Supports the **DSH 0.1.0 series** (`0.1.0-rc.6` and siblings).
|
|
350
395
|
- The panel reads the running `@deepseek-ai/dsh-web-app` version: after a breaking upgrade
|
|
351
396
|
(0.2 / 1.0) a compatibility warning appears instead of silent failure.
|
|
397
|
+
- **Plugin adapt gate (v0.3.25)**: third-party plugins incompatible with the current
|
|
398
|
+
framework are force-disabled after an upgrade (enable locked); run 「Check update」to
|
|
399
|
+
upgrade the plugin first — it unlocks automatically after the auto-verification passes.
|
|
400
|
+
See 「Framework upgrade safety & plugin adapt gate」.
|
|
352
401
|
- Likely breaking seams: patch semantics, `webServer.register`, loader entry shape,
|
|
353
402
|
`dsh.client` bundle format, `settings.plugins.tab` slot.
|
|
354
403
|
- Deploy scripts do not check versions; the in-panel warning is authoritative.
|
|
@@ -390,6 +439,11 @@ test-harness.mjs Logic self-test (state/toggle/validation/loopback; search SKI
|
|
|
390
439
|
| `/plugin-console/gitee-oauth-url` / `gitee-oauth-callback` | GET | Gitee OAuth flow |
|
|
391
440
|
| `/plugin-console/ai-consent` | POST | Approve/cancel the AI-fallback step |
|
|
392
441
|
| `/plugin-console/restart` | POST | Watchdog-safe self-restart (panel button equivalent) |
|
|
442
|
+
| `/plugin-console/framework-upgrade` | POST | One-click framework upgrade (full-tree checkpoint → online install → rollback protection) |
|
|
443
|
+
| `/plugin-console/framework-rollback` | POST | One-click rollback to the previous version (stop → full-tree restore → auto relaunch → health check) |
|
|
444
|
+
| `/plugin-console/self-update` | POST | Update the console itself (latest npm tarball into the profile) |
|
|
445
|
+
| `/plugin-console/ai-empower/plan` / `status` / `run` / `cancel` | POST | AI Empower: plan (with framework compat pre-check) → status polling → confirmed run → cancel |
|
|
446
|
+
| `/plugin-console/components` / `component/start` / `stop` / `status` | GET/POST | Server component registry and start/stop/status |
|
|
393
447
|
|
|
394
448
|
---
|
|
395
449
|
|
package/README.zh.md
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
# DSH 插件中心(dsh-plugin-hub)
|
|
8
8
|
|
|
9
|
-
> ## 🎉 v0.3.
|
|
9
|
+
> ## 🎉 v0.3.25 正式发布 — 框架升级安全与插件适配门(2026-09-04)
|
|
10
10
|
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
11
|
+
> **升级安全三件套**:框架安装根识别(修复 `.pnpm` realpath 混版本根因)、升级前**全树 checkpoint**、
|
|
12
|
+
> 拉起失败**自动全树回滚**;升级后框架卡片**一键回滚到上一版**。
|
|
13
|
+
> **插件适配门**:升级后不兼容插件强制禁用(启用锁定),「检测更新 → 更新并适配」→
|
|
14
|
+
> 自动校验声明/依赖,通过即解锁;AI 赋能规划时自动预检框架适配并给出说明。
|
|
14
15
|
>
|
|
15
16
|
> **安装:**
|
|
16
17
|
> ```bash
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
| | 优势 | 说明 |
|
|
76
77
|
|---|---|---|
|
|
77
78
|
| 🤖 | **AI 赋能(v0.3.24)** | 输入 npm 包名 / GitHub 仓库,本地 AI 读文档生成**部署计划**(安装/写配置/启动服务/健康检查),你确认后安全执行;服务器类组件自动生成控制卡片 |
|
|
79
|
+
| 🛡️ | **框架升级适配门与安全(v0.3.25)** | 升级后不兼容插件强制禁用(适配门锁定,「检测更新 → 更新并适配 → 自动校验解锁」);升级前全树 checkpoint、拉起失败自动全树回滚、框架卡片一键回滚到上一版 |
|
|
78
80
|
| 🚀 | **服务器组件卡片** | 左侧浮卡与主面板顶边对齐:启动 / 停止 / 状态 / 【打开】直达 Web UI,多服务器下拉、可折叠(状态记忆) |
|
|
79
81
|
| 🧩 | **插件 + 技能双市场** | 自动收录 `dsh-plugin` topic 仓库(按 star **500+**),另有**技能 tab**(`agent-skills` ∪ `claude-skills` ∪ `dsh-skill`,最多 300)——浏览、搜索、一键安装,零 GitHub API 调用;插件自带技能(如 openviking-memory)只读展示 |
|
|
80
82
|
| 🤖 | **自动收录 CI** | GitHub Actions 每 6 小时自动重跑 `build-index`(也支持手动触发);作者只需给自己的仓库打 `dsh-plugin` / `agent-skills` / `claude-skills` / `dsh-skill` 标签,**无需申请、无需审核** |
|
|
@@ -227,6 +229,37 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
227
229
|
- **卡片关闭语义**:终态(成功/失败)点叉号永久关闭(持久化);进行中点叉号仅本次会话
|
|
228
230
|
隐藏,刷新后恢复显示。
|
|
229
231
|
|
|
232
|
+
### 框架升级适配门与升级安全(v0.3.25)
|
|
233
|
+
|
|
234
|
+
> 缘起:2026-09-04 DSH `0.1.1-rc.2 → 0.1.2-rc.1` 升级事故——升级脚本在错误的工作目录执行,
|
|
235
|
+
> 把新 CLI 原位覆盖进旧 `.pnpm` 目录、依赖树混版本,服务无法拉起。本版本把「升级安全」与
|
|
236
|
+
> 「插件兼容检测」固化为控制台机制。
|
|
237
|
+
|
|
238
|
+
**升级安全三件套**
|
|
239
|
+
|
|
240
|
+
1. **框架安装根识别**:定位真正的框架安装根(含 `.pnpm` 的顶层 `node_modules`),修复
|
|
241
|
+
`require.resolve` 返回 `.pnpm` 内部 realpath 导致的「重链空转 / 新包覆盖旧目录」根因;
|
|
242
|
+
定位不到时**拒绝升级**(不 kill 服务、服务保持运行);
|
|
243
|
+
2. **升级前全树 checkpoint**:镜像 `.pnpm` 全部 `@deepseek-ai` 版本包 + 顶层 scope +
|
|
244
|
+
`lock.yaml`(写入 `~/.dsh/plugin-console/framework-backups/<版本>/fw-tree/`);
|
|
245
|
+
3. **拉起失败自动全树回滚并重试**:安装失败或服务拉起失败都会自动恢复升级前全树并再次拉起,
|
|
246
|
+
不再只留「请手动运行」提示;回滚后跳过重链/修复(避免对已恢复旧树二次破坏)。
|
|
247
|
+
|
|
248
|
+
**一键回滚**:升级后框架卡片出现「回滚到上一版」按钮(`/plugin-console/framework-rollback`):
|
|
249
|
+
停服 → 全树恢复 → 自动拉起 → 健康检测,全程状态可见。
|
|
250
|
+
|
|
251
|
+
**插件适配门(插件检测)**
|
|
252
|
+
|
|
253
|
+
- 升级后写入 `~/.dsh/plugin-console/compat-pending.json` 的插件行被**强制禁用**,启用按钮
|
|
254
|
+
锁定(服务端 `/toggle` 直接 409,无法绕过);行状态以按钮文案「待适配」提示;
|
|
255
|
+
- 「详情」面板给出说明与入口:**检测更新 → 更新并适配**;
|
|
256
|
+
- **更新后自动校验**:扫描最新版 `package.json` 的 `dsh.engines.framework` / `engines.dsh`
|
|
257
|
+
显式声明 + `@deepseek-ai/*` 依赖范围(内置零依赖 semver 判定器:依赖判定遵循 npm
|
|
258
|
+
prerelease 严格规则、声明判定宽松);版本已变化且未判定不兼容 → **自动移除禁用块解锁**;
|
|
259
|
+
聚合包更新连带校验同步的子包;
|
|
260
|
+
- **AI 赋能适配预检**:规划时服务端预检 registry 最新版声明与适配门命中情况,注入子代理
|
|
261
|
+
提示词(要求计划向用户解释适配结论),并在计划面板标注(不兼容标红)。
|
|
262
|
+
|
|
230
263
|
### 插件市场(多搜索源)
|
|
231
264
|
|
|
232
265
|
- **搜索源切换**:点击顶部登录态标识弹出菜单,在 **GitHub / Gitee / 自定义源** 间切换(选择持久化);
|
|
@@ -323,6 +356,9 @@ GitHub Actions(每 6 小时,仓库自带 token)
|
|
|
323
356
|
- 当前支持 **DSH 0.1.0 系列**(`0.1.0-rc.6` 及同系列版本)。
|
|
324
357
|
- 面板会读取运行中的 `@deepseek-ai/dsh-web-app` 版本:官方发布破坏性升级
|
|
325
358
|
(0.2 / 1.0 等)后,面板顶部会显示兼容性警告并给出本仓库地址,而不是默默失效。
|
|
359
|
+
- **插件适配门(v0.3.25)**:框架升级后不兼容的第三方插件会被**强制禁用**(启用按钮锁定);
|
|
360
|
+
请先「检测更新」升级该插件,更新并通过自动校验后才会解锁——详见
|
|
361
|
+
「框架升级适配门与升级安全」。
|
|
326
362
|
- 官方破坏性更新可能改动的接口:补丁层语义、`webServer.register`、
|
|
327
363
|
加载器条目结构、`dsh.client` bundle 格式、`settings.plugins.tab` 插槽。
|
|
328
364
|
届时随官方版本更新本仓库即可(依赖面已收窄到上述几个点)。
|
|
@@ -341,6 +377,7 @@ scripts/apply-framework-patch.cjs 框架层补丁(issue #5,幂等,可重
|
|
|
341
377
|
marketplace/index.json 生成的静态索引(jsDelivr CDN 分发)
|
|
342
378
|
deploy.ps1 / deploy.sh 一键部署脚本(Windows / Linux·macOS)
|
|
343
379
|
test-harness.mjs 逻辑自检(state/toggle/校验/环回保护;搜索视网络环境 SKIP)
|
|
380
|
+
test-compat-gate.mjs 适配门单元测试(内置 semver 判定器:声明/依赖兼容规则)
|
|
344
381
|
```
|
|
345
382
|
<img width="1878" height="945" alt="image" src="https://github.com/user-attachments/assets/b26f2f19-0ba4-4be7-9ca1-b3fd4c51a7a8" />
|
|
346
383
|
|
|
@@ -365,6 +402,11 @@ test-harness.mjs 逻辑自检(state/toggle/校验/环回保护;搜索视
|
|
|
365
402
|
| `/plugin-console/gitee-oauth-url` / `gitee-oauth-callback` | GET | Gitee OAuth 流程 |
|
|
366
403
|
| `/plugin-console/ai-consent` | POST | 同意/取消 AI 兜底步骤 |
|
|
367
404
|
| `/plugin-console/restart` | POST | 自带守护的安全自重启(等同面板按钮) |
|
|
405
|
+
| `/plugin-console/framework-upgrade` | POST | 框架一键升级(全树 checkpoint → 在线安装 → 全树回滚保护) |
|
|
406
|
+
| `/plugin-console/framework-rollback` | POST | 一键回滚到升级前版本(停服 → 全树恢复 → 自动拉起 → 健康检测) |
|
|
407
|
+
| `/plugin-console/self-update` | POST | 控制台自身一键更新(npm 最新 tarball 到当前 profile) |
|
|
408
|
+
| `/plugin-console/ai-empower/plan` / `status` / `run` / `cancel` | POST | AI 赋能:规划(含框架适配预检)→ 状态轮询 → 确认执行 → 取消 |
|
|
409
|
+
| `/plugin-console/components` / `component/start` / `stop` / `status` | GET/POST | 服务器组件清单与启动/停止/状态 |
|
|
368
410
|
|
|
369
411
|
---
|
|
370
412
|
|
package/lib/client.js
CHANGED
|
@@ -1396,7 +1396,29 @@ window.__ModuleLoader__.load({
|
|
|
1396
1396
|
// 挂载时加载静态插件索引(GitHub 源空查询时秒开浏览,零 API 调用)
|
|
1397
1397
|
react.useEffect(() => {
|
|
1398
1398
|
call("/plugin-console/market-index").then(
|
|
1399
|
-
(data) => {
|
|
1399
|
+
(data) => {
|
|
1400
|
+
if (data && Array.isArray(data.items)) {
|
|
1401
|
+
setIndexData({ items: data.items, skills: Array.isArray(data.skills) ? data.skills : [], loaded: true });
|
|
1402
|
+
// ★「只看官方」需要官方标记:索引条目补标(网络/服务端此前判定失败时不动已有标记,避免 null 覆盖)
|
|
1403
|
+
// 服务端 /enrich 已带 24h 结果缓存时,此处仅兜底浏览器直连失败部分。
|
|
1404
|
+
const base = data.items;
|
|
1405
|
+
enrichOfficialBundle(base).then((enriched) => {
|
|
1406
|
+
if (!Array.isArray(enriched)) return;
|
|
1407
|
+
const merged = enriched.map((e, i) => {
|
|
1408
|
+
const b = base[i] ?? {};
|
|
1409
|
+
return {
|
|
1410
|
+
...e,
|
|
1411
|
+
official: e.official !== null ? e.official : (b.official ?? null),
|
|
1412
|
+
aggregate: e.aggregate === true || b.aggregate === true,
|
|
1413
|
+
aggregateInstallable: b.aggregateInstallable === true,
|
|
1414
|
+
hasSkill: e.hasSkill === true || b.hasSkill === true,
|
|
1415
|
+
hasSuite: e.hasSuite === true || b.hasSuite === true,
|
|
1416
|
+
};
|
|
1417
|
+
});
|
|
1418
|
+
setIndexData((prev) => (prev !== null && prev.loaded === true ? { ...prev, items: merged } : prev));
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1400
1422
|
() => {},
|
|
1401
1423
|
);
|
|
1402
1424
|
// 市场状态记忆:关闭前搜索过 → 重开时自动恢复该搜索(query 已从 localStorage 初始化)
|
package/lib/index.js
CHANGED
|
@@ -293,19 +293,67 @@ function semverRangeMatchLoose(versionText, rangeText) {
|
|
|
293
293
|
return semverRangeMatch(`${v.major}.${v.minor}.${v.patch}`, rangeText)
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* 扫描已安装包源码,检测对已删除的 dsh-settings API 的引用
|
|
298
|
+
* (2026-09-04 教训:0.1.2-rc.1 起 settingsNamespace / installSettingsSection 已删除,
|
|
299
|
+
* 静态声明检查判 pass 是假通过;真实兼容性只有模块 import 时见分晓,而 loader 单行失败
|
|
300
|
+
* = 整个服务启动崩溃)。返回命中的符号列表,空数组 = 干净。
|
|
301
|
+
*/
|
|
302
|
+
function scanSettingsApiUsage(pkgDir) {
|
|
303
|
+
const found = []
|
|
304
|
+
const seen = new Set()
|
|
305
|
+
const walk = (dir, depth) => {
|
|
306
|
+
if (depth > 3) return
|
|
307
|
+
let entries = []
|
|
308
|
+
try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const full = join(dir, entry.name)
|
|
311
|
+
if (entry.isDirectory()) {
|
|
312
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
|
|
313
|
+
walk(full, depth + 1)
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
if (!entry.isFile() || !/\.(?:js|mjs|cjs)$/u.test(entry.name)) continue
|
|
317
|
+
if (seen.has(full)) continue
|
|
318
|
+
seen.add(full)
|
|
319
|
+
if (seen.size > 120) return
|
|
320
|
+
try {
|
|
321
|
+
const text = readFileSync(full, 'utf8').slice(0, 400000)
|
|
322
|
+
for (const sym of ['settingsNamespace', 'installSettingsSection']) {
|
|
323
|
+
if (text.includes(sym) && !found.includes(sym)) found.push(sym)
|
|
324
|
+
}
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
walk(pkgDir, 0)
|
|
329
|
+
return found
|
|
330
|
+
}
|
|
331
|
+
|
|
296
332
|
/**
|
|
297
333
|
* 插件-框架兼容校验(框架升级适配门):
|
|
298
334
|
* 1) 显式兼容声明(dsh.engines.framework / dsh.compat.framework / engines.dsh):以声明为准;
|
|
299
|
-
* 2)
|
|
300
|
-
* 3)
|
|
335
|
+
* 2) 扫描依赖/peerDeps 的 @deepseek-ai/*:任一范围明确不含框架版本 → fail;
|
|
336
|
+
* 3) 【硬判据】提供 pkgDir 时扫描包源码:命中已删除的 dsh-settings API
|
|
337
|
+
* (settingsNamespace / installSettingsSection)→ 直接 fail(2026-09-04 教训:
|
|
338
|
+
* 0.3.6 全家仍引用旧 API,静态声明/依赖检查判 pass 是假通过,loader 单行失败 = 服务崩溃);
|
|
339
|
+
* 4) 无声明且依赖全部满足 → unknown(新版变动不大;由调用方在「版本已变化」前提下放行)。
|
|
301
340
|
* 返回 { decision: 'pass'|'fail'|'unknown', reason }。
|
|
302
341
|
*/
|
|
303
|
-
function checkPluginFrameworkCompat(pkg, frameworkVersion) {
|
|
342
|
+
function checkPluginFrameworkCompat(pkg, frameworkVersion, pkgDir = null) {
|
|
304
343
|
const declared = pkg?.dsh?.engines?.framework ?? pkg?.dsh?.compat?.framework ?? pkg?.engines?.dsh ?? null
|
|
305
344
|
if (typeof declared === 'string' && declared !== '') {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
345
|
+
const ok = semverRangeMatchLoose(frameworkVersion, declared)
|
|
346
|
+
if (!ok) return { decision: 'fail', reason: `声明兼容范围 ${declared} 不满足当前框架 ${frameworkVersion}` }
|
|
347
|
+
}
|
|
348
|
+
// 硬判据:实际源码扫描(比声明/依赖更接近真实兼容性)
|
|
349
|
+
if (pkgDir !== null && typeof pkgDir === 'string' && semverRangeMatchLoose(frameworkVersion, '>=0.1.2')) {
|
|
350
|
+
const broken = scanSettingsApiUsage(pkgDir)
|
|
351
|
+
if (broken.length > 0) {
|
|
352
|
+
return { decision: 'fail', reason: `源码仍引用 0.1.2 起已删除的 dsh-settings API(${broken.join('、')})——实际不兼容,启用会让整个服务启动崩溃` }
|
|
353
|
+
}
|
|
354
|
+
if (typeof declared === 'string' && declared !== '') return { decision: 'pass', reason: `声明兼容范围 ${declared} 满足框架 ${frameworkVersion},且源码无已删除 API 引用` }
|
|
355
|
+
} else if (typeof declared === 'string' && declared !== '') {
|
|
356
|
+
return { decision: 'pass', reason: `声明兼容范围 ${declared} 满足框架 ${frameworkVersion}` }
|
|
309
357
|
}
|
|
310
358
|
const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.peerDependencies ?? {}), ...(pkg?.optionalDependencies ?? {}) }
|
|
311
359
|
const hits = Object.entries(deps).filter(([name]) => /^@deepseek-ai\//u.test(name))
|
|
@@ -1485,56 +1533,114 @@ async function fetchSkillMeta(repo, branch, skillDir) {
|
|
|
1485
1533
|
* 全部条目并发 + raw 主站与镜像双通道竞速,单条 4 秒封顶。
|
|
1486
1534
|
* 聚合仓库(根包 private+workspaces)额外检查子包是否有 dsh.bundle 清单:
|
|
1487
1535
|
* 任一子包可 `dsh plugin add` 直装 → aggregateInstallable = true(★ 筛选会包含它)。 */
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1536
|
+
const ENRICH_CACHE_FILE = () => join(dshHome(), 'plugin-console', 'enrich-cache.json')
|
|
1537
|
+
const ENRICH_CACHE_TTL = 24 * 60 * 60 * 1000
|
|
1538
|
+
|
|
1539
|
+
function readEnrichCache() {
|
|
1540
|
+
try {
|
|
1541
|
+
const j = JSON.parse(readFileSync(ENRICH_CACHE_FILE(), 'utf8'))
|
|
1542
|
+
return j !== null && typeof j === 'object' ? j : {}
|
|
1543
|
+
} catch { return {} }
|
|
1544
|
+
}
|
|
1545
|
+
function writeEnrichCache(cache) {
|
|
1546
|
+
try {
|
|
1547
|
+
mkdirSync(dirname(ENRICH_CACHE_FILE()), { recursive: true })
|
|
1548
|
+
const entries = Object.entries(cache).sort((a, b) => (b[1]?.at ?? 0) - (a[1]?.at ?? 0)).slice(0, 2000)
|
|
1549
|
+
writeFileSync(ENRICH_CACHE_FILE(), JSON.stringify(Object.fromEntries(entries), null, 2), 'utf8')
|
|
1550
|
+
} catch {}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/** 单项识别:官方通道 / 聚合仓库 / 技能 / 套装(失败返回 official=null)。 */
|
|
1554
|
+
async function enrichItemOne(item) {
|
|
1555
|
+
let official = /^deepseek-ai\//u.test(item.fullName ?? '') ? true : null
|
|
1556
|
+
let aggregate = false
|
|
1557
|
+
let aggregateInstallable = false
|
|
1558
|
+
let hasSkill = false
|
|
1559
|
+
let hasSuite = false
|
|
1560
|
+
try {
|
|
1561
|
+
const branch = item.defaultBranch ?? 'main'
|
|
1562
|
+
const base = `https://raw.githubusercontent.com/${item.fullName}/${encodeURIComponent(branch)}/package.json`
|
|
1563
|
+
const [pkgResult, skillResult, suiteResult] = await Promise.allSettled([
|
|
1564
|
+
Promise.any([
|
|
1565
|
+
curlText(base, 4000),
|
|
1566
|
+
curlText(`https://ghproxy.net/${base}`, 4000),
|
|
1567
|
+
]),
|
|
1568
|
+
detectSkillRepo(item.fullName, branch),
|
|
1569
|
+
rawTextWithFallback(item.fullName, branch, '.gitmodules'),
|
|
1570
|
+
])
|
|
1571
|
+
if (suiteResult.status === 'fulfilled' && suiteResult.value !== null) {
|
|
1572
|
+
hasSuite = true
|
|
1573
|
+
}
|
|
1574
|
+
if (pkgResult.status === 'fulfilled') {
|
|
1575
|
+
const pkg = JSON.parse(pkgResult.value)
|
|
1576
|
+
if (typeof pkg.dsh?.bundle?.patch === 'string') {
|
|
1577
|
+
official = true
|
|
1578
|
+
} else if (pkg.private === true && (Array.isArray(pkg.workspaces) || /(^|-)dsh[-/]/u.test(String(pkg.name ?? '')))) {
|
|
1579
|
+
aggregate = true
|
|
1580
|
+
try {
|
|
1581
|
+
const tree = await curlJson(`https://api.github.com/repos/${item.fullName}/git/trees/${encodeURIComponent(branch)}?recursive=1`, 6000)
|
|
1582
|
+
const pkgPaths = (tree.tree ?? [])
|
|
1583
|
+
.filter((n) => n.type === 'blob' && /^packages\/[^/]+\/package\.json$/u.test(n.path))
|
|
1584
|
+
.map((n) => n.path)
|
|
1585
|
+
.slice(0, 12)
|
|
1586
|
+
if (pkgPaths.length > 0) {
|
|
1587
|
+
const subs = await Promise.all(pkgPaths.map((p) => curlText(`https://raw.githubusercontent.com/${item.fullName}/${encodeURIComponent(branch)}/${p}`, 4000)
|
|
1588
|
+
.then((t) => { try { return JSON.parse(t) } catch { return null } })
|
|
1589
|
+
.catch(() => null)))
|
|
1590
|
+
if (subs.some((sp) => sp && typeof sp.dsh?.bundle?.patch === 'string')) {
|
|
1591
|
+
aggregateInstallable = true
|
|
1528
1592
|
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1593
|
+
}
|
|
1594
|
+
} catch {}
|
|
1531
1595
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1596
|
+
}
|
|
1597
|
+
if (skillResult.status === 'fulfilled' && skillResult.value?.hasSkill === true) {
|
|
1598
|
+
hasSkill = true
|
|
1599
|
+
}
|
|
1600
|
+
} catch {}
|
|
1601
|
+
return { ...item, official, aggregate, aggregateInstallable, hasSkill, hasSuite }
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
/**
|
|
1605
|
+
* 批量识别(★ 筛选数据源):并发限流 + 24h 结果缓存。
|
|
1606
|
+
* 网络黑洞期(raw.githubusercontent 大部分拉取失败)自动回退缓存中的上次判定,
|
|
1607
|
+
* 保证「只看官方」不因瞬时网络而坍缩成 0/1 条。
|
|
1608
|
+
*/
|
|
1609
|
+
async function enrichItems(items) {
|
|
1610
|
+
const cache = readEnrichCache()
|
|
1611
|
+
const out = new Array(items.length)
|
|
1612
|
+
let next = 0
|
|
1613
|
+
let cacheDirty = false
|
|
1614
|
+
async function worker() {
|
|
1615
|
+
while (true) {
|
|
1616
|
+
const i = next
|
|
1617
|
+
next += 1
|
|
1618
|
+
if (i >= items.length) return
|
|
1619
|
+
const item = items[i]
|
|
1620
|
+
const key = `${item.fullName}@${item.defaultBranch ?? 'main'}`
|
|
1621
|
+
const cached = cache[key]
|
|
1622
|
+
try {
|
|
1623
|
+
if (cached !== undefined && typeof cached?.at === 'number' && Date.now() - cached.at < ENRICH_CACHE_TTL && cached.data && (cached.data.official !== null || item.fullName.startsWith('deepseek-ai/'))) {
|
|
1624
|
+
out[i] = { ...item, ...cached.data }
|
|
1625
|
+
continue
|
|
1626
|
+
}
|
|
1627
|
+
let result = await enrichItemOne(item)
|
|
1628
|
+
// 本次失败 → 回退缓存(哪怕已过期),避免「看天吃饭」
|
|
1629
|
+
if ((result.official === null && !/^deepseek-ai\//u.test(item.fullName ?? '')) && cached?.data) {
|
|
1630
|
+
result = { ...item, ...cached.data }
|
|
1631
|
+
} else {
|
|
1632
|
+
cache[key] = { at: Date.now(), data: { official: result.official, aggregate: result.aggregate, aggregateInstallable: result.aggregateInstallable, hasSkill: result.hasSkill, hasSuite: result.hasSuite } }
|
|
1633
|
+
cacheDirty = true
|
|
1634
|
+
}
|
|
1635
|
+
out[i] = result
|
|
1636
|
+
} catch {
|
|
1637
|
+
out[i] = cached?.data ? { ...item, ...cached.data } : { ...item, official: null, aggregate: false, aggregateInstallable: false, hasSkill: false, hasSuite: false }
|
|
1534
1638
|
}
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
}))
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
await Promise.all(Array.from({ length: Math.min(12, items.length) }, worker))
|
|
1642
|
+
if (cacheDirty) writeEnrichCache(cache)
|
|
1643
|
+
return out
|
|
1538
1644
|
}
|
|
1539
1645
|
|
|
1540
1646
|
function githubRepoInfo(repo) {
|
|
@@ -3396,14 +3502,16 @@ async function maybeAutoAdaptCompat({ profileDir, packageName, syncedNames, patc
|
|
|
3396
3502
|
const kept = []
|
|
3397
3503
|
for (const p of targets) {
|
|
3398
3504
|
let pkg = null
|
|
3505
|
+
let pkgDir = null
|
|
3399
3506
|
try {
|
|
3400
3507
|
const require = createRequire(join(profileDir, 'package.json'))
|
|
3401
3508
|
const pkgPath = require.resolve(`${p.moduleName}/package.json`)
|
|
3402
3509
|
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
3510
|
+
pkgDir = dirname(pkgPath)
|
|
3403
3511
|
} catch {}
|
|
3404
3512
|
const version = typeof pkg?.version === 'string' ? pkg.version : null
|
|
3405
3513
|
const changed = version !== null && version !== p.version
|
|
3406
|
-
const check = pkg !== null ? checkPluginFrameworkCompat(pkg, fwVer) : { decision: 'unknown', reason: '无法读取包信息' }
|
|
3514
|
+
const check = pkg !== null ? checkPluginFrameworkCompat(pkg, fwVer, pkgDir) : { decision: 'unknown', reason: '无法读取包信息' }
|
|
3407
3515
|
if (changed && check.decision !== 'fail') {
|
|
3408
3516
|
try { await removeDisableBlock(patchPath, p.rowId) } catch {}
|
|
3409
3517
|
p.status = 'adopted'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noob-stupid/dsh-plugin-console",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|