@noob-stupid/dsh-plugin-console 0.5.30 → 0.5.32

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.
Files changed (45) hide show
  1. package/README.md +39 -7
  2. package/README.zh.md +40 -8
  3. package/SECURITY.md +2 -2
  4. package/lib/client.js +17 -3
  5. package/lib/index.js +141 -141
  6. package/lib/server/domain/ai-run.js +1 -1
  7. package/lib/server/domain/ai.js +39 -11
  8. package/lib/server/domain/auto-disable.js +226 -0
  9. package/lib/server/domain/auto-preflight-run.js +184 -0
  10. package/lib/server/domain/auto-preflight.js +345 -0
  11. package/lib/server/domain/compat-state.js +57 -8
  12. package/lib/server/domain/compat.js +7 -3
  13. package/lib/server/domain/components.js +1 -1
  14. package/lib/server/domain/dep-source.js +1 -1
  15. package/lib/server/domain/framework.js +65 -6
  16. package/lib/server/domain/install-job.js +1 -1
  17. package/lib/server/domain/install.js +1 -1
  18. package/lib/server/domain/jobs.js +1 -1
  19. package/lib/server/domain/market.js +1 -1
  20. package/lib/server/domain/patch-yaml-check.js +151 -0
  21. package/lib/server/domain/patch.js +1 -1
  22. package/lib/server/domain/presets.js +1 -1
  23. package/lib/server/domain/release-source.js +3 -3
  24. package/lib/server/domain/repoland.js +1 -1
  25. package/lib/server/domain/runtime.js +1 -1
  26. package/lib/server/domain/safe-boot.js +520 -0
  27. package/lib/server/domain/skills.js +1 -1
  28. package/lib/server/domain/sources.js +1 -1
  29. package/lib/server/domain/suite.js +1 -1
  30. package/lib/server/infra/exec.js +2 -2
  31. package/lib/server/infra/fsx.js +3 -3
  32. package/lib/server/infra/http.js +1 -1
  33. package/lib/server/infra/httpd.js +1 -1
  34. package/lib/server/infra/mask.js +1 -1
  35. package/lib/server/infra/paths.js +1 -1
  36. package/lib/server/infra/semver.js +1 -1
  37. package/lib/server/routes/compat.js +240 -11
  38. package/lib/server/routes/components.js +1 -1
  39. package/lib/server/routes/framework-preflight.js +14 -1
  40. package/lib/server/routes/framework-upgrade.js +16 -17
  41. package/lib/server/routes/framework.js +18 -2
  42. package/lib/server/routes/github-login.js +2 -2
  43. package/lib/server/routes/index.js +8 -1
  44. package/lib/server/routes/safe-boot.js +139 -0
  45. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,7 @@
66
66
  dsh plugin --profile web add @noob-stupid/dsh-plugin-console
67
67
 
68
68
  # or install from GitHub source (needs git; allowBuilds authorization on first add)
69
- dsh plugin --profile web add github:Noob-stupid/dsh-plugin-hub
69
+ dsh plugin --profile web add github:Noob-stupid/dsh-plugin-gating-hub
70
70
  ```
71
71
 
72
72
  Then restart the dsh service → refresh the page → **Settings → Plugins → Plugin Console**.
@@ -78,13 +78,13 @@ Then restart the dsh service → refresh the page → **Settings → Plugins →
78
78
  Windows (PowerShell):
79
79
 
80
80
  ```powershell
81
- git clone https://github.com/Noob-stupid/dsh-plugin-hub "$env:TEMP\dsh-plugin-console" 2>$null; & "$env:TEMP\dsh-plugin-console\deploy.ps1"
81
+ git clone https://github.com/Noob-stupid/dsh-plugin-gating-hub "$env:TEMP\dsh-plugin-console" 2>$null; & "$env:TEMP\dsh-plugin-console\deploy.ps1"
82
82
  ```
83
83
 
84
84
  Linux / macOS:
85
85
 
86
86
  ```bash
87
- git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console 2>/dev/null; bash /tmp/dsh-plugin-console/deploy.sh
87
+ git clone https://github.com/Noob-stupid/dsh-plugin-gating-hub /tmp/dsh-plugin-console 2>/dev/null; bash /tmp/dsh-plugin-console/deploy.sh
88
88
  ```
89
89
 
90
90
  The script copies the plugin into `$DSH_HOME/profiles/<profile>/node_modules/` and
@@ -96,7 +96,7 @@ idempotently appends an enable entry to `cordis.patch.yml`. Afterwards:
96
96
 
97
97
  ### Option 3: hand it to an AI in one sentence
98
98
 
99
- > Install the DSH plugin hub (dsh-plugin-hub): run `dsh plugin --profile web add @noob-stupid/dsh-plugin-console` (npm release); if there is no dsh CLI, clone https://github.com/Noob-stupid/dsh-plugin-hub to `~/.dsh/profiles/web/node_modules/` and register it in `cordis.patch.yml` (id: plugin-console, name: @noob-stupid/dsh-plugin-console). Restart dsh web afterwards.
99
+ > Install the DSH plugin hub (dsh-plugin-hub): run `dsh plugin --profile web add @noob-stupid/dsh-plugin-console` (npm release); if there is no dsh CLI, clone https://github.com/Noob-stupid/dsh-plugin-gating-hub to `~/.dsh/profiles/web/node_modules/` and register it in `cordis.patch.yml` (id: plugin-console, name: @noob-stupid/dsh-plugin-console). Restart dsh web afterwards.
100
100
 
101
101
  Requires: DSH ≥ 0.1.0-rc.6 (web profile, with `dsh-client-modules` / `dsh-host-plugin-inventory`).
102
102
 
@@ -311,8 +311,40 @@ The mode decides **who leads framework upgrades** — nothing else:
311
311
 
312
312
  - **Observer (default)**: the console only preflights, gates, and guards the rollback point. It does not take over upgrades.
313
313
  - **Managed**: framework upgrade and rollback are owned by this console. It switches to managed automatically when you upgrade through this console, and records the reason.
314
+ - **Switches back to Observer automatically (0.5.32)**: when the **desktop-host shape** is detected (the existing `detectHostShape()` criteria), a mode that was **not set by hand** (`compat-mode.json` `source` is `auto:*`; a missing field counts as `manual`) is switched back to Observer on startup / first status query, with a one-line notice. **Once you switch it by hand it is never changed automatically again**; when it is already Observer nothing is written.
314
315
 
315
316
  **Boot-failure quarantine is not affected by the mode.** When the service cannot start, the console locates the culprit from the boot log and disables it (broken presets are renamed to `.broken-*`; with no clear culprit it falls back to safe mode so the service can start). A console that cannot boot cannot gate anything — so this rescue path always runs.
317
+
318
+ **Auto-disable on confirmed evidence (0.5.32)**: when the fingerprint-triggered auto-preflight, or a
319
+ boot-failure record, provides **confirmed evidence** (the row's package resolves on **none** of the
320
+ resolution bases, a `file://` target truly does not exist, or the boot log names the row), the console
321
+ writes `disabled: true` for those rows **itself** (no click needed). Safety rails: **only rows with
322
+ evidence**; `@deepseek-ai/*` (framework-owned) and protected/core rows are **never** auto-disabled;
323
+ a **safe-boot snapshot is written first**, so any auto-disable is one command away from a rollback;
324
+ when there is nothing to write, **not a single byte is written**; every auto-disable leaves a record in
325
+ `dshHome()/plugin-console/auto-disable.log` (time / row id / evidence / restore command).
326
+
327
+ Prefer "report only, let me click" (enabled by default) — replace `<host:port>` with your instance
328
+ (the default `dsh web` profile listens on `127.0.0.1:3080`):
329
+
330
+ ```bash
331
+ curl -X POST http://<host:port>/plugin-console/compat-ack \
332
+ -H 'Content-Type: application/json' -d '{"autoDisableOnEvidence":false}'
333
+ ```
334
+
335
+ **How to roll an auto-disable back**: the route `POST /plugin-console/safe-boot` with
336
+ `{"action":"restore-last-good"}` (the current state is backed up first), or — when the service will not
337
+ start — from the plugin directory:
338
+
339
+ ```bash
340
+ node scripts/safe-boot.mjs --list # see which snapshots exist
341
+ node scripts/safe-boot.mjs --restore-last-good # restore the newest good snapshot byte-for-byte
342
+ node scripts/safe-boot.mjs --disable-suspects # or: append disabled: true to named rows only (nothing deleted)
343
+ node scripts/safe-boot.mjs --mark-good # mark a snapshot as good (never removed by retention)
344
+ ```
345
+
346
+ Safe-boot snapshots live under `dshHome()/plugin-console/safe-boot/` (**not** in the profile directory),
347
+ keeping the newest 3 plus the one you marked as good.
316
348
  ## Framework compatibility
