@noob-stupid/dsh-plugin-console 0.5.29 → 0.5.31
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 +7 -7
- package/README.zh.md +8 -8
- package/SECURITY.md +2 -2
- package/lib/client.js +91 -5
- package/lib/index.js +141 -141
- package/lib/server/domain/ai-run.js +1 -1
- package/lib/server/domain/ai.js +39 -11
- package/lib/server/domain/compat.js +69 -3
- package/lib/server/domain/components.js +1 -1
- package/lib/server/domain/dep-source.js +1 -1
- package/lib/server/domain/framework-cleanup.js +225 -0
- package/lib/server/domain/framework-residuals.js +59 -0
- package/lib/server/domain/framework.js +65 -6
- package/lib/server/domain/install-job.js +1 -1
- package/lib/server/domain/install.js +1 -1
- package/lib/server/domain/jobs.js +1 -1
- package/lib/server/domain/market.js +1 -1
- package/lib/server/domain/patch-composition-audit.js +218 -0
- package/lib/server/domain/patch.js +69 -16
- package/lib/server/domain/preset-audit.js +144 -0
- package/lib/server/domain/presets.js +1 -1
- package/lib/server/domain/release-source.js +3 -3
- package/lib/server/domain/repoland.js +1 -1
- package/lib/server/domain/runtime.js +1 -1
- package/lib/server/domain/skills.js +1 -1
- package/lib/server/domain/sources.js +1 -1
- package/lib/server/domain/suite.js +1 -1
- package/lib/server/infra/exec.js +24 -3
- package/lib/server/infra/framework-root.js +99 -0
- package/lib/server/infra/fsx.js +3 -3
- package/lib/server/infra/http.js +1 -1
- package/lib/server/infra/httpd.js +1 -1
- package/lib/server/infra/mask.js +1 -1
- package/lib/server/infra/package-resolve.js +73 -0
- package/lib/server/infra/paths.js +1 -1
- package/lib/server/infra/registry-versions.js +64 -0
- package/lib/server/infra/semver.js +1 -1
- package/lib/server/routes/compat.js +41 -1
- package/lib/server/routes/components.js +1 -1
- package/lib/server/routes/framework-preflight.js +43 -2
- package/lib/server/routes/framework-upgrade.js +16 -17
- package/lib/server/routes/framework.js +18 -2
- package/lib/server/routes/github-login.js +2 -2
- package/lib/server/routes/index.js +17 -2
- package/lib/server/routes/patch-audit.js +59 -0
- package/lib/server/routes/plugins.js +20 -2
- package/lib/server/routes/preset-audit.js +24 -0
- package/lib/server/routes/state.js +9 -3
- 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
|
|
|
@@ -384,7 +384,7 @@ If this panel saves you time or makes DSH more fun to use:
|
|
|
384
384
|
- ⭐ **Star this repo** — it directly helps more DSH users find it;
|
|
385
385
|
- Share it with DSH users or in DSH communities;
|
|
386
386
|
- 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).
|
|
387
|
+
- Found a bug or want a feature? [Open an issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues).
|
|
388
388
|
|
|
389
389
|
---
|
|
390
390
|
|
|
@@ -395,7 +395,7 @@ Contributions of all kinds are welcome — issues, PRs, docs, translations.
|
|
|
395
395
|
- **Guidelines**: [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
396
396
|
- **Code of Conduct**: [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
397
397
|
- **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
|
|
398
|
+
- **Issue templates**: bug reports & feature requests via the [new issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues/new/choose) page
|
|
399
399
|
|
|
400
400
|
---
|
|
401
401
|
|
|
@@ -420,4 +420,4 @@ The layered-refactor preview lives in its **own repository**: [**`Noob-stupid/ds
|
|
|
420
420
|
- **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
421
|
- **Verified**: 19 test suites green · 8 architecture-guard assertions · **route inventory identical** (`status` + response fields) against the stable build;
|
|
422
422
|
- **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@
|
|
423
|
+
- **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
|
|
|
@@ -287,7 +287,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
287
287
|
|
|
288
288
|
## 帮助 / Help
|
|
289
289
|
|
|
290
|
-
遇到问题先看这里;仍有疑问请到 [议题](https://github.com/Noob-stupid/dsh-plugin-hub/issues) 提问。
|
|
290
|
+
遇到问题先看这里;仍有疑问请到 [议题](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues) 提问。
|
|
291
291
|
|
|
292
292
|
- **面板没出现**:重启 dsh 服务 → 刷新页面 → 设置 → 插件 → 插件管理。
|
|
293
293
|
- **点开关没反应**:基础设施行带"受保护"标签(禁止开关,这是保护机制);普通插件开关经
|
|
@@ -318,7 +318,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
318
318
|
- ⭐ **给本仓库点个 Star**——直接帮助更多 DSH 用户发现它;
|
|
319
319
|
- 分享给身边的 DSH 用户或社区;
|
|
320
320
|
- 提交你自己的插件(打上 `dsh-plugin` 标签)一起壮大生态;
|
|
321
|
-
- 发现 bug 或有新需求?[开一个 Issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues)。
|
|
321
|
+
- 发现 bug 或有新需求?[开一个 Issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues)。
|
|
322
322
|
|
|
323
323
|
---
|
|
324
324
|
|
|
@@ -329,7 +329,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
|
|
|
329
329
|
- **贡献指南**:[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
330
330
|
- **行为准则**:[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
331
331
|
- **安全政策**:[SECURITY.md](SECURITY.md)(漏洞私下报告)
|
|
332
|
-
- **Issue 模板**:从 [新建 Issue](https://github.com/Noob-stupid/dsh-plugin-hub/issues/new/choose) 页面选择 Bug / 功能建议
|
|
332
|
+
- **Issue 模板**:从 [新建 Issue](https://github.com/Noob-stupid/dsh-plugin-gating-hub/issues/new/choose) 页面选择 Bug / 功能建议
|
|
333
333
|
|
|
334
334
|
---
|
|
335
335
|
|
|
@@ -355,5 +355,5 @@ MIT
|
|
|
355
355
|
- **已验证**:19 套测试全绿 · 8 条架构守卫断言 · 与稳定版逐条对打**路由清单一致**(`status` + 响应字段);
|
|
356
356
|
- **已实测**(2026-09-20 真装真卸演练):普通插件 / bundle 插件 / 无 npm 仓库 / 套装 / 技能 / 聚合仓库子包 / 仓库落地 / 服务器组件启停;
|
|
357
357
|
- **尚未实测**:真框架升级 / 真回滚、重启守护链路、AI 真跑、Gitee OAuth 回调(长尾风险主观估计 **10%~25%**);
|
|
358
|
-
- **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console@
|
|
358
|
+
- **日常使用请继续用**:npm `@noob-stupid/dsh-plugin-console` 的**最新发布版**(`npm view @noob-stupid/dsh-plugin-console version`),或本仓库 `main`。
|
|
359
359
|
- 请多反馈问题
|
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
|
@@ -342,6 +342,17 @@ window.__ModuleLoader__.load({
|
|
|
342
342
|
githubLoginFail: "登录失败",
|
|
343
343
|
hubUpdateAvailable: "下载更新",
|
|
344
344
|
cleanResiduals: "清理残余备份",
|
|
345
|
+
cleanFrameworkResiduals: "清理框架残留",
|
|
346
|
+
cfStale: "旧目录 {n} 个",
|
|
347
|
+
cfBackups: "可清备份 {n} 条(约 {mb} MB)",
|
|
348
|
+
cfEntities: "旧 store 实体 {n} 个(只报告)",
|
|
349
|
+
cfConfirm: "确认执行?旧目录会改名备份并重建链接;超出保留数量的框架备份会被删除。",
|
|
350
|
+
cfNothing: "没有需要清理的项",
|
|
351
|
+
cfCancelled: "已取消(未改动任何文件)",
|
|
352
|
+
cfRunning: "正在执行清理…",
|
|
353
|
+
cfDone: "已改名 {renamed} / 重建链接 {linked} / 删备份 {removed} 条(释放约 {mb} MB)",
|
|
354
|
+
cfFailed: "{n} 项失败(详见 fw/console 日志)",
|
|
355
|
+
compatNoticeDismiss: "我知道了(此版本不再提示)",
|
|
345
356
|
searchInstalledPlaceholder: "搜索已安装插件(名称或 id)",
|
|
346
357
|
extraFilter: "已装",
|
|
347
358
|
extraFilterAll: "全部",
|
|
@@ -489,7 +500,7 @@ window.__ModuleLoader__.load({
|
|
|
489
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",
|
|
490
501
|
archiveUrlPlaceholder: "https://ghproxy.net/https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.tar.gz",
|
|
491
502
|
addArchiveSource: "添加 archive 源",
|
|
492
|
-
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:///
|
|
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。主源优先,失败自动回退下一个",
|
|
493
504
|
addGitSource: "添加 Git 源",
|
|
494
505
|
gitUrlPlaceholder: "如 https://gitee.com/{owner}/{repo}.git",
|
|
495
506
|
gitSourceBtn: "Git 源",
|
|
@@ -871,6 +882,17 @@ window.__ModuleLoader__.load({
|
|
|
871
882
|
githubLoggedIn: "GitHub signed in: ",
|
|
872
883
|
hubUpdateAvailable: "Download update",
|
|
873
884
|
cleanResiduals: "Clean residual backups",
|
|
885
|
+
cleanFrameworkResiduals: "Clean framework residuals",
|
|
886
|
+
cfStale: "{n} stale dirs",
|
|
887
|
+
cfBackups: "{n} backups removable (~{mb} MB)",
|
|
888
|
+
cfEntities: "{n} old store entities (report only)",
|
|
889
|
+
cfConfirm: "Proceed? Stale dirs get renamed+relinked; framework backups beyond the keep count are deleted.",
|
|
890
|
+
cfNothing: "nothing to clean",
|
|
891
|
+
cfCancelled: "cancelled (nothing changed)",
|
|
892
|
+
cfRunning: "cleaning…",
|
|
893
|
+
cfDone: "renamed {renamed} / relinked {linked} / backups removed {removed} (~{mb} MB freed)",
|
|
894
|
+
cfFailed: "{n} failed (see fw/console logs)",
|
|
895
|
+
compatNoticeDismiss: "Got it (hide for this version)",
|
|
874
896
|
githubCornerOut: "Not signed in to GitHub",
|
|
875
897
|
githubLoginViaApp: "GitHub login",
|
|
876
898
|
githubLoginOpened: "Authorization window opened — enter the code on the GitHub page to finish",
|
|
@@ -1028,7 +1050,7 @@ window.__ModuleLoader__.load({
|
|
|
1028
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.",
|
|
1029
1051
|
archiveUrlPlaceholder: "https://ghproxy.net/https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.tar.gz",
|
|
1030
1052
|
addArchiveSource: "Add archive source",
|
|
1031
|
-
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:///
|
|
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",
|
|
1032
1054
|
addGitSource: "Add Git source",
|
|
1033
1055
|
gitUrlPlaceholder: "e.g. https://gitee.com/{owner}/{repo}.git",
|
|
1034
1056
|
gitSourceBtn: "Git sources",
|
|
@@ -4038,6 +4060,7 @@ window.__ModuleLoader__.load({
|
|
|
4038
4060
|
const gatePendingCount = gateReady ? (state.data.compatPending?.pending ?? []).length : 0;
|
|
4039
4061
|
// 门控明细清单(用户 2026-09-24 要求:面板要显示"是哪些插件",不只是数量)
|
|
4040
4062
|
const [gateShowAll, setGateShowAll] = react.useState(false);
|
|
4063
|
+
const [compatNoticeClosed, setCompatNoticeClosed] = react.useState(false);
|
|
4041
4064
|
const gateRows = gateReady ? (state.data.gating ?? null) : null;
|
|
4042
4065
|
const gateTime = (ms) => (typeof ms === "number" && ms > 0 ? new Date(ms).toLocaleString() : "—");
|
|
4043
4066
|
// 「功能包 → 框架」按钮角标:升级进行中 ⟳ / 上次失败 ✕ / 有可用更新 ↑(优先级从高到低)
|
|
@@ -4045,9 +4068,29 @@ window.__ModuleLoader__.load({
|
|
|
4045
4068
|
&& frameworkStatus.status !== "idle" && frameworkStatus.status !== "done" && frameworkStatus.status !== "failed";
|
|
4046
4069
|
const fwBadge = fwRunning ? " ⟳" : (frameworkStatus?.status === "failed" ? " ✕" : (fwCheck?.target ? " ↑" : ""));
|
|
4047
4070
|
return el("section", { ref: sectionRef, className: styles.section, "aria-busy": state.status === "loading" },
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4071
|
+
// 兼容提示(2026-09-28 加法):以前是纯 <p>,关不掉。现在按服务端的 noticeVisible 显示
|
|
4072
|
+
// (老服务端没有该字段时回落 !supported),并给一个手动关闭入口 —— 关闭只影响展示:
|
|
4073
|
+
// 服务端记的是「这个框架版本我看过了」,版本一变会重新提示;supported 与门控语义一个字都不变。
|
|
4074
|
+
state.status === "ready" && state.data.compat
|
|
4075
|
+
&& (state.data.compat.noticeVisible === undefined
|
|
4076
|
+
? !state.data.compat.supported
|
|
4077
|
+
: state.data.compat.noticeVisible === true)
|
|
4078
|
+
&& !compatNoticeClosed
|
|
4079
|
+
? el("p", { className: styles.message, "data-error": "true" },
|
|
4080
|
+
state.data.compat.notice,
|
|
4081
|
+
" ",
|
|
4082
|
+
el("button", {
|
|
4083
|
+
type: "button",
|
|
4084
|
+
style: { marginLeft: "8px", cursor: "pointer" },
|
|
4085
|
+
title: t("compatNoticeDismiss"),
|
|
4086
|
+
onClick: () => {
|
|
4087
|
+
// 失败不收起(关了却还在服务端显示会造成「点了没反应」的错觉)
|
|
4088
|
+
call("/plugin-console/compat-ack", { webAppVersion: state.data.compat?.webAppVersion })
|
|
4089
|
+
.then(() => setCompatNoticeClosed(true), () => {});
|
|
4090
|
+
},
|
|
4091
|
+
}, t("compatNoticeDismiss")),
|
|
4092
|
+
)
|
|
4093
|
+
: null,
|
|
4051
4094
|
state.status === "ready" && comps.length > 0 && details === null && repoInfo === null
|
|
4052
4095
|
? el("button", {
|
|
4053
4096
|
type: "button",
|
|
@@ -4393,6 +4436,49 @@ window.__ModuleLoader__.load({
|
|
|
4393
4436
|
);
|
|
4394
4437
|
},
|
|
4395
4438
|
}, "🧹 " + t("cleanResiduals")),
|
|
4439
|
+
el("button", {
|
|
4440
|
+
type: "button",
|
|
4441
|
+
style: { position: "fixed", left: 12, bottom: 44, zIndex: 1000, opacity: 0.75 },
|
|
4442
|
+
title: t("cleanFrameworkResiduals"),
|
|
4443
|
+
onClick: () => {
|
|
4444
|
+
// 2026-09-28 加法:框架残留清理(顶层旧版本目录 / framework-backups)。两步走:
|
|
4445
|
+
// 先 dryRun 出报告 → window.confirm 让用户看清要动什么 → 才带 confirm:true 真执行。
|
|
4446
|
+
// 服务端默认 dryRun,所以「误点一下」不会动任何文件。
|
|
4447
|
+
const SCOPES = ["framework", "frameworkBackups", "pnpmEntities"];
|
|
4448
|
+
setMessage(t("cleanFrameworkResiduals") + "…");
|
|
4449
|
+
call("/plugin-console/clean-residuals", { scope: SCOPES }).then(
|
|
4450
|
+
(report) => {
|
|
4451
|
+
const fw = report && report.framework ? report.framework : null;
|
|
4452
|
+
const bk = report && report.frameworkBackups ? report.frameworkBackups : null;
|
|
4453
|
+
const pe = report && report.pnpmEntities ? report.pnpmEntities : null;
|
|
4454
|
+
const stale = fw && Array.isArray(fw.planned) ? fw.planned.length : 0;
|
|
4455
|
+
const pend = bk && Array.isArray(bk.plannedRemove) ? bk.plannedRemove : [];
|
|
4456
|
+
const mb = Math.round(pend.reduce((s, it) => s + (it && Number.isFinite(it.sizeBytes) ? it.sizeBytes : 0), 0) / 1048576);
|
|
4457
|
+
const entities = pe && Number.isFinite(pe.entityCount) ? pe.entityCount : 0;
|
|
4458
|
+
const head = t("cleanFrameworkResiduals") + ":" + t("cfStale").replace("{n}", String(stale))
|
|
4459
|
+
+ "、" + t("cfBackups").replace("{n}", String(pend.length)).replace("{mb}", String(mb))
|
|
4460
|
+
+ (entities > 0 ? ";" + t("cfEntities").replace("{n}", String(entities)) : "");
|
|
4461
|
+
if (stale === 0 && pend.length === 0) { setMessage(head + ";" + t("cfNothing")); return; }
|
|
4462
|
+
if (!window.confirm(head + "\n" + t("cfConfirm"))) { setMessage(head + ";" + t("cfCancelled")); return; }
|
|
4463
|
+
setMessage(t("cfRunning"));
|
|
4464
|
+
call("/plugin-console/clean-residuals", { scope: SCOPES, confirm: true, keep: 1 }).then(
|
|
4465
|
+
(done) => {
|
|
4466
|
+
const renamed = done && done.framework && Array.isArray(done.framework.renamed) ? done.framework.renamed.length : 0;
|
|
4467
|
+
const relinked = done && done.framework && Array.isArray(done.framework.relinked) ? done.framework.relinked.length : 0;
|
|
4468
|
+
const removed = done && done.frameworkBackups && Array.isArray(done.frameworkBackups.removed) ? done.frameworkBackups.removed.length : 0;
|
|
4469
|
+
const freed = done && done.frameworkBackups && Number.isFinite(done.frameworkBackups.freedBytes) ? Math.round(done.frameworkBackups.freedBytes / 1048576) : 0;
|
|
4470
|
+
const failed = (done && done.framework && Array.isArray(done.framework.failed) ? done.framework.failed.length : 0)
|
|
4471
|
+
+ (done && done.frameworkBackups && Array.isArray(done.frameworkBackups.failed) ? done.frameworkBackups.failed.length : 0);
|
|
4472
|
+
setMessage(t("cfDone").replace("{renamed}", String(renamed)).replace("{linked}", String(relinked)).replace("{removed}", String(removed)).replace("{mb}", String(freed))
|
|
4473
|
+
+ (failed > 0 ? ";" + t("cfFailed").replace("{n}", String(failed)) : ""));
|
|
4474
|
+
},
|
|
4475
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
4476
|
+
);
|
|
4477
|
+
},
|
|
4478
|
+
(error) => setMessage(t("failed") + ":" + friendlyGithubError(error).message),
|
|
4479
|
+
);
|
|
4480
|
+
},
|
|
4481
|
+
}, "🧰 " + t("cleanFrameworkResiduals")),
|
|
4396
4482
|
fwShowCard
|
|
4397
4483
|
? el("div", { className: styles.detail },
|
|
4398
4484
|
el("div", { className: styles.rowTop },
|
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
|
-
//
|
|
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'
|