@noob-stupid/dsh-plugin-console 0.5.32 → 0.5.34
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 +40 -10
- package/README.zh.md +31 -5
- package/lib/client.js +87 -55
- package/lib/server/domain/compat-verdict.js +241 -0
- package/lib/server/domain/compat.js +88 -74
- package/lib/server/domain/framework-cleanup.js +225 -24
- package/lib/server/domain/framework-residuals.js +52 -3
- package/lib/server/domain/peer-veto.js +103 -0
- package/lib/server/domain/settings-api-scan.js +266 -0
- package/lib/server/routes/plugins.js +25 -12
- package/lib/server/routes/state.js +31 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,44 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<img width="1170" height="609" alt="image" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
8
|
-
|
|
9
7
|
# DSH Plugin Gating Hub (dsh-plugin-gating-hub)
|
|
10
8
|
|
|
11
9
|
*Renamed from `dsh-plugin-hub` — old URLs redirect.*
|
|
12
10
|
|
|
11
|
+
**Framework-upgrade safety and plugin-version gating for DSH.** A failed upgrade rolls back.
|
|
12
|
+
A framework changed by **any** update channel is still guarded. A version the host cannot
|
|
13
|
+
take is refused, not installed.
|
|
14
|
+
|
|
15
|
+
**Install (npm, one line)**
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then restart the dsh service → refresh the page → **Settings → Plugins → Plugin Console**.
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
**Three hard guarantees**
|
|
26
|
+
|
|
27
|
+
- 🛡️ **Contract pre-check → rollback point → auto-rollback.** A data-driven **contract pre-check**
|
|
28
|
+
runs first (message contract / dependency API / config schema / removed API / loader contract);
|
|
29
|
+
then config backup + a full-tree **rollback point**. A failed install or relaunch **auto-rolls
|
|
30
|
+
the tree back**, a 「roll back to previous」 button restores the last version, and plugins the
|
|
31
|
+
new framework cannot load are **auto-disabled** — version check catches fake success, 15-min
|
|
32
|
+
timeout + stall detection.
|
|
33
|
+
- 👁️ **Even when another update channel changes the framework.** An **environment fingerprint**
|
|
34
|
+
catches changes made by the official desktop updater or a manual `pnpm`: it auto-runs a
|
|
35
|
+
**read-only pre-flight**, and disables a row **only with hard evidence** (package unresolvable /
|
|
36
|
+
`file://` target gone / named in a boot-failure log). `@deepseek-ai/*` and **protected / core
|
|
37
|
+
rows are never auto-disabled**; each auto-disable writes a one-command recovery record.
|
|
38
|
+
- 🔒 **Plugin install & upgrade gating.** Version/declaration gate (`dsh.engines.framework` /
|
|
39
|
+
`engines.dsh` / `@deepseek-ai/*` ranges, built-in zero-dependency semver), a **repo-size gate**
|
|
40
|
+
on source channels, and explicit handling of **non-registry sources** — a bare version is
|
|
41
|
+
written back only once the registry really resolves it, otherwise the package is materialized
|
|
42
|
+
as `link:`. Install scripts are never auto-authorized, and pnpm's supply-chain gate is never
|
|
43
|
+
bypassed.
|
|
44
|
+
|
|
13
45
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
14
46
|
[](https://awesome-dsh-plugin.com)
|
|
15
47
|
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/stargazers)
|
|
@@ -22,13 +54,11 @@
|
|
|
22
54
|
[](https://github.com/Noob-stupid/dsh-plugin-gating-hub/releases)[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
23
55
|
[](https://www.dsh.so/artifact/dsh-plugin-hub)
|
|
24
56
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
>
|
|
30
|
-
> API calls) rides on top as the **discovery layer** — and **every source is swappable**: install
|
|
31
|
-
> source (incl. a private intranet registry), search source (URL template + headers), index source
|
|
57
|
+
<img width="1170" height="609" alt="DSH Plugin Gating Hub" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
58
|
+
|
|
59
|
+
> **Discovery layer on top**: a **built-in multi-source plugin market & index** (500+ plugins /
|
|
60
|
+
> 300+ skills, zero GitHub API calls) — and **every source is swappable**: install source
|
|
61
|
+
> (incl. a private intranet registry), search source (URL template + headers), index source
|
|
32
62
|
> (self-hosted intranet index), Git source (incl. a local `file://` bare repo), so plugins can be
|
|
33
63
|
> installed on an **intranet-only or fully offline** machine.
|
|
34
64
|
|
|
@@ -385,7 +415,7 @@ Verification matrix, raw logs and the one framework-side caveat (`0.1.2-rc.1` sh
|
|
|
385
415
|
|
|
386
416
|
---
|
|
387
417
|
|
|
388
|
-
|
|
418
|
+
## Help & Ecosystem
|
|
389
419
|
|
|
390
420
|
- **Panel missing**: restart dsh → refresh → Settings → Plugins → Plugin Console.
|
|
391
421
|
- **Toggle does nothing**: infrastructure rows are "Protected" (by design); normal toggles
|
package/README.zh.md
CHANGED
|
@@ -4,15 +4,41 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
<img width="1170" height="609" alt="image" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
8
|
-
|
|
9
7
|
# DSH 插件门控中心(dsh-plugin-gating-hub)
|
|
10
8
|
|
|
11
9
|
*原名 `dsh-plugin-hub`,已改名;旧链接 301 跳转有效。*
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
**DSH 的框架升级安全 + 插件升级门控。** 升级失败自动回滚。**别的通道**改了框架也照样守门。
|
|
12
|
+
宿主带不动的插件版本直接拒绝,不会装上。
|
|
13
|
+
|
|
14
|
+
**安装(npm,一行)**
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
dsh plugin --profile web add @noob-stupid/dsh-plugin-console
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
然后重启 dsh 服务 → 刷新页面 → **设置 → 插件 → 插件控制台**。
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
**三条最硬的能力**
|
|
25
|
+
|
|
26
|
+
- 🛡️ **升级前契约预检 → 回滚点 → 失败自动回滚。** 先跑数据化的**契约预检**(消息契约 /
|
|
27
|
+
依赖 API / 配置 schema / 被删 API / 装载器契约),再做配置备份 + 全树**回滚点**;
|
|
28
|
+
安装或拉起失败即**全树自动回滚**,升级成功后还有「回滚到上一版」按钮,
|
|
29
|
+
新框架带不动的插件**自动禁用** —— 版本校验防假成功,15 分钟硬超时 + 卡死检测。
|
|
30
|
+
- 👁️ **别的更新通道改了框架,也守门。** **环境指纹**能发现官方桌面端更新器或手动 `pnpm`
|
|
31
|
+
造成的框架变更:变化即自动跑一次**只读预检**,且**有确证证据才**禁用某行
|
|
32
|
+
(包确证解析不到 / `file://` 目标确实不存在 / 启动失败日志点名);`@deepseek-ai/*`(框架自带)
|
|
33
|
+
与**受保护 / 核心行永不禁用**,每次自动禁用都留一条可读的恢复命令。
|
|
34
|
+
- 🔒 **插件安装与升级门控。** 版本/声明门控(`dsh.engines.framework` / `engines.dsh` /
|
|
35
|
+
`@deepseek-ai/*` 范围,内置零依赖 semver)+ 源码通道的**体积门禁** + **非 registry 来源**的
|
|
36
|
+
显式处理(只有在 registry 真能解析到该版本时才写回裸版本号,否则物化成 `link:`);
|
|
37
|
+
安装脚本**绝不自动放行**,pnpm 的供应链闸**绝不绕过**。
|
|
38
|
+
|
|
39
|
+
<img width="1170" height="609" alt="DSH 插件门控中心" src="https://github.com/user-attachments/assets/b802d606-14ba-4151-9956-ff642ed12b0a" />
|
|
40
|
+
|
|
41
|
+
> **上层还带一个发现层**:**内置多源插件市场与自定义索引**(500+ 插件 / 300+ 技能,零 GitHub API 调用)——
|
|
16
42
|
> 而且**每一条源都能换成你自己的**:安装源(含内网私有 registry)、搜索源(URL 模板 + 请求头)、
|
|
17
43
|
> 索引源(内网自建索引)、Git 源(含 `file://` 本地裸仓库),**纯内网 / 断网也能装插件**。
|
|
18
44
|
|
package/lib/client.js
CHANGED
|
@@ -208,6 +208,9 @@ window.__ModuleLoader__.load({
|
|
|
208
208
|
riskyEnableConfirm: "确认强行启用",
|
|
209
209
|
riskyEnableCancel: "取消",
|
|
210
210
|
adoptableDetected: "检测到已适配:当前版本 {version} 已通过源码扫描,点「已适配,立即解锁」即可启用(不会自动开启)",
|
|
211
|
+
adoptableRescinded: "判据已更新:旧版扫描给出的「不兼容」结论已撤回(当前判据下源码扫描干净,版本不变也算)。点「已适配,立即解锁」即可启用(不会自动开启)",
|
|
212
|
+
compatRescindedTag: "判据已撤回·可解锁",
|
|
213
|
+
compatRescindedHint: "这条禁用是旧判据的误报:当前判据重算后不再判「不兼容」。点上方「已适配,立即解锁」即可启用;补丁由你确认后才改。",
|
|
211
214
|
compatGateLabel: "兼容门",
|
|
212
215
|
compatGateAutoDisable: "升级时自动禁用不适配插件",
|
|
213
216
|
compatGateAutoDetect: "打开时自动检测已适配(仅提示)",
|
|
@@ -256,7 +259,6 @@ window.__ModuleLoader__.load({
|
|
|
256
259
|
unloading: "卸载中",
|
|
257
260
|
toggledOn: "已请求启用",
|
|
258
261
|
toggledOff: "已请求停用",
|
|
259
|
-
failed: "操作失败",
|
|
260
262
|
view: "查看",
|
|
261
263
|
install: "添加并启用",
|
|
262
264
|
installing: "正在安装…",
|
|
@@ -527,7 +529,6 @@ window.__ModuleLoader__.load({
|
|
|
527
529
|
sourcesUpdated: "软件源已更新",
|
|
528
530
|
sourceOf: "源:",
|
|
529
531
|
searchSourceTitle: "点击切换搜索源(GitHub / Gitee / 自定义)",
|
|
530
|
-
recentFailures: "最近安装失败",
|
|
531
532
|
installedAt: "已安装",
|
|
532
533
|
checkUpdate: "检测更新",
|
|
533
534
|
checkingUpdate: "检测中…",
|
|
@@ -762,6 +763,9 @@ window.__ModuleLoader__.load({
|
|
|
762
763
|
riskyEnableConfirm: "Force enable",
|
|
763
764
|
riskyEnableCancel: "Cancel",
|
|
764
765
|
adoptableDetected: "Now compatible: version {version} passed the source scan — click \"Adapted - unlock now\" to enable it (never auto-enabled)",
|
|
766
|
+
adoptableRescinded: "Criteria updated: the older scan's \"incompatible\" verdict has been withdrawn (the current criteria find the source clean even without a version change). Click \"Adapted - unlock now\" to enable it (never auto-enabled)",
|
|
767
|
+
compatRescindedTag: "Verdict withdrawn - unlockable",
|
|
768
|
+
compatRescindedHint: "This disable came from a false positive of the older criteria: recomputing with the current criteria no longer says \"incompatible\". Click \"Adapted - unlock now\" above; your patch is only changed after you confirm.",
|
|
765
769
|
compatGateLabel: "Compat gate",
|
|
766
770
|
compatGateAutoDisable: "Auto-disable incompatible plugins on upgrade",
|
|
767
771
|
compatGateAutoDetect: "Detect adapted plugins on open (hint only)",
|
|
@@ -810,7 +814,6 @@ window.__ModuleLoader__.load({
|
|
|
810
814
|
unloading: "Unloading",
|
|
811
815
|
toggledOn: "Enable requested",
|
|
812
816
|
toggledOff: "Disable requested",
|
|
813
|
-
failed: "Operation failed",
|
|
814
817
|
view: "Inspect",
|
|
815
818
|
install: "Add & enable",
|
|
816
819
|
installing: "Installing…",
|
|
@@ -1077,7 +1080,6 @@ window.__ModuleLoader__.load({
|
|
|
1077
1080
|
sourcesUpdated: "Sources updated",
|
|
1078
1081
|
sourceOf: "Source: ",
|
|
1079
1082
|
searchSourceTitle: "Click to switch search source (GitHub / Gitee / custom)",
|
|
1080
|
-
recentFailures: "Recent install failures",
|
|
1081
1083
|
installedAt: "Installed",
|
|
1082
1084
|
checkUpdate: "Check update",
|
|
1083
1085
|
checkingUpdate: "Checking…",
|
|
@@ -1229,6 +1231,21 @@ window.__ModuleLoader__.load({
|
|
|
1229
1231
|
function phaseLabel(phase, t) {
|
|
1230
1232
|
return phase === null ? t("unobserved") : t(PHASE_KEYS[phase]);
|
|
1231
1233
|
}
|
|
1234
|
+
// 0.5.33 改错 ①-2:「已适配,立即解锁」按钮的门控判据 —— 必须与同一卡片正文用的是**同一个**判据。
|
|
1235
|
+
// 现症:按钮只判 pendingCompat === true,不判 entry.adoptable → 正文按 adoptable 说"不适配",
|
|
1236
|
+
// 按钮按 pendingCompat 说"已适配",自相矛盾;点下去 POST /adapt-unlock 必得 409(适配校验 fail),
|
|
1237
|
+
// 补丁与行状态零变化 ⇒ 用户观感"点了没反应"。
|
|
1238
|
+
// 现在只有**真的检测到已适配**(adoptable 是服务端 detectAdoptablePending 给出的对象)才出按钮;
|
|
1239
|
+
// 其余情况不给这个承诺,原因照旧由正文那行(pendingCompatHint + checkNote)如实展示。
|
|
1240
|
+
// 0.5.34 加法:`compatRescinded`(记录里的 fail 是旧判据的误报、已被当前判据撤回)同样给一键解锁入口 ——
|
|
1241
|
+
// 这类行**没有版本变化**,旧判据要求"版本变化"会让用户被永久钉在「待适配」(真机 dshmarket 就是这样)。
|
|
1242
|
+
// 判据本身没放宽:adoptable 仍是服务端算出来的对象(真 fail 的行根本拿不到它)。
|
|
1243
|
+
function canAdaptUnlock(entry) {
|
|
1244
|
+
return entry !== null && entry !== undefined
|
|
1245
|
+
&& (entry.pendingCompat === true || entry.compatRescinded === true)
|
|
1246
|
+
&& entry.adoptable !== null && entry.adoptable !== undefined
|
|
1247
|
+
&& typeof entry.adoptable === "object";
|
|
1248
|
+
}
|
|
1232
1249
|
async function call(path, body) {
|
|
1233
1250
|
const response = await fetch(path, body === undefined
|
|
1234
1251
|
? {}
|
|
@@ -1273,7 +1290,7 @@ window.__ModuleLoader__.load({
|
|
|
1273
1290
|
if (reload) setMessage(t("restarting"));
|
|
1274
1291
|
call("/plugin-console/restart", {}).then(
|
|
1275
1292
|
() => { if (reload) window.setTimeout(() => window.location.reload(), 12000); },
|
|
1276
|
-
(error) => { setMessage(error !== null && typeof error.message === "string" && error.message !== "" ? error.message : t("
|
|
1293
|
+
(error) => { setMessage(error !== null && typeof error.message === "string" && error.message !== "" ? error.message : t("safetySwitchFailed")); },
|
|
1277
1294
|
);
|
|
1278
1295
|
}
|
|
1279
1296
|
/** 超时类错误附上可操作提示(网络黑洞期的最常见表现)。 */
|
|
@@ -1718,7 +1735,7 @@ window.__ModuleLoader__.load({
|
|
|
1718
1735
|
setFwClearing(true);
|
|
1719
1736
|
call("/plugin-console/framework-status-clear", {}).then(
|
|
1720
1737
|
() => { setFrameworkStatus({ status: "idle", message: null }); setFwStatusDismissed(true); setMessage(t("fwRecordCleared")); },
|
|
1721
|
-
(error) => { setMessage(t("
|
|
1738
|
+
(error) => { setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
1722
1739
|
).finally(() => setFwClearing(false));
|
|
1723
1740
|
};
|
|
1724
1741
|
const fwStatusRefresh = () => {
|
|
@@ -1736,7 +1753,7 @@ window.__ModuleLoader__.load({
|
|
|
1736
1753
|
setFwCheckBusy(true);
|
|
1737
1754
|
call("/plugin-console/framework-check", refresh === true ? { refresh: true } : {}).then(
|
|
1738
1755
|
(data) => { setFwCheckBusy(false); if (data && data.ok === true) setFwCheck(data); },
|
|
1739
|
-
(error) => { setFwCheckBusy(false); setMessage(t("
|
|
1756
|
+
(error) => { setFwCheckBusy(false); setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
1740
1757
|
);
|
|
1741
1758
|
};
|
|
1742
1759
|
// 升级前契约预检:先只读预检(抓目标版本会话格式契约 + 扫本地生产方),
|
|
@@ -1753,7 +1770,7 @@ window.__ModuleLoader__.load({
|
|
|
1753
1770
|
setFwPreflightBusy(false);
|
|
1754
1771
|
setFwPreflight((prev) => (apply === true ? { report: (prev && prev.report) ?? null, patch: data } : { report: data, patch: null }));
|
|
1755
1772
|
},
|
|
1756
|
-
(error) => { setFwPreflightBusy(false); setMessage(t("
|
|
1773
|
+
(error) => { setFwPreflightBusy(false); setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
1757
1774
|
);
|
|
1758
1775
|
};
|
|
1759
1776
|
const fwShortPath = (p) => String(p ?? "").replace(/\\/gu, "/").split("/").slice(-3).join("/");
|
|
@@ -1766,7 +1783,7 @@ window.__ModuleLoader__.load({
|
|
|
1766
1783
|
setFrameworkStatus({ status: "rollback", message: t("frameworkRollbackDone").replace("{from}", String(data?.from ?? "?")) });
|
|
1767
1784
|
pollFrameworkStatus();
|
|
1768
1785
|
},
|
|
1769
|
-
(error) => { setFrameworkUpgrading(false); setMessage(t("
|
|
1786
|
+
(error) => { setFrameworkUpgrading(false); setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
1770
1787
|
);
|
|
1771
1788
|
};
|
|
1772
1789
|
// 回滚按钮可用性:已经处在「回滚目标版本」时不再提供回滚(点了等于恢复现状)。
|
|
@@ -2013,10 +2030,10 @@ window.__ModuleLoader__.load({
|
|
|
2013
2030
|
setAiJob({ jobId: data.jobId, source, status: "running", stage: "planning" });
|
|
2014
2031
|
aiPoll(data.jobId);
|
|
2015
2032
|
}
|
|
2016
|
-
else { setAiRunning(false); setMessage(t("
|
|
2033
|
+
else { setAiRunning(false); setMessage(t("safetySwitchFailed") + ":" + (data?.error ?? "未知")); }
|
|
2017
2034
|
} catch (error) {
|
|
2018
2035
|
setAiRunning(false);
|
|
2019
|
-
setMessage(t("
|
|
2036
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2020
2037
|
}
|
|
2021
2038
|
}
|
|
2022
2039
|
async function aiRun() {
|
|
@@ -2035,7 +2052,7 @@ window.__ModuleLoader__.load({
|
|
|
2035
2052
|
aiPoll(aiJob.jobId);
|
|
2036
2053
|
} catch (error) {
|
|
2037
2054
|
setAiRunning(false);
|
|
2038
|
-
setMessage(t("
|
|
2055
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2039
2056
|
}
|
|
2040
2057
|
}
|
|
2041
2058
|
async function aiCancel() {
|
|
@@ -2056,7 +2073,7 @@ window.__ModuleLoader__.load({
|
|
|
2056
2073
|
const data = await call("/plugin-console/component/" + action, { id });
|
|
2057
2074
|
setMessage(t("compActionDone").replace("{action}", t(action === "start" ? "compStart" : "compStop")).replace("{name}", data?.name ?? id));
|
|
2058
2075
|
} catch (error) {
|
|
2059
|
-
setMessage(t("
|
|
2076
|
+
setMessage(t("safetySwitchFailed") + ":" + error.message);
|
|
2060
2077
|
}
|
|
2061
2078
|
setCompBusy(null);
|
|
2062
2079
|
refreshComps();
|
|
@@ -2069,7 +2086,7 @@ window.__ModuleLoader__.load({
|
|
|
2069
2086
|
setMessage(data.running ? (data.healthy ? t("compHealthy") : t("compUnhealthy")) + ":" + (data.name ?? id) : t("compStopped") + ":" + (data.name ?? id));
|
|
2070
2087
|
}
|
|
2071
2088
|
} catch (error) {
|
|
2072
|
-
setMessage(t("
|
|
2089
|
+
setMessage(t("safetySwitchFailed") + ":" + error.message);
|
|
2073
2090
|
}
|
|
2074
2091
|
setCompBusy(null);
|
|
2075
2092
|
refreshComps();
|
|
@@ -2088,7 +2105,7 @@ window.__ModuleLoader__.load({
|
|
|
2088
2105
|
await call("/plugin-console/component/autostart", { id, enabled: on });
|
|
2089
2106
|
setMessage((on ? "✔ 已开启自启动:" : "已关闭自启动:") + id);
|
|
2090
2107
|
} catch (error) {
|
|
2091
|
-
setMessage(t("
|
|
2108
|
+
setMessage(t("safetySwitchFailed") + ":" + error.message);
|
|
2092
2109
|
}
|
|
2093
2110
|
setCompBusy(null);
|
|
2094
2111
|
refreshComps();
|
|
@@ -2500,7 +2517,7 @@ window.__ModuleLoader__.load({
|
|
|
2500
2517
|
setRiskyConfirm({ entryId: entry.entryId, moduleName: details.moduleName ?? entry.moduleName ?? null, frameworkVersion: details.frameworkVersion ?? null, checkNote: details.checkNote ?? null });
|
|
2501
2518
|
return;
|
|
2502
2519
|
}
|
|
2503
|
-
setMessage(t("
|
|
2520
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2504
2521
|
},
|
|
2505
2522
|
).finally(() => setBusy(null));
|
|
2506
2523
|
};
|
|
@@ -2513,7 +2530,7 @@ window.__ModuleLoader__.load({
|
|
|
2513
2530
|
: prev));
|
|
2514
2531
|
setMessage(t("compatGateSaved"));
|
|
2515
2532
|
},
|
|
2516
|
-
(error) => setMessage(t("
|
|
2533
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
2517
2534
|
);
|
|
2518
2535
|
};
|
|
2519
2536
|
const levenshtein = (a, b) => {
|
|
@@ -2704,7 +2721,7 @@ window.__ModuleLoader__.load({
|
|
|
2704
2721
|
},
|
|
2705
2722
|
(error) => {
|
|
2706
2723
|
setLoadingMore(false);
|
|
2707
|
-
setMessage(t("
|
|
2724
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2708
2725
|
},
|
|
2709
2726
|
);
|
|
2710
2727
|
return;
|
|
@@ -2728,7 +2745,7 @@ window.__ModuleLoader__.load({
|
|
|
2728
2745
|
},
|
|
2729
2746
|
(error) => {
|
|
2730
2747
|
setLoadingMore(false);
|
|
2731
|
-
setMessage(t("
|
|
2748
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2732
2749
|
},
|
|
2733
2750
|
);
|
|
2734
2751
|
return;
|
|
@@ -2746,7 +2763,7 @@ window.__ModuleLoader__.load({
|
|
|
2746
2763
|
},
|
|
2747
2764
|
(error) => {
|
|
2748
2765
|
setLoadingMore(false);
|
|
2749
|
-
setMessage(t("
|
|
2766
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2750
2767
|
},
|
|
2751
2768
|
);
|
|
2752
2769
|
return;
|
|
@@ -2784,7 +2801,7 @@ window.__ModuleLoader__.load({
|
|
|
2784
2801
|
},
|
|
2785
2802
|
(error) => {
|
|
2786
2803
|
setLoadingMore(false);
|
|
2787
|
-
setMessage(t("
|
|
2804
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
2788
2805
|
},
|
|
2789
2806
|
),
|
|
2790
2807
|
);
|
|
@@ -2953,7 +2970,7 @@ window.__ModuleLoader__.load({
|
|
|
2953
2970
|
window.setTimeout(() => window.location.reload(), 2500);
|
|
2954
2971
|
}
|
|
2955
2972
|
} else {
|
|
2956
|
-
setMessage(t("
|
|
2973
|
+
setMessage(t("safetySwitchFailed") + ":" + (data.error ?? "未知错误") + diagMessage(data));
|
|
2957
2974
|
}
|
|
2958
2975
|
setInstalling(null);
|
|
2959
2976
|
setTimeout(refresh, 1500);
|
|
@@ -2966,8 +2983,8 @@ window.__ModuleLoader__.load({
|
|
|
2966
2983
|
/** 本地 AI 兜底授权:调用模型 API 产生费用,必须用户明确同意。 */
|
|
2967
2984
|
const aiConsent = (jobId, approved) => {
|
|
2968
2985
|
call("/plugin-console/ai-consent", { jobId, approved }).then(
|
|
2969
|
-
() => { if (!approved) setMessage(t("
|
|
2970
|
-
(error) => setMessage(t("
|
|
2986
|
+
() => { if (!approved) setMessage(t("safetySwitchFailed") + ":已取消本地 AI 兜底(不会调用模型 API)"); },
|
|
2987
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
2971
2988
|
);
|
|
2972
2989
|
};
|
|
2973
2990
|
/** 安装进度文案:服务端 progress{channel,phase,index,total,name,done} → 一行人话。
|
|
@@ -3127,7 +3144,7 @@ window.__ModuleLoader__.load({
|
|
|
3127
3144
|
// clientId 打码回显(如 abc12345…):用户未修改时原样传回服务端,服务端识别打码标记保留原配置
|
|
3128
3145
|
const idValue = giteeClientId.trim();
|
|
3129
3146
|
if (!idValue || !giteeClientSecret.trim()) {
|
|
3130
|
-
setMessage(t("
|
|
3147
|
+
setMessage(t("safetySwitchFailed") + ":client_id / client_secret 不能为空");
|
|
3131
3148
|
return;
|
|
3132
3149
|
}
|
|
3133
3150
|
if (idValue.endsWith("…")) {
|
|
@@ -3139,7 +3156,7 @@ window.__ModuleLoader__.load({
|
|
|
3139
3156
|
const startGiteeOauth = () => {
|
|
3140
3157
|
call("/plugin-console/gitee-oauth-url").then(
|
|
3141
3158
|
(data) => { if (data && data.url) window.open(data.url, "_blank", "noopener"); },
|
|
3142
|
-
(error) => setMessage(t("
|
|
3159
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
3143
3160
|
);
|
|
3144
3161
|
};
|
|
3145
3162
|
const sourcesAction = (payload, done) => {
|
|
@@ -3151,12 +3168,12 @@ window.__ModuleLoader__.load({
|
|
|
3151
3168
|
setMessage(t("sourcesUpdated"));
|
|
3152
3169
|
if (done) done();
|
|
3153
3170
|
},
|
|
3154
|
-
(error) => setMessage(t("
|
|
3171
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
3155
3172
|
).finally(() => setSourcesBusy(false));
|
|
3156
3173
|
};
|
|
3157
3174
|
const addSource = () => {
|
|
3158
3175
|
if (!isAllowedSourceUrl(sourceUrl.trim())) {
|
|
3159
|
-
setMessage(t("
|
|
3176
|
+
setMessage(t("safetySwitchFailed") + ":" + t("invalidSourceUrl"));
|
|
3160
3177
|
return;
|
|
3161
3178
|
}
|
|
3162
3179
|
sourcesAction({ action: "add", name: sourceName.trim(), url: sourceUrl.trim() }, () => { setSourceName(""); setSourceUrl(""); });
|
|
@@ -3166,7 +3183,7 @@ window.__ModuleLoader__.load({
|
|
|
3166
3183
|
setRegScan({ status: "scanning" });
|
|
3167
3184
|
call("/plugin-console/registry-scan", {}).then(
|
|
3168
3185
|
(data) => setRegScan({ status: "ready", results: Array.isArray(data.results) ? data.results : [], scannedAt: typeof data.scannedAt === "number" ? data.scannedAt : null }),
|
|
3169
|
-
(error) => { setRegScan(null); setMessage(t("
|
|
3186
|
+
(error) => { setRegScan(null); setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
3170
3187
|
);
|
|
3171
3188
|
};
|
|
3172
3189
|
// 扫描结果派生视图:每条源的命中结果 + 可达计数 + 最新版本来自哪个源
|
|
@@ -3190,7 +3207,7 @@ window.__ModuleLoader__.load({
|
|
|
3190
3207
|
};
|
|
3191
3208
|
const addIndexSource = () => {
|
|
3192
3209
|
if (!isAllowedSourceUrl(indexSourceUrl.trim())) {
|
|
3193
|
-
setMessage(t("
|
|
3210
|
+
setMessage(t("safetySwitchFailed") + ":" + t("invalidSourceUrl"));
|
|
3194
3211
|
return;
|
|
3195
3212
|
}
|
|
3196
3213
|
sourcesAction({ action: "add-index", name: indexSourceName.trim(), url: indexSourceUrl.trim() }, () => { setIndexSourceName(""); setIndexSourceUrl(""); });
|
|
@@ -3213,11 +3230,11 @@ window.__ModuleLoader__.load({
|
|
|
3213
3230
|
const addGitSource = () => {
|
|
3214
3231
|
const tpl = gitSourceUrl.trim();
|
|
3215
3232
|
if (!tpl.includes("{owner}") || !tpl.includes("{repo}")) {
|
|
3216
|
-
setMessage(t("
|
|
3233
|
+
setMessage(t("safetySwitchFailed") + ":" + t("gitUrlPlaceholder"));
|
|
3217
3234
|
return;
|
|
3218
3235
|
}
|
|
3219
3236
|
if (!isAllowedSourceUrl(tpl)) {
|
|
3220
|
-
setMessage(t("
|
|
3237
|
+
setMessage(t("safetySwitchFailed") + ":" + t("invalidSourceUrl"));
|
|
3221
3238
|
return;
|
|
3222
3239
|
}
|
|
3223
3240
|
sourcesAction({ action: "add-git", name: gitSourceName.trim(), urlTemplate: tpl }, () => { setGitSourceName(""); setGitSourceUrl(""); });
|
|
@@ -3241,7 +3258,7 @@ window.__ModuleLoader__.load({
|
|
|
3241
3258
|
};
|
|
3242
3259
|
const addSearchSource = () => {
|
|
3243
3260
|
if (!isAllowedSourceUrl(searchSourceUrl.trim()) || !searchSourceUrl.includes("{q}")) {
|
|
3244
|
-
setMessage(t("
|
|
3261
|
+
setMessage(t("safetySwitchFailed") + ":" + t("invalidSearchUrl"));
|
|
3245
3262
|
return;
|
|
3246
3263
|
}
|
|
3247
3264
|
// 解析请求头:每行 "名称: 值"
|
|
@@ -3281,7 +3298,7 @@ window.__ModuleLoader__.load({
|
|
|
3281
3298
|
}
|
|
3282
3299
|
},
|
|
3283
3300
|
(error) => {
|
|
3284
|
-
setMessage(t("
|
|
3301
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
3285
3302
|
setInstalling(null);
|
|
3286
3303
|
},
|
|
3287
3304
|
);
|
|
@@ -3353,7 +3370,7 @@ window.__ModuleLoader__.load({
|
|
|
3353
3370
|
},
|
|
3354
3371
|
(error) => {
|
|
3355
3372
|
setTogglingSkill(null);
|
|
3356
|
-
setMessage(t("
|
|
3373
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
3357
3374
|
},
|
|
3358
3375
|
);
|
|
3359
3376
|
};
|
|
@@ -3404,7 +3421,7 @@ window.__ModuleLoader__.load({
|
|
|
3404
3421
|
(error) => {
|
|
3405
3422
|
setFrameworkUpgrading(false);
|
|
3406
3423
|
setConfirmFrameworkUpgrade(false);
|
|
3407
|
-
setMessage(t("
|
|
3424
|
+
setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message);
|
|
3408
3425
|
},
|
|
3409
3426
|
);
|
|
3410
3427
|
};
|
|
@@ -3416,7 +3433,7 @@ window.__ModuleLoader__.load({
|
|
|
3416
3433
|
setConfirmSkillDelete(null);
|
|
3417
3434
|
setInstalledSkills((prev) => prev.filter((s) => s.name !== name));
|
|
3418
3435
|
},
|
|
3419
|
-
(error) => setMessage(t("
|
|
3436
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
3420
3437
|
);
|
|
3421
3438
|
};
|
|
3422
3439
|
const installedQueryNorm = installedQuery.trim().toLowerCase();
|
|
@@ -3470,7 +3487,7 @@ window.__ModuleLoader__.load({
|
|
|
3470
3487
|
el("p", { className: styles.status }, t("loadingDetails")));
|
|
3471
3488
|
} else if (details.status === "error") {
|
|
3472
3489
|
detailPanel = el("div", { className: styles.detail },
|
|
3473
|
-
el("p", { className: styles.status, "data-error": "true" }, t("
|
|
3490
|
+
el("p", { className: styles.status, "data-error": "true" }, t("safetySwitchFailed") + ":" + details.error.message));
|
|
3474
3491
|
} else {
|
|
3475
3492
|
const meta = details.data.meta;
|
|
3476
3493
|
const readme = details.data.readme;
|
|
@@ -3482,7 +3499,11 @@ window.__ModuleLoader__.load({
|
|
|
3482
3499
|
entry.pendingCompat === true
|
|
3483
3500
|
? el("p", { className: styles.status, "data-pending": entry.adoptable ? "false" : "true" },
|
|
3484
3501
|
entry.adoptable
|
|
3485
|
-
?
|
|
3502
|
+
? (entry.adoptable.basis === "verdict-rescinded"
|
|
3503
|
+
// 0.5.34:旧判据的误报已被当前判据撤回(真机 dshmarket 现场)——如实说是"判据更新",
|
|
3504
|
+
// 不能说成"插件已适配"(那会让人以为作者发过新版)
|
|
3505
|
+
? t("adoptableRescinded").replace("{version}", String(entry.adoptable.version ?? "?"))
|
|
3506
|
+
: t("adoptableDetected").replace("{version}", String(entry.adoptable.version ?? "?")).replace("{prev}", String(compatInfo?.version ?? "?")))
|
|
3486
3507
|
: t("pendingCompatHint") + (compatInfo && compatInfo.checkNote ? ";已知校验:" + compatInfo.checkNote : ""))
|
|
3487
3508
|
: null,
|
|
3488
3509
|
meta !== null && meta.version ? el("div", { className: styles.rowTop },
|
|
@@ -3499,7 +3520,7 @@ window.__ModuleLoader__.load({
|
|
|
3499
3520
|
title: t("aiEmpowerTitle"),
|
|
3500
3521
|
onClick: () => openAiFor(String(entry.moduleName ?? entry.repo ?? "")),
|
|
3501
3522
|
}, t("aiEmpowerLabel")),
|
|
3502
|
-
entry
|
|
3523
|
+
canAdaptUnlock(entry)
|
|
3503
3524
|
? el("button", {
|
|
3504
3525
|
type: "button",
|
|
3505
3526
|
className: styles.toggle,
|
|
@@ -3514,7 +3535,7 @@ window.__ModuleLoader__.load({
|
|
|
3514
3535
|
setMessage(t("adaptUnlockDone"));
|
|
3515
3536
|
window.setTimeout(() => window.location.reload(), 1200);
|
|
3516
3537
|
},
|
|
3517
|
-
(error) => { setAdaptUnlocking(null); setMessage(t("
|
|
3538
|
+
(error) => { setAdaptUnlocking(null); setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message); },
|
|
3518
3539
|
);
|
|
3519
3540
|
},
|
|
3520
3541
|
}, adaptUnlocking === entry.entryId ? t("loadingPhase") : t("adaptUnlockBtn"))
|
|
@@ -3586,15 +3607,23 @@ window.__ModuleLoader__.load({
|
|
|
3586
3607
|
phaseLabel(entry.fiberPhase, t)),
|
|
3587
3608
|
el("code", null, entry.entryId),
|
|
3588
3609
|
),
|
|
3610
|
+
// 0.5.33 加法 ①-4:框架 peer 预检**否决**的本地复算结果(服务端 entries[].veto)。
|
|
3611
|
+
// 框架只在内存里禁用、原因只打 stderr —— 这里把它补出来。复用**既有行**内的
|
|
3612
|
+
// status 短句位(不新增常驻面板行),出路放悬浮提示(复用既有 title 位置)。
|
|
3613
|
+
entry.veto && entry.veto.reason
|
|
3614
|
+
? el("p", { className: styles.status, "data-pending": "true", title: entry.veto.hint ?? undefined },
|
|
3615
|
+
entry.veto.reason)
|
|
3616
|
+
: null,
|
|
3589
3617
|
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
3590
3618
|
el("div", { className: styles.rowTop },
|
|
3591
3619
|
el("button", {
|
|
3592
3620
|
type: "button",
|
|
3593
3621
|
className: styles.toggle,
|
|
3594
3622
|
disabled: !entry.toggleable || busy === entry.entryId || entry.pendingCompat === true,
|
|
3595
|
-
|
|
3623
|
+
// 0.5.34:结论已被当前判据撤回的行不再显示「待适配」(旧判据的误报不许当事实展示)
|
|
3624
|
+
title: entry.pendingCompat === true ? (entry.compatRescinded === true ? t("compatRescindedHint") : t("pendingCompatHint")) : undefined,
|
|
3596
3625
|
onClick: () => toggle(entry, !entry.enabled),
|
|
3597
|
-
}, busy === entry.entryId ? t("loadingPhase") : (entry.pendingCompat === true ? t("pendingCompatTag") : t(entry.enabled ? "off" : "on"))),
|
|
3626
|
+
}, busy === entry.entryId ? t("loadingPhase") : (entry.pendingCompat === true ? (entry.compatRescinded === true ? t("compatRescindedTag") : t("pendingCompatTag")) : t(entry.enabled ? "off" : "on"))),
|
|
3598
3627
|
el("button", {
|
|
3599
3628
|
type: "button",
|
|
3600
3629
|
className: styles.toggle,
|
|
@@ -3623,7 +3652,7 @@ window.__ModuleLoader__.load({
|
|
|
3623
3652
|
setTimeout(refresh, 1500);
|
|
3624
3653
|
}
|
|
3625
3654
|
},
|
|
3626
|
-
(error) => setMessage(t("
|
|
3655
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
3627
3656
|
).finally(() => setDeleteBusy(null));
|
|
3628
3657
|
} else {
|
|
3629
3658
|
setConfirmDeleteRowId(entry.entryId);
|
|
@@ -3679,7 +3708,7 @@ window.__ModuleLoader__.load({
|
|
|
3679
3708
|
setFamilyCheck((prev) => ({ ...prev, [root]: { latest: d?.latest ?? null, installed } }));
|
|
3680
3709
|
setMessage(t("familyUpdate") + ":" + root + " " + (d?.latest ?? "?") + (d?.migrate && d.migrate.compatible ? " → " + d.migrate.to + " v" + d.migrate.latest : "") + (d?.error ? "(" + d.error + ")" : ""));
|
|
3681
3710
|
},
|
|
3682
|
-
(e) => setMessage(t("
|
|
3711
|
+
(e) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(e).message),
|
|
3683
3712
|
);
|
|
3684
3713
|
},
|
|
3685
3714
|
}, t("familyCheckUpdate")),
|
|
@@ -3706,7 +3735,7 @@ window.__ModuleLoader__.load({
|
|
|
3706
3735
|
setMessage(t("familyUnlockAllDone") + ":" + unlockedCount + " 行" + (Array.isArray(d?.kept) && d.kept.length > 0 ? ";保留 " + d.kept.length + " 行(未通过校验)" : ""));
|
|
3707
3736
|
window.setTimeout(() => window.location.reload(), 1500);
|
|
3708
3737
|
},
|
|
3709
|
-
(e) => setMessage(t("
|
|
3738
|
+
(e) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(e).message),
|
|
3710
3739
|
);
|
|
3711
3740
|
},
|
|
3712
3741
|
}, t("familyUnlockAll")))
|
|
@@ -3765,7 +3794,7 @@ window.__ModuleLoader__.load({
|
|
|
3765
3794
|
: t("pendingRestartNote") + ":" + (data?.packageName ?? "") + (data?.uninstallError ? "(包卸载警告:" + data.uninstallError + ")" : ""));
|
|
3766
3795
|
refresh();
|
|
3767
3796
|
},
|
|
3768
|
-
(error) => setMessage(t("
|
|
3797
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
3769
3798
|
).finally(() => setDeleteBusy(null));
|
|
3770
3799
|
} else {
|
|
3771
3800
|
setConfirmDeleteRowId(job.jobId);
|
|
@@ -4388,7 +4417,7 @@ window.__ModuleLoader__.load({
|
|
|
4388
4417
|
? el("div", { className: styles.rowTop },
|
|
4389
4418
|
el("p", { className: styles.message, style: { flex: 1, minWidth: 0, wordBreak: "break-all" } }, t("repoLandDone") + repoLandResult.path),
|
|
4390
4419
|
el("button", { type: "button", className: styles.toggle, onClick: repoLandOpenFolder }, t("repoLandOpenFolder")))
|
|
4391
|
-
: el("p", { className: styles.message, "data-error": "true" }, t("
|
|
4420
|
+
: el("p", { className: styles.message, "data-error": "true" }, t("safetySwitchFailed") + ":" + (repoLandResult.error ?? "?")))
|
|
4392
4421
|
: null,
|
|
4393
4422
|
el("strong", { className: styles.name }, t("repoLandList") + "(" + repoLanded.length + ")"),
|
|
4394
4423
|
repoLanded.length === 0
|
|
@@ -4415,7 +4444,7 @@ window.__ModuleLoader__.load({
|
|
|
4415
4444
|
onClick: () => {
|
|
4416
4445
|
call("/plugin-console/framework-relaunch", {}).then(
|
|
4417
4446
|
(data) => { if (data && data.ok === true) setMessage(t("relaunchDone")); },
|
|
4418
|
-
(error) => setMessage(t("
|
|
4447
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
4419
4448
|
);
|
|
4420
4449
|
},
|
|
4421
4450
|
}, t("relaunchBtn"))
|
|
@@ -4432,7 +4461,7 @@ window.__ModuleLoader__.load({
|
|
|
4432
4461
|
const keptNote = keptCount > 0 ? ";保留在用插件 " + keptCount + " 个" : "";
|
|
4433
4462
|
if (data && data.ok === true) setMessage(t("cleanResiduals") + ":" + (data.count === 0 ? "无残余" : "已清理 " + data.count + " 项") + keptNote);
|
|
4434
4463
|
},
|
|
4435
|
-
(error) => setMessage(t("
|
|
4464
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
4436
4465
|
);
|
|
4437
4466
|
},
|
|
4438
4467
|
}, "🧹 " + t("cleanResiduals")),
|
|
@@ -4472,10 +4501,10 @@ window.__ModuleLoader__.load({
|
|
|
4472
4501
|
setMessage(t("cfDone").replace("{renamed}", String(renamed)).replace("{linked}", String(relinked)).replace("{removed}", String(removed)).replace("{mb}", String(freed))
|
|
4473
4502
|
+ (failed > 0 ? ";" + t("cfFailed").replace("{n}", String(failed)) : ""));
|
|
4474
4503
|
},
|
|
4475
|
-
(error) => setMessage(t("
|
|
4504
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
4476
4505
|
);
|
|
4477
4506
|
},
|
|
4478
|
-
(error) => setMessage(t("
|
|
4507
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
4479
4508
|
);
|
|
4480
4509
|
},
|
|
4481
4510
|
}, "🧰 " + t("cleanFrameworkResiduals")),
|
|
@@ -4559,10 +4588,10 @@ const lockWarn = data.lockUpdated === false && typeof data.lockNote === "string"
|
|
|
4559
4588
|
setMessage((data.note ?? "已更新") + (lockWarn === "" ? "" : "。" + lockWarn) + ",正在重启生效…");
|
|
4560
4589
|
restartServiceViaServer(setMessage, { reload: false });
|
|
4561
4590
|
} else {
|
|
4562
|
-
setMessage(t("
|
|
4591
|
+
setMessage(t("safetySwitchFailed") + ":" + (data?.reason ?? "未知"));
|
|
4563
4592
|
}
|
|
4564
4593
|
},
|
|
4565
|
-
(error) => setMessage(t("
|
|
4594
|
+
(error) => setMessage(t("safetySwitchFailed") + ":" + friendlyGithubError(error).message),
|
|
4566
4595
|
);
|
|
4567
4596
|
}
|
|
4568
4597
|
}, "⬇ " + t("hubUpdateAvailable") + " v" + selfUpdate.latest) : null,
|
|
@@ -5146,7 +5175,7 @@ setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
|
|
|
5146
5175
|
? el("p", { className: styles.status }, t("loading"))
|
|
5147
5176
|
: sourcesData.error !== undefined
|
|
5148
5177
|
? el("div", null,
|
|
5149
|
-
el("p", { className: styles.status }, t("
|
|
5178
|
+
el("p", { className: styles.status }, t("safetySwitchFailed") + ":" + sourcesData.error),
|
|
5150
5179
|
el("button", { type: "button", className: styles.toggle, onClick: () => { setSourcesData(null); openSources(); } }, t("retry")))
|
|
5151
5180
|
: el("div", { className: styles.accWrap },
|
|
5152
5181
|
accSection("registries", t("sourcesBtn"), [
|
|
@@ -5704,6 +5733,9 @@ setMessage(t("failed") + ":" + (data?.reason ?? "未知"));
|
|
|
5704
5733
|
// 仅测试用导出(tests/test-plugin-actions.mjs 的离线渲染断言:用假 React 真渲染这个组件,
|
|
5705
5734
|
// 断言"有/无 suggestedAction、执行中、成功、失败、中英切换"五种情形的 DOM 结构)。
|
|
5706
5735
|
exports.SuggestedActionBox = SuggestedActionBox;
|
|
5736
|
+
// 仅测试用导出(tests/test-adapt-unlock-gate.mjs ①-2:断言「已适配,立即解锁」按钮的门控判据,
|
|
5737
|
+
// 以及它必须与卡片正文用同一个判据)。
|
|
5738
|
+
exports.canAdaptUnlock = canAdaptUnlock;
|
|
5707
5739
|
return module.exports;
|
|
5708
5740
|
}
|
|
5709
5741
|
});
|