317
349
 
318
350
  Measured on isolated instances (fresh `DSH_HOME`, real HTTP probes) — **supported: framework ≥ `0.1.5-rc.2`**, no workarounds needed.
@@ -384,7 +416,7 @@ If this panel saves you time or makes DSH more fun to use:
384
416
  - ⭐ **Star this repo** — it directly helps more DSH users find it;
385
417
  - Share it with DSH users or in DSH communities;
386
418
  - Submit your own plugin (tag it `dsh-plugin`) to grow the ecosystem;
387
- - Found a bug or want a feature? [Open an issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues).
419
+ - Found a bug or want a feature? [Open an issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues).
388
420
 
389
421
  ---
390
422
 
@@ -395,7 +427,7 @@ Contributions of all kinds are welcome — issues, PRs, docs, translations.
395
427
  - **Guidelines**: [CONTRIBUTING.md](CONTRIBUTING.md)
396
428
  - **Code of Conduct**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
397
429
  - **Security policy**: [SECURITY.md](SECURITY.md) (report vulnerabilities privately)
398
- - **Issue templates**: bug reports & feature requests via the [new issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues/new/choose) page
430
+ - **Issue templates**: bug reports & feature requests via the [new issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues/new/choose) page
399
431
 
400
432
  ---
401
433
 
@@ -420,4 +452,4 @@ The layered-refactor preview lives in its **own repository**: [**`Noob-stupid/ds
420
452
  - **What it is**: the monolithic `lib/index.js` (8547 lines) split into layers — `lib/index.js` **141 lines** + `lib/server/**` **38 modules**; **feature-equivalent to `0.3.57`**, maintainability only;
421
453
  - **Verified**: 19 test suites green · 8 architecture-guard assertions · **route inventory identical** (`status` + response fields) against the stable build;
422
454
  - **Not yet exercised**: real framework upgrade / rollback, restart guardian, component start/stop, real AI run, Gitee OAuth (~10–25% long-tail risk, subjective);
423
- - **For everyday use** stick to npm `@noob-stupid/dsh-plugin-console@0.3.67`, or `main` of this repo.
455
+ - **For everyday use** stick to the **latest npm release** of `@noob-stupid/dsh-plugin-console` (`npm view @noob-stupid/dsh-plugin-console version`), or `main` of this repo.
package/README.zh.md CHANGED
@@ -45,7 +45,7 @@
45
45
  dsh plugin --profile web add @noob-stupid/dsh-plugin-console
46
46
 
47
47
  # 或直接装 GitHub 源码(需 git,首次需 allowBuilds 授权)
48
- dsh plugin --profile web add github:Noob-stupid/dsh-plugin-hub
48
+ dsh plugin --profile web add github:Noob-stupid/dsh-plugin-gating-hub
49
49
  ```
50
50
 
51
51
  然后重启 dsh 服务 → 刷新页面 → **设置 → 插件 → 插件控制台**。
@@ -57,13 +57,13 @@ dsh plugin --profile web add github:Noob-stupid/dsh-plugin-hub
57
57
  Windows(PowerShell):
58
58
 
59
59
  ```powershell
60
- git clone https://github.com/Noob-stupid/dsh-plugin-hub "$env:TEMP\dsh-plugin-console" 2>$null; & "$env:TEMP\dsh-plugin-console\deploy.ps1"
60
+ git clone https://github.com/Noob-stupid/dsh-plugin-gating-hub "$env:TEMP\dsh-plugin-console" 2>$null; & "$env:TEMP\dsh-plugin-console\deploy.ps1"
61
61
  ```
62
62
 
63
63
  Linux / macOS:
64
64
 
65
65
  ```bash
66
- git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console 2>/dev/null; bash /tmp/dsh-plugin-console/deploy.sh
66
+ git clone https://github.com/Noob-stupid/dsh-plugin-gating-hub /tmp/dsh-plugin-console 2>/dev/null; bash /tmp/dsh-plugin-console/deploy.sh
67
67
  ```
68
68
 
69
69
  脚本会做两件事:把插件包拷进 `$DSH_HOME/profiles/<profile>/node_modules/`,
@@ -74,7 +74,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
74
74
 
75
75
  ### 方式三:一句话交给 AI
76
76
 
77
- > 安装 DSH 插件中心(dsh-plugin-hub):运行 `dsh plugin --profile web add @noob-stupid/dsh-plugin-console`(npm 发布版);若没有 dsh CLI,则克隆 https://github.com/Noob-stupid/dsh-plugin-hub 到 `~/.dsh/profiles/web/node_modules/`,在 `cordis.patch.yml` 注册(id: plugin-console,name: @noob-stupid/dsh-plugin-console)。完成后重启 dsh web。
77
+ > 安装 DSH 插件中心(dsh-plugin-hub):运行 `dsh plugin --profile web add @noob-stupid/dsh-plugin-console`(npm 发布版);若没有 dsh CLI,则克隆 https://github.com/Noob-stupid/dsh-plugin-gating-hub 到 `~/.dsh/profiles/web/node_modules/`,在 `cordis.patch.yml` 注册(id: plugin-console,name: @noob-stupid/dsh-plugin-console)。完成后重启 dsh web。
78
78
 
79
79
  要求:DSH ≥ 0.1.0-rc.6(web profile,含 `dsh-client-modules` / `dsh-host-plugin-inventory`)。
80
80
 
@@ -248,8 +248,40 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
248
248
 
249
249
  - **观察者(默认)**:只做预检、门控与回滚点守护,不接管框架升级。
250
250
  - **托管**:框架升级与回滚由本控制台负责。**走本控制台升级时会自动切到托管**并记下原因。
251
+ - **自动切回观察者(0.5.32)**:检测到**桌面端宿主形态**(复用既有 `detectHostShape()` 判据)时,
252
+ 若当前模式**不是用户手动设定的**(`compat-mode.json` 的 `source` 为 `auto:*`;缺字段按 `manual` 兼容),
253
+ 启动/首次状态查询会把它自动拨回观察者并给一行提示。**你手动拨过一次之后,它永不自动改**;
254
+ 已经是观察者时零写盘。
251
255
 
252
256
  **启动失败隔离不受模式影响。** 服务起不来时,控制台会从启动日志定位肇事插件并禁用它(预设文件改名 `.broken-*`;找不到明确肇事者则进安全模式先让服务起来)。**起不来的控制台什么都门控不了** —— 所以这条救火链路永远生效。
257
+
258
+ **有确证证据时会自动禁用(0.5.32)**:环境指纹变化触发的自动预检、或启动失败记录给出**确证证据**时
259
+ (行引用的包**在所有解析基准上都解析不到**、`file://` 目标确实不存在、启动失败日志**点名**该行),
260
+ 控制台会**自动**给这些行写 `disabled: true`(不再等你点)。安全栏:**只禁有证据的行**;
261
+ `@deepseek-ai/*`(框架自带)与受保护/核心行**永不自动禁用**;**禁前先写一份"安全启动快照"**,
262
+ 所以任何一次自动禁用都能一条命令回滚;没有可写内容时**一个字节都不写**;每次自动禁用都在
263
+ `dshHome()/plugin-console/auto-disable.log` 留一条记录(时间 / 行 id / 证据 / 恢复命令)。
264
+
265
+ 想改成"只报告、等我自己点"(默认启用)—— `<host:port>` 换成你实例的地址
266
+ (默认 `dsh web` profile 监听 `127.0.0.1:3080`):
267
+
268
+ ```bash
269
+ curl -X POST http://<host:port>/plugin-console/compat-ack \
270
+ -H 'Content-Type: application/json' -d '{"autoDisableOnEvidence":false}'
271
+ ```
272
+
273
+ **怎么恢复(把自动禁用回滚)**:接口 `POST /plugin-console/safe-boot` + `{"action":"restore-last-good"}`
274
+ (改前会再备份一份当前状态);或服务打不开时,在插件目录下跑:
275
+
276
+ ```bash
277
+ node scripts/safe-boot.mjs --list # 先看清有哪些快照
278
+ node scripts/safe-boot.mjs --restore-last-good # 整份逐字节恢复到最近的良好快照
279
+ node scripts/safe-boot.mjs --disable-suspects # 或:只给点名的可疑行追加 disabled: true(不删任何行)
280
+ node scripts/safe-boot.mjs --mark-good # 把某份快照标记为"良好"(永不随保留策略删除)
281
+ ```
282
+
283
+ 安全启动快照落在 `dshHome()/plugin-console/safe-boot/`(**不在** profile 目录里),保留最近 3 份 +
284
+ 永不删你标记为"良好"的那份。
253
285
  ## 框架兼容性
254
286
 
255
287
  在隔离实例(全新 `DSH_HOME` + 真实 HTTP 探针)实测:**支持框架 ≥ `0.1.5-rc.2`**,无需任何变通。
@@ -287,7 +319,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
287
319
 
288
320
  ## 帮助 / Help
289
321
 
290
- 遇到问题先看这里;仍有疑问请到 [议题](https://github.com/Noob-stupid/dsh-plugin-hub/issues) 提问。
322
+ 遇到问题先看这里;仍有疑问请到 [议题](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues) 提问。
291
323
 
292
324
  - **面板没出现**:重启 dsh 服务 → 刷新页面 → 设置 → 插件 → 插件管理。
293
325
  - **点开关没反应**:基础设施行带"受保护"标签(禁止开关,这是保护机制);普通插件开关经
@@ -318,7 +350,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
318
350
  - ⭐ **给本仓库点个 Star**——直接帮助更多 DSH 用户发现它;
319
351
  - 分享给身边的 DSH 用户或社区;
320
352
  - 提交你自己的插件(打上 `dsh-plugin` 标签)一起壮大生态;
321
- - 发现 bug 或有新需求?[开一个 Issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues)。
353
+ - 发现 bug 或有新需求?[开一个 Issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues)。
322
354
 
323
355
  ---
324
356
 
@@ -329,7 +361,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
329
361
  - **贡献指南**:[CONTRIBUTING.md](CONTRIBUTING.md)
330
362
  - **行为准则**:[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
331
363
  - **安全政策**:[SECURITY.md](SECURITY.md)(漏洞私下报告)
332
- - **Issue 模板**:从 [新建 Issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues/new/choose) 页面选择 Bug / 功能建议
364
+ - **Issue 模板**:从 [新建 Issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues/new/choose) 页面选择 Bug / 功能建议
333
365
 
334
366
  ---
335
367
 
@@ -355,5 +387,5 @@ MIT
355
387
  - **已验证**:19 套测试全绿 · 8 条架构守卫断言 · 与稳定版逐条对打**路由清单一致**(`status` + 响应字段);
356
388
  - **已实测**(2026-09-20 真装真卸演练):普通插件 / bundle 插件 / 无 npm 仓库 / 套装 / 技能 / 聚合仓库子包 / 仓库落地 / 服务器组件启停;
357
389
  - **尚未实测**:真框架升级 / 真回滚、重启守护链路、AI 真跑、Gitee OAuth 回调(长尾风险主观估计 **10%~25%**);
358
- - **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console@0.3.67`,或本仓库 `main`。
390
+ - **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console` 的**最新发布版**(`npm view @noob-stupid/dsh-plugin-console version`),或本仓库 `main`。
359
391
  - 请多反馈问题
package/SECURITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 安全政策 Security Policy
2
2
 
3
- 本文件说明 `@noob-stupid/dsh-plugin-console`(仓库 [Noob-stupid/dsh-plugin-hub](https://github.com/Noob-stupid/dsh-plugin-hub))的安全政策:这个插件**能做什么**(能力清单)、已内置哪些护栏、自动化静态扫描容易误报的地方,以及如何报告漏洞。
3
+ 本文件说明 `@noob-stupid/dsh-plugin-console`(仓库 [Noob-stupid/dsh-plugin-gating-hub](https://github.com/Noob-stupid/dsh-plugin-gating-hub))的安全政策:这个插件**能做什么**(能力清单)、已内置哪些护栏、自动化静态扫描容易误报的地方,以及如何报告漏洞。
4
4
 
5
5
  英文能力摘要见文末 [Capability summary (English)](#capability-summary-english)。
6
6
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  **请勿在公开 Issue 中披露安全漏洞**。请通过以下方式私下报告:
17
17
 
18
- 1. 通过 GitHub 的 [Security Advisory](https://github.com/Noob-stupid/dsh-plugin-hub/security/advisories/new)(推荐,私密)
18
+ 1. 通过 GitHub 的 [Security Advisory](https://github.com/Noob-stupid/dsh-plugin-gating-hub/security/advisories/new)(推荐,私密)
19
19
  2. 或给仓库 owner 发 GitHub 私信
20
20
 
21
21
  请在报告中包含:
package/lib/client.js CHANGED
@@ -500,7 +500,7 @@ window.__ModuleLoader__.load({
500
500
  archiveSourcesDesc: "git 协议拉不动时的替代通道:直接用 HTTP 下载仓库压缩包再解压建仓(真机实测同一镜像下 archive 4 MB/s、git 协议 0 B/s)。模板用 {owner}/{repo} 占位符,可选 {branch};主源优先,失败自动回退下一个。示例:https://ghproxy.net/https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.tar.gz",
501
501
  archiveUrlPlaceholder: "https://ghproxy.net/https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.tar.gz",
502
502
  addArchiveSource: "添加 archive 源",
503
- gitSourcesDesc: "克隆仓库时用的地址模板({owner}/{repo} 占位符)。下面这个列表就是「自定义 Git 源」——内置两条是默认值,点「添加 Git 源」即可换成你自己的:Gitee = https://gitee.com/{owner}/{repo}.git;GitLab = https://gitlab.com/{owner}/{repo}.git;自建 Gitea = https://你的域名/{owner}/{repo}.git;镜像代理 = https://镜像站/https://github.com/{owner}/{repo}.git;本地裸仓库 = file:///D:/repos/{owner}/{repo}.git。主源优先,失败自动回退下一个",
503
+ gitSourcesDesc: "克隆仓库时用的地址模板({owner}/{repo} 占位符)。下面这个列表就是「自定义 Git 源」——内置两条是默认值,点「添加 Git 源」即可换成你自己的:Gitee = https://gitee.com/{owner}/{repo}.git;GitLab = https://gitlab.com/{owner}/{repo}.git;自建 Gitea = https://你的域名/{owner}/{repo}.git;镜像代理 = https://镜像站/https://github.com/{owner}/{repo}.git;本地裸仓库 = file:///C:/repos/{owner}/{repo}.git。主源优先,失败自动回退下一个",
504
504
  addGitSource: "添加 Git 源",
505
505
  gitUrlPlaceholder: "如 https://gitee.com/{owner}/{repo}.git",
506
506
  gitSourceBtn: "Git 源",
@@ -1050,7 +1050,7 @@ window.__ModuleLoader__.load({
1050
1050
  archiveSourcesDesc: "Fallback transport when the git protocol cannot move data: download the repo tarball over plain HTTP, extract, and create a repo from it (measured: on the same mirror archive ran at 4 MB/s while git moved 0 B/s). Templates use {owner}/{repo}, optionally {branch}; primary first, falls back on failure.",
1051
1051
  archiveUrlPlaceholder: "https://ghproxy.net/https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.tar.gz",
1052
1052
  addArchiveSource: "Add archive source",
1053
- gitSourcesDesc: "URL template for cloning ({owner}/{repo} placeholders). This list IS the custom git-source list — the two built-ins are just defaults; click Add git source to swap in your own: Gitee = https://gitee.com/{owner}/{repo}.git; GitLab = https://gitlab.com/{owner}/{repo}.git; self-hosted Gitea = https://your-domain/{owner}/{repo}.git; mirror proxy = https://mirror/https://github.com/{owner}/{repo}.git; local bare repo = file:///D:/repos/{owner}/{repo}.git. Primary first, falls back on failure",
1053
+ gitSourcesDesc: "URL template for cloning ({owner}/{repo} placeholders). This list IS the custom git-source list — the two built-ins are just defaults; click Add git source to swap in your own: Gitee = https://gitee.com/{owner}/{repo}.git; GitLab = https://gitlab.com/{owner}/{repo}.git; self-hosted Gitea = https://your-domain/{owner}/{repo}.git; mirror proxy = https://mirror/https://github.com/{owner}/{repo}.git; local bare repo = file:///C:/repos/{owner}/{repo}.git. Primary first, falls back on failure",
1054
1054
  addGitSource: "Add Git source",
1055
1055
  gitUrlPlaceholder: "e.g. https://gitee.com/{owner}/{repo}.git",
1056
1056
  gitSourceBtn: "Git sources",
@@ -5486,7 +5486,21 @@ setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
5486
5486
  // 2026-09-27 加法:依赖锁体检结果(null = 还没体检过;不自动跑,用户点「体检」才打接口)
5487
5487
  const [lockFix, setLockFix] = react.useState(null);
5488
5488
  const load = async () => {
5489
- try { const data = await call("/plugin-console/compat-status"); if (data !== null) setStatus(data); } catch {}
5489
+ try {
5490
+ const data = await call("/plugin-console/compat-status");
5491
+ if (data !== null) {
5492
+ setStatus(data);
5493
+ // 0.5.32 加法(D1/D4/D5):**瞬时提示**,沿用既有 note 通道(自动消失、无新组件、无按钮)。
5494
+ // 文案由服务端下发(中英双语短句):「已自动切观察者」/「框架变化,已自动预检」/「已自动禁用 N 行」。
5495
+ // 语言选择复用既有 i18n:t() 在中文下取键名本身(sentinel),在英文下取字典值。
5496
+ const isEn = t("safetyUnknown") !== "safetyUnknown";
5497
+ const pick = (pair) => (pair === null || pair === undefined ? null : (isEn ? (pair.en || pair.zh) : (pair.zh || pair.en)));
5498
+ const auto = data.autoDisable && data.autoDisable.notice ? pick(data.autoDisable.notice) : null;
5499
+ if (typeof auto === "string" && auto !== "") setNote({ text: auto });
5500
+ else if (typeof data.autoPreflight?.note?.zh === "string" && data.autoPreflight.changed === true && data.autoPreflight.scheduled !== true) setNote({ text: pick(data.autoPreflight.note) });
5501
+ else if (data.modeAuto) setNote({ text: pick(data.modeAuto.note) });
5502
+ }
5503
+ } catch {}
5490
5504
  };
5491
5505
  // 依赖锁体检:**只读**接口,不打任何写操作
5492
5506
  const lockCheck = async () => {
package/lib/index.js CHANGED
@@ -1,141 +1,141 @@
1
- /**
2
- * @deepseek-ai/dsh-plugin-console — 插件控制台宿主端。
3
- *
4
- * 提供环回 HTTP 路由(前缀 /plugin-console):
5
- * GET /plugin-console/state 当前插件清单 + 用户补丁层状态
6
- * POST /plugin-console/toggle 一键启用/停用插件(写 cordis.patch.yml,HMR 生效)
7
- * POST /plugin-console/search GitHub 仓库搜索(dsh-plugin 相关)
8
- * POST /plugin-console/repo 读取仓库的 package.json,判断是否可安装
9
- * POST /plugin-console/install 安装 npm 包(或 git 仓库)并追加启用条目
10
- *
11
- * 插件开关的机制:用户补丁层 cordis.patch.yml 是逐键覆盖(id-targeted patch),
12
- * 追加 `- id: X` + `disabled: true` 即可停用任意行(含 bundle 行与用户 insert 行),
13
- * 移除该块即恢复;HMR 监视器会自动重组合,无需重启。
14
- */
15
- import { readFile, writeFile } from 'node:fs/promises'
16
- import { readFileSync, writeFileSync, existsSync, statSync, rmSync, readdirSync, mkdirSync, copyFileSync, realpathSync } from 'node:fs'
17
- import { execFile, spawn } from 'node:child_process'
18
- import { promisify } from 'node:util'
19
- import { dirname, join, basename, sep } from 'node:path'
20
- import { tmpdir, homedir } from 'node:os'
21
- import { fileURLToPath, pathToFileURL } from 'node:url'
22
- import { createRequire } from 'node:module'
23
- import { request as httpsRequest } from 'node:https'
24
- import { aiJobsFile, componentsFile, dshHome, entryPkgMeta, findPatchPath, marketIndexCacheFile, packageNameOf, pluginRoot, profileDirOf, repoLandConfFile, resolvePackageJson, rowIdOf } from './server/infra/paths.js'
25
- import { isFrameworkVersionNewer, parseFrameworkVersion, semverRangeMatch, semverRangeMatchLoose } from './server/infra/semver.js'
26
- import { GITHUB_API, curlJson, fetchJsonUrl, githubJson, postJsonUrl, rawTextWithFallback } from './server/infra/http.js'
27
- import { cleanupStalePackageDir, copyTree, queuedWrite, startTrashCleanup, trashScanRoots } from './server/infra/fsx.js'
28
- import { execFileAsync, gitEnv, processAlive } from './server/infra/exec.js'
29
- import { escapeRegExp, maskUrl } from './server/infra/mask.js'
30
- import { isAllowedWriteOrigin, isLoopback, readBody, sendError, sendJson } from './server/infra/httpd.js'
31
- import { DEFAULT_SOURCES, consumeGiteeOAuthState, createGiteeOAuthState, gitCloneUrls, giteeStatusView, isAllowedGitSourceUrl, isAllowedSourceUrl, maskSources, orderedRegistries, readGiteeConfig, readSources, writeSources } from './server/domain/sources.js'
32
- import { CORE_PATCH_ROW_IDS, appendInsert, disableEntry, enableEntry, healPatchSafety, readPatchState, removeDisableBlock, removeInsertRow, sanitizePatchText, syncNameFromNote } from './server/domain/patch.js'
33
- import { enrichItems, fetchRepoPackage, fetchSubpackageNames, githubRepoInfo, normalizePlatformItems, readPluginDetails, subpackageCandidates } from './server/domain/market.js'
34
- import { SKILL_TOPICS, detectSkillRepo, fetchSkillMeta, listInstalledSkills, runSkillInstallJob, setSkillEnabled } from './server/domain/skills.js'
35
- import { autostartComponents, compFind, compStart, compStatus, compStop, compUiUrl, compUpsert, findComponents } from './server/domain/components.js'
36
- import { getReposDir, gitCloneRepo, listLandedRepos, setReposDir } from './server/domain/repoland.js'
37
- import { DEFAULT_BUNDLES, addBundleToManifest, backfillMissingDeps, curlManualInstall, detectBundleOnly, ensureBundlePatchIntegrity, githubReleaseInstall, installJobView, pnpmInstall, raceInstallChannels, readBundlePatchRefNames, readExtraBundleOwners, readGithubAuth, syncAggregateSubpackageVersions } from './server/domain/install.js'
38
- import { findPresetDirs, readGitmodules } from './server/domain/suite.js'
39
- import { FIBER_PHASE, SUPPORTED_WEB_APP_PATTERN, checkPluginFrameworkCompat, frameworkCheckPromptText, isFrameworkOwnedPackage, probePluginImport, readCompatGate, readCompatPending, writeCompatGate, writeCompatPending } from './server/domain/compat.js'
40
- import { logQuarantineMergeError, markPendingAdopted, mergeQuarantineRecord, planQuarantine, readQuarantineRecord, reconcileCompatPending } from './server/domain/quarantine.js'
41
- import { FRAMEWORK_BACKUP_ROOT, FRAMEWORK_STATE_FILE, applyFrameworkTolerancePatchOnce, checkpointFrameworkTree, cleanupStaleFwTasks, relaunchPrelude, resolveDshBin, resolveFrameworkRootNodeModules } from './server/domain/framework.js'
42
- import { migrateAgentConfigsForUpgrade } from './server/domain/presets.js'
43
- import { GITEE_AUTH_URL, GITEE_TOKEN_URL } from './server/domain/sources.js'
44
- import { aiEmpowerPresetFor, aiJobs, builtinPlanFor, isAllowedWritePath, isSafeRunCmd, loadAiJobs, parsePlanJson, resolvePlaceholders, resolvePythonPath, saveAiJobs } from './server/domain/ai.js'
45
- import { jobLog, waitHealth } from './server/domain/jobs.js'
46
- import { fwCheckCache, installJobs, marketIndexCache, nextAiJobSeq, nextInstallJobSeq, patchHealAt, patchHealReport, setFwCheckCache, setMarketIndexCache, setPatchHealAt, setPatchHealReport } from './server/state.js'
47
- import { routeStateGet, routeDetails } from './server/routes/state.js'
48
- import {routeFrameworkUpgradeStatusGet, routeFrameworkRelaunch, routeFrameworkCheck, routeCompatGate, routeCheckUpdate, routeFrameworkRollback, routeRestart } from './server/routes/framework.js'
49
- import { routeFrameworkUpgrade } from './server/routes/framework-upgrade.js'
50
- import { routeToggle, routeUninstall, routeAdaptUnlock, routeAdaptUnlockAll, routeCleanResiduals, routeSelfUpdate } from './server/routes/plugins.js'
51
- import { routeSearch, routeEnrich, routeRepo, routeSubpackages, routeMarketIndex } from './server/routes/market.js'
52
- import { routeAiConsent, routeAiEmpowerPlan, routeAiEmpowerStatus, routeAiEmpowerList, routeAiEmpowerRun, routeAiEmpowerCancel } from './server/routes/ai.js'
53
- import { routeComponents, routeRepoClone, routeRepoList, routeRepoLandConfig, routeRepoRemove, routeRepoOpen, routeComponentAutostart, routeComponentStart, routeComponentStop, routeComponentStatus } from './server/routes/components.js'
54
- import { routeSourcesGet, routeGiteeOauthUrlGet, routeGiteeOauthCallbackGet, routeRegistryScan, routeSources } from './server/routes/sources.js'
55
- import { routeSkillsInstalledGet, routeSkillRemove, routeSkillToggle } from './server/routes/skills.js'
56
- import { routeInstall, routeInstallStatus } from './server/routes/install.js'
57
- import { detectAdoptablePending, detectCompat, frameworkCompatReportFor, maybeAutoAdaptCompat, rowIdModuleMap } from './server/domain/compat.js'
58
- import { backupProfileSnapshot, currentFrameworkVersion, detectFrameworkUpgrade, preflightDisableIncompatible } from './server/domain/framework.js'
59
- import { listEntries, webPort } from './server/domain/runtime.js'
60
- import { isProtectedModule } from './server/domain/runtime.js'
61
- import { aiEmpowerExecute, aiEmpowerPlan } from './server/domain/ai-run.js'
62
- import { runInstallJob } from './server/domain/install-job.js'
63
- import { runSuiteInstallJob } from './server/domain/suite.js'
64
- import { pnpmRemove, readExtraBundleRows } from './server/domain/install-job.js'
65
- import { DEFAULT_SEARCH } from './server/domain/sources.js'
66
- import { ROUTE_PREFIX, handle } from './server/routes/index.js'
67
-
68
- /** Cordis 插件元信息。 */
69
- export const name = '@noob-stupid/dsh-plugin-console'
70
- export const inject = ['webServer', 'loader']
71
-
72
- /**
73
- * 后台安装任务注册表:请求立即返回,安装继续在服务端执行;
74
- * 面板通过 /install-status 轮询进度(stage + status),离开面板不中断。
75
- */
76
- /** 静态插件索引内存缓存(/market-index 用)。 */
77
-
78
- // ============================================================
79
- // AI 赋能:文档驱动的通用组件部署(读文档 → 出计划 → 确认 → 执行 → 组件控制)
80
- // ============================================================
81
-
82
- /** 应用插件:注册 /plugin-console 路由。 */
83
- export function apply(ctx) {
84
- // 恢复 AI 赋能任务(restart 后 running 置失败,plan-ready/结果保留)
85
- loadAiJobs()
86
- // 升级脚本的「启动失败隔离」记录并入适配门清单(让用户看到谁被自动关了)
87
- // v0.3.44:这里的 catch 不再静默 —— 2026-09-11 那次隔离记录(20 行)就是这样凭空消失的:
88
- // 合并函数在中途抛错、catch 吞掉,界面只剩一个没有解释的【停用】。失败必须留痕。
89
- try { mergeQuarantineRecord(ctx) } catch (error) { logQuarantineMergeError(error) }
90
- // v0.3.45:清单与现实对账 —— 补 moduleName(否则「全家桶一键启用已适配」永远匹配不到),
91
- // 以及把「已经启用、却还挂着 pending」的记录转成已适配(用户实测:启用后重启又变【待适配】)
92
- try {
93
- const fixed = reconcileCompatPending(ctx)
94
- if (fixed.backfilled > 0 || fixed.adopted > 0) {
95
- try { writeFileSync(join(dshHome(), 'plugin-console', 'fw-merge-error.log'), `${new Date().toISOString()} 清单对账:补 moduleName ${fixed.backfilled} 行,已启用转已适配 ${fixed.adopted} 行\n`, { flag: 'a' }) } catch {}
96
- }
97
- } catch (error) { logQuarantineMergeError(error) }
98
- // 启动时清掉上次升级/回滚/重启留下的僵尸计划任务(脚本被强杀时来不及自删)+ 后台清掉 `.trash-*` 降级目录(2026-09-26 加法;覆盖 tmpdir / profile node_modules / skills / repos,即发即忘、绝不阻塞启动)
99
- try { cleanupStaleFwTasks(); startTrashCleanup({ roots: trashScanRoots({ profileDir: profileDirOf(ctx) ?? '', extra: [join(dshHome(), 'skills'), getReposDir()] }) }) } catch {}
100
- // DSH 启动时自动拉起标记了「自启动」的服务器组件(幂等,不阻塞启动)
101
- autostartComponents().catch(() => {})
102
- ctx.effect(() => {
103
- const route = {
104
- kind: 'prefix',
105
- path: ROUTE_PREFIX,
106
- handler: async (req, res) => {
107
- if (!isLoopback(req.socket?.remoteAddress ?? '')) {
108
- sendError(res, 403, '仅允许本机访问')
109
- return
110
- }
111
- const port = webPort(ctx)
112
- const host = typeof req.headers?.host === 'string' ? req.headers.host : ''
113
- if (![`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`].includes(host)) {
114
- sendError(res, 403, 'Host 校验失败(仅允许本机访问)')
115
- return
116
- }
117
- // 安全护栏(issue #9):写路由必须是本机同源请求,防止恶意网页跨站驱动敏感操作。
118
- const method = req.method ?? 'GET'
119
- if (method !== 'GET' && method !== 'HEAD' && !isAllowedWriteOrigin(req, port)) {
120
- sendError(res, 403, '跨站请求被拒绝(Origin/Sec-Fetch-Site 校验)')
121
- return
122
- }
123
- try {
124
- await handle(ctx, req, res)
125
- } catch (error) {
126
- sendError(res, 500, error instanceof Error ? error.message : String(error))
127
- }
128
- },
129
- }
130
- return ctx.webServer.register(route)
131
- }, 'plugin-console: routes')
132
- }
133
-
134
- // ── 路由表(分层 Step 8b:表驱动分发,替代逐条 if 分支)─────────────────────────
135
- // 方法限定且位于 405 守卫之前的分支(只读接口)
136
-
137
- /** 路由处理器需要的宿主服务(仍住在 index.js 的 ctx 相关函数)—— ports 注入点(方案 §四)。
138
- * 用函数而不是模块级常量:这些是函数声明(会提升),但用惰性函数可以彻底避免初始化顺序问题。 */
139
-
140
- /** 框架版本检查缓存(功能包 → 框架 面板用;5 分钟 TTL,避免每次打开面板都打 registry)。 */
141
-
1
+ /**
2
+ * @deepseek-ai/dsh-plugin-console — 插件控制台宿主端。
3
+ *
4
+ * 提供环回 HTTP 路由(前缀 /plugin-console):
5
+ * GET /plugin-console/state 当前插件清单 + 用户补丁层状态
6
+ * POST /plugin-console/toggle 一键启用/停用插件(写 cordis.patch.yml,HMR 生效)
7
+ * POST /plugin-console/search GitHub 仓库搜索(dsh-plugin 相关)
8
+ * POST /plugin-console/repo 读取仓库的 package.json,判断是否可安装
9
+ * POST /plugin-console/install 安装 npm 包(或 git 仓库)并追加启用条目
10
+ *
11
+ * 插件开关的机制:用户补丁层 cordis.patch.yml 是逐键覆盖(id-targeted patch),
12
+ * 追加 `- id: X` + `disabled: true` 即可停用任意行(含 bundle 行与用户 insert 行),
13
+ * 移除该块即恢复;HMR 监视器会自动重组合,无需重启。
14
+ */
15
+ import { readFile, writeFile } from 'node:fs/promises'
16
+ import { readFileSync, writeFileSync, existsSync, statSync, rmSync, readdirSync, mkdirSync, copyFileSync, realpathSync } from 'node:fs'
17
+ import { execFile, spawn } from 'node:child_process'
18
+ import { promisify } from 'node:util'
19
+ import { dirname, join, basename, sep } from 'node:path'
20
+ import { tmpdir, homedir } from 'node:os'
21
+ import { fileURLToPath, pathToFileURL } from 'node:url'
22
+ import { createRequire } from 'node:module'
23
+ import { request as httpsRequest } from 'node:https'
24
+ import { aiJobsFile, componentsFile, dshHome, entryPkgMeta, findPatchPath, marketIndexCacheFile, packageNameOf, pluginRoot, profileDirOf, repoLandConfFile, resolvePackageJson, rowIdOf } from './server/infra/paths.js'
25
+ import { isFrameworkVersionNewer, parseFrameworkVersion, semverRangeMatch, semverRangeMatchLoose } from './server/infra/semver.js'
26
+ import { GITHUB_API, curlJson, fetchJsonUrl, githubJson, postJsonUrl, rawTextWithFallback } from './server/infra/http.js'
27
+ import { cleanupStalePackageDir, copyTree, queuedWrite, startTrashCleanup, trashScanRoots } from './server/infra/fsx.js'
28
+ import { execFileAsync, gitEnv, processAlive } from './server/infra/exec.js'
29
+ import { escapeRegExp, maskUrl } from './server/infra/mask.js'
30
+ import { isAllowedWriteOrigin, isLoopback, readBody, sendError, sendJson } from './server/infra/httpd.js'
31
+ import { DEFAULT_SOURCES, consumeGiteeOAuthState, createGiteeOAuthState, gitCloneUrls, giteeStatusView, isAllowedGitSourceUrl, isAllowedSourceUrl, maskSources, orderedRegistries, readGiteeConfig, readSources, writeSources } from './server/domain/sources.js'
32
+ import { CORE_PATCH_ROW_IDS, appendInsert, disableEntry, enableEntry, healPatchSafety, readPatchState, removeDisableBlock, removeInsertRow, sanitizePatchText, syncNameFromNote } from './server/domain/patch.js'
33
+ import { enrichItems, fetchRepoPackage, fetchSubpackageNames, githubRepoInfo, normalizePlatformItems, readPluginDetails, subpackageCandidates } from './server/domain/market.js'
34
+ import { SKILL_TOPICS, detectSkillRepo, fetchSkillMeta, listInstalledSkills, runSkillInstallJob, setSkillEnabled } from './server/domain/skills.js'
35
+ import { autostartComponents, compFind, compStart, compStatus, compStop, compUiUrl, compUpsert, findComponents } from './server/domain/components.js'
36
+ import { getReposDir, gitCloneRepo, listLandedRepos, setReposDir } from './server/domain/repoland.js'
37
+ import { DEFAULT_BUNDLES, addBundleToManifest, backfillMissingDeps, curlManualInstall, detectBundleOnly, ensureBundlePatchIntegrity, githubReleaseInstall, installJobView, pnpmInstall, raceInstallChannels, readBundlePatchRefNames, readExtraBundleOwners, readGithubAuth, syncAggregateSubpackageVersions } from './server/domain/install.js'
38
+ import { findPresetDirs, readGitmodules } from './server/domain/suite.js'
39
+ import { FIBER_PHASE, SUPPORTED_WEB_APP_PATTERN, checkPluginFrameworkCompat, frameworkCheckPromptText, isFrameworkOwnedPackage, probePluginImport, readCompatGate, readCompatPending, writeCompatGate, writeCompatPending } from './server/domain/compat.js'
40
+ import { logQuarantineMergeError, markPendingAdopted, mergeQuarantineRecord, planQuarantine, readQuarantineRecord, reconcileCompatPending } from './server/domain/quarantine.js'
41
+ import { FRAMEWORK_BACKUP_ROOT, FRAMEWORK_STATE_FILE, applyFrameworkTolerancePatchOnce, checkpointFrameworkTree, cleanupStaleFwTasks, relaunchPrelude, resolveDshBin, resolveFrameworkRootNodeModules } from './server/domain/framework.js'
42
+ import { migrateAgentConfigsForUpgrade } from './server/domain/presets.js'
43
+ import { GITEE_AUTH_URL, GITEE_TOKEN_URL } from './server/domain/sources.js'
44
+ import { aiEmpowerPresetFor, aiJobs, builtinPlanFor, isAllowedWritePath, isSafeRunCmd, loadAiJobs, parsePlanJson, resolvePlaceholders, resolvePythonPath, saveAiJobs } from './server/domain/ai.js'
45
+ import { jobLog, waitHealth } from './server/domain/jobs.js'
46
+ import { fwCheckCache, installJobs, marketIndexCache, nextAiJobSeq, nextInstallJobSeq, patchHealAt, patchHealReport, setFwCheckCache, setMarketIndexCache, setPatchHealAt, setPatchHealReport } from './server/state.js'
47
+ import { routeStateGet, routeDetails } from './server/routes/state.js'
48
+ import {routeFrameworkUpgradeStatusGet, routeFrameworkRelaunch, routeFrameworkCheck, routeCompatGate, routeCheckUpdate, routeFrameworkRollback, routeRestart } from './server/routes/framework.js'
49
+ import { routeFrameworkUpgrade } from './server/routes/framework-upgrade.js'
50
+ import { routeToggle, routeUninstall, routeAdaptUnlock, routeAdaptUnlockAll, routeCleanResiduals, routeSelfUpdate } from './server/routes/plugins.js'
51
+ import { routeSearch, routeEnrich, routeRepo, routeSubpackages, routeMarketIndex } from './server/routes/market.js'
52
+ import { routeAiConsent, routeAiEmpowerPlan, routeAiEmpowerStatus, routeAiEmpowerList, routeAiEmpowerRun, routeAiEmpowerCancel } from './server/routes/ai.js'
53
+ import { routeComponents, routeRepoClone, routeRepoList, routeRepoLandConfig, routeRepoRemove, routeRepoOpen, routeComponentAutostart, routeComponentStart, routeComponentStop, routeComponentStatus } from './server/routes/components.js'
54
+ import { routeSourcesGet, routeGiteeOauthUrlGet, routeGiteeOauthCallbackGet, routeRegistryScan, routeSources } from './server/routes/sources.js'
55
+ import { routeSkillsInstalledGet, routeSkillRemove, routeSkillToggle } from './server/routes/skills.js'
56
+ import { routeInstall, routeInstallStatus } from './server/routes/install.js'
57
+ import { detectAdoptablePending, detectCompat, frameworkCompatReportFor, maybeAutoAdaptCompat, rowIdModuleMap } from './server/domain/compat.js'
58
+ import { backupProfileSnapshot, currentFrameworkVersion, detectFrameworkUpgrade, preflightDisableIncompatible } from './server/domain/framework.js'
59
+ import { listEntries, webPort } from './server/domain/runtime.js'
60
+ import { isProtectedModule } from './server/domain/runtime.js'
61
+ import { aiEmpowerExecute, aiEmpowerPlan } from './server/domain/ai-run.js'
62
+ import { runInstallJob } from './server/domain/install-job.js'
63
+ import { runSuiteInstallJob } from './server/domain/suite.js'
64
+ import { pnpmRemove, readExtraBundleRows } from './server/domain/install-job.js'
65
+ import { DEFAULT_SEARCH } from './server/domain/sources.js'
66
+ import { ROUTE_PREFIX, handle } from './server/routes/index.js'
67
+
68
+ /** Cordis 插件元信息。 */
69
+ export const name = '@noob-stupid/dsh-plugin-console'
70
+ export const inject = ['webServer', 'loader']
71
+
72
+ /**
73
+ * 后台安装任务注册表:请求立即返回,安装继续在服务端执行;
74
+ * 面板通过 /install-status 轮询进度(stage + status),离开面板不中断。
75
+ */
76
+ /** 静态插件索引内存缓存(/market-index 用)。 */
77
+
78
+ // ============================================================
79
+ // AI 赋能:文档驱动的通用组件部署(读文档 → 出计划 → 确认 → 执行 → 组件控制)
80
+ // ============================================================
81
+
82
+ /** 应用插件:注册 /plugin-console 路由。 */
83
+ export function apply(ctx) {
84
+ // 恢复 AI 赋能任务(restart 后 running 置失败,plan-ready/结果保留)
85
+ loadAiJobs()
86
+ // 升级脚本的「启动失败隔离」记录并入适配门清单(让用户看到谁被自动关了)
87
+ // v0.3.44:这里的 catch 不再静默 —— 2026-09-11 那次隔离记录(20 行)就是这样凭空消失的:
88
+ // 合并函数在中途抛错、catch 吞掉,界面只剩一个没有解释的【停用】。失败必须留痕。
89
+ try { mergeQuarantineRecord(ctx) } catch (error) { logQuarantineMergeError(error) }
90
+ // v0.3.45:清单与现实对账 —— 补 moduleName(否则「全家桶一键启用已适配」永远匹配不到),
91
+ // 以及把「已经启用、却还挂着 pending」的记录转成已适配(用户实测:启用后重启又变【待适配】)
92
+ try {
93
+ const fixed = reconcileCompatPending(ctx)
94
+ if (fixed.backfilled > 0 || fixed.adopted > 0) {
95
+ try { writeFileSync(join(dshHome(), 'plugin-console', 'fw-merge-error.log'), `${new Date().toISOString()} 清单对账:补 moduleName ${fixed.backfilled} 行,已启用转已适配 ${fixed.adopted} 行\n`, { flag: 'a' }) } catch {}
96
+ }
97
+ } catch (error) { logQuarantineMergeError(error) }
98
+ // 启动时清掉上次升级/回滚/重启留下的僵尸计划任务(脚本被强杀时来不及自删)+ 后台清掉 `.trash-*` 降级目录(2026-09-26 加法;覆盖 tmpdir / profile node_modules / skills / repos,即发即忘、绝不阻塞启动)
99
+ try { cleanupStaleFwTasks(); startTrashCleanup({ roots: trashScanRoots({ profileDir: profileDirOf(ctx) ?? '', extra: [join(dshHome(), 'skills'), getReposDir()] }) }) } catch {}
100
+ // DSH 启动时自动拉起标记了「自启动」的服务器组件(幂等,不阻塞启动)
101
+ autostartComponents().catch(() => {})
102
+ ctx.effect(() => {
103
+ const route = {
104
+ kind: 'prefix',
105
+ path: ROUTE_PREFIX,
106
+ handler: async (req, res) => {
107
+ if (!isLoopback(req.socket?.remoteAddress ?? '')) {
108
+ sendError(res, 403, '仅允许本机访问')
109
+ return
110
+ }
111
+ const port = webPort(ctx)
112
+ const host = typeof req.headers?.host === 'string' ? req.headers.host : ''
113
+ if (![`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`].includes(host)) {
114
+ sendError(res, 403, 'Host 校验失败(仅允许本机访问)')
115
+ return
116
+ }
117
+ // 安全护栏(issue #9):写路由必须是本机同源请求,防止恶意网页跨站驱动敏感操作。
118
+ const method = req.method ?? 'GET'
119
+ if (method !== 'GET' && method !== 'HEAD' && !isAllowedWriteOrigin(req, port)) {
120
+ sendError(res, 403, '跨站请求被拒绝(Origin/Sec-Fetch-Site 校验)')
121
+ return
122
+ }
123
+ try {
124
+ await handle(ctx, req, res)
125
+ } catch (error) {
126
+ sendError(res, 500, error instanceof Error ? error.message : String(error))
127
+ }
128
+ },
129
+ }
130
+ return ctx.webServer.register(route)
131
+ }, 'plugin-console: routes')
132
+ }
133
+
134
+ // ── 路由表(分层 Step 8b:表驱动分发,替代逐条 if 分支)─────────────────────────
135
+ // 方法限定且位于 405 守卫之前的分支(只读接口)
136
+
137
+ /** 路由处理器需要的宿主服务(仍住在 index.js 的 ctx 相关函数)—— ports 注入点(方案 §四)。
138
+ * 用函数而不是模块级常量:这些是函数声明(会提升),但用惰性函数可以彻底避免初始化顺序问题。 */
139
+
140
+ /** 框架版本检查缓存(功能包 → 框架 面板用;5 分钟 TTL,避免每次打开面板都打 registry)。 */
141
+
@@ -1,6 +1,6 @@
1
1
  // L1 · domain —— ai-run.js(AI 赋能的执行侧:规划 / 执行 / 修复 / 单步运行)。
2
2
  // 分层 Step 8c-2 从 lib/index.js 搬出;形参由 ctx 收窄为 ports,只搬移未改逻辑。
3
- // 分组见 D:\dsh\dsh-plugin-hub-plan\architecture.zh.md 三
3
+ // 分层分组:L1 · domain(边界由 tests/test-architecture-guard.mjs 断言)
4
4
 
5
5
  import { writeFileSync, existsSync, statSync, rmSync, mkdirSync } from 'node:fs'
6
6
  import { readFile } from 'node:fs/promises'