@dsh-workbench/core 0.3.8 → 0.3.9
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 +2 -1
- package/bin/dsh-workbench +67 -1
- package/client.js +80 -7
- package/dist/client/client.body.js +80 -7
- package/dist/client/index.js +80 -7
- package/dist/host/index.js +141 -5
- package/dist/types/app/layouts/env-gate/boot-gate.d.ts +8 -0
- package/dist/types/shared/api/host/diy-preset-health.d.ts +35 -0
- package/dist/types/shared/api/host/installed-module.d.ts +9 -0
- package/dist/types/shared/api/host/preflight-route.d.ts +15 -0
- package/dist/types/shared/api/host/preflight.d.ts +12 -1
- package/docs/HANDOFF.md +23 -18
- package/docs/OPERATIONS.md +3 -0
- package/package.json +6 -2
- package/scripts/seed-profile.sh +34 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ dsh web (:3080) dsh workbench (:3081)
|
|
|
23
23
|
|
|
24
24
|
| 要有 | 为什么 | 怎么有 |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `dsh` | 本体。本包在 `0.1
|
|
26
|
+
| `dsh` | 本体。本包在 `0.2.1-rc.2` 上验过;**更旧的会被拒绝**(`engines.dsh`,旧到不认识它的 dsh 由启动器当场拒绝并给升级命令)—— DIY 模式是从那一代 dsh 的 preset 清单派生的,旧一代它会整个不出现且不报错 | `npm i -g @deepseek-ai/dsh@latest` |
|
|
27
27
|
| `node` + `pnpm` | dsh 的 `plugin` 子命令是**转发给 pnpm** 的;没有 pnpm 会直接报 `dsh: pnpm was not found` | 各自装 |
|
|
28
28
|
| 跑过一次 `dsh web` | `setup` 拿 `~/.dsh/profiles/web` 当模板(**bundle 清单**:这台机器上装了哪些 bundle),也是「配置同步」的默认来源。没有它会**明确报错**,不会静默建出半个 profile | `dsh web`,起来就行,然后 Ctrl-C |
|
|
29
29
|
|
|
@@ -111,6 +111,7 @@ npx @dsh-workbench/core # ② 起工作台,默认 :3081
|
|
|
111
111
|
| 一切正常,但左栏是出厂样子、右边没有面板 | 工作台插件在 Settings 里被关了(见上一条 ⚠️)。**重启之后**启动器不会再让你拿到这种实例:它读到 `dsh.profile.bundles` 里没有它就直接拒绝启动,并打印 `./bin/dsh-workbench setup` |
|
|
112
112
|
| 模型列表里只有出厂的几个、没有自己配的供应商 | 新装的 profile 不带设置(ADR-0044):设置 → 「配置同步」→ 选来源 → 勾 `llm-` 那几行 → 同步(即生效)|
|
|
113
113
|
| 「配置同步」里某一行说 `@x 还没装` | 那一行点名的包不在这个 profile 的 `node_modules` 里:`dsh plugin --profile workbench add @x`(或在模块市场装),(装完 dsh 会自行重组;它报 `restartReason` 时才重启)|
|
|
114
|
+
| 左栏没有「工作台DIY」那一项、模式下拉里也没有「工作台 DIY」 | 多半是 dsh 太旧(DIY 模式是从 **dsh 0.2.1-rc.2** 的 preset 清单派生的;旧一代那个 preset 挂不起来,而 dsh 的模式选择器**只列挂得起来的** ⇒ 整个不见且不报错)。**启动体检会把它摆出来并停在那一页**(「检查 DIY 模式 · 挂不起来」+ 缺哪几行 + 升级命令 + 一颗「先进入工作台」);启动器更早一步:dsh 太旧就直接拒绝启动/安装并打印 `npm i -g @deepseek-ai/dsh@latest`,装完**重启工作台**即可。想自己核对是哪一行挂不起来:`dsh` 的 设置 → 「Agent presets」里那一行会带红标与原因 |
|
|
114
115
|
|
|
115
116
|
排障全表在仓库的 [`docs/OPERATIONS.md`](https://github.com/code-farmer-i/dsh-workbench/blob/main/packages/workbench/docs/OPERATIONS.md);
|
|
116
117
|
把工作台装到别人机器上的前置清单见 [`docs/HANDOFF.md`](https://github.com/code-farmer-i/dsh-workbench/blob/main/packages/workbench/docs/HANDOFF.md)。
|
package/bin/dsh-workbench
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
# · **仓库副本**(这个包落在一个 git 工作树里):开发用。bundle 以 link 方式装本目录,
|
|
16
16
|
# 改源码立刻生效(配合 `pnpm run dev`)。
|
|
17
17
|
# · **安装包**(从内网源下下来的):`setup` 把 **registry 上那个包** 装进 profile。
|
|
18
|
-
#
|
|
18
|
+
# 判据是「**这个包的 package.json 被那个 git 顶层跟踪**」—— `npm pack` 的产物里不含 .git
|
|
19
|
+
# (见 package.json 的 files);只问"顶层在不在"不够:装出来的那份完全可能落在某个仓库里
|
|
20
|
+
# (`$HOME` 是 dotfiles 仓库、而 npm 前缀就在 `$HOME` 下,实测踩过 —— 症状见下面 GIT_TOP 那段)。
|
|
19
21
|
# ⚠️ 不能判 `$REPO_DIR/.git`:workspace 之后 .git 在**仓库根**,不在这个包里(实测踩过,
|
|
20
22
|
# 判错的症状是"仓库副本却去源上装",改源码不再生效,而且不报错)。
|
|
21
23
|
|
|
@@ -38,12 +40,76 @@ while [ -L "$SELF" ]; do
|
|
|
38
40
|
done
|
|
39
41
|
REPO_DIR="$(cd "$(dirname "$SELF")/.." && pwd -P)"
|
|
40
42
|
GIT_TOP="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
43
|
+
# `rev-parse --show-toplevel` 只说明"**某个祖先目录**是个仓库"—— 装出来的那份完全可能刚好落在
|
|
44
|
+
# 某个仓库里:最常见的是 `$HOME` 本身就是 dotfiles 仓库,而 npm 的前缀(`~/.nvm/...`、
|
|
45
|
+
# `~/.npm-global`…)就在 `$HOME` 下。那会把"装出来的那份"当成仓库副本,以 link 装进 profile:
|
|
46
|
+
# 市场里工作台那一行会说「这份是本仓正在开发的那一份」,而 `npm i -g …@latest` 之后 profile
|
|
47
|
+
# 指向的东西还会悄悄变。所以还得证明**这份 package.json 本身被那个仓库跟踪**(仓库副本才有的事;
|
|
48
|
+
# 与 seed-workshop.sh 那条"工作树里真有 packages/workbench + packages/module-tools"同旨)。
|
|
49
|
+
if [ -n "$GIT_TOP" ] && ! git -C "$REPO_DIR" ls-files --error-unmatch package.json >/dev/null 2>&1; then
|
|
50
|
+
GIT_TOP=""
|
|
51
|
+
fi
|
|
41
52
|
|
|
42
53
|
if ! command -v dsh >/dev/null 2>&1; then
|
|
43
54
|
echo "dsh-workbench: PATH 上没有 dsh。先装:npm i -g @deepseek-ai/dsh" >&2
|
|
44
55
|
exit 1
|
|
45
56
|
fi
|
|
46
57
|
|
|
58
|
+
# ── 硬门禁:dsh 太旧就别起(DIY 模式会整个不见,而且一句错都不报)────────────
|
|
59
|
+
#
|
|
60
|
+
# DIY 模式就是 `preset/diy.patch.yml` 那一行 agent preset,而它是**从 dsh 出厂那份
|
|
61
|
+
# `standard.patch.yml` 派生的**(scripts/gen-diy-preset.mjs)—— 所以它跟的是**派生那一刻的
|
|
62
|
+
# dsh**。dsh 旧到那份清单里有几个包它根本没有时(实测 0.2.0-rc.2 缺
|
|
63
|
+
# `@deepseek-ai/dsh-tool-schedule`),preset 挂不起来;dsh 的模式选择器**只列挂得起来的**
|
|
64
|
+
# (healthy presets only),于是「工作台 DIY」在用户面前整个消失,界面与终端都不吭声。
|
|
65
|
+
# 同一代 dsh 0.2.0 的 `uiWorkspace.startSession()` 也**不收** `agentPreset`(0.2.1-alpha.3 才收),
|
|
66
|
+
# 所以就算 preset 挂上了,「造一个模块」也落不到 DIY 模式 —— 这一档没有"少一把工具"的降级空间。
|
|
67
|
+
#
|
|
68
|
+
# 为什么门禁写在启动器、而不只写 package.json 的 `engines.dsh`:
|
|
69
|
+
# engines.dsh 只有 **0.2.1-alpha.4 起**的 dsh 才读(见 HANDOFF §八),而这台机器上那份 dsh
|
|
70
|
+
# 恰恰可能就是旧到不认识的(0.2.0-rc.2 只读 peerDependencies,而 peer 不能写 ——
|
|
71
|
+
# pnpm 的 auto-install-peers 会把整套 dsh 拖进 profile)。所以旧 dsh 由这里兜。
|
|
72
|
+
#
|
|
73
|
+
# 下限只有一处真相:package.json 的 engines.dsh。这里只读它,别在这儿另写一个版本号。
|
|
74
|
+
DSH_FLOOR="$(WB_MANIFEST="$REPO_DIR/package.json" node -e 'const p = require(process.env.WB_MANIFEST); const e = p.engines ?? {}; process.stdout.write(typeof e.dsh === "string" ? e.dsh : "");' 2>/dev/null || true)"
|
|
75
|
+
DSH_VERSION="$(dsh --version 2>/dev/null | tr -d '[:space:]' | sed 's/^v//' || true)"
|
|
76
|
+
|
|
77
|
+
if [ -n "$DSH_FLOOR" ] && [ -n "$DSH_VERSION" ]; then
|
|
78
|
+
# `>=X.Y.Z[-pre]` 一档:与 dsh 自己那条预检同一口径(semver + includePrerelease)。
|
|
79
|
+
# 认不出的下限形状就不拦 —— 这条是给用户的诊断门,权威仍是 dsh 自己的预检。
|
|
80
|
+
DSH_FLOOR="$DSH_FLOOR" DSH_VERSION="$DSH_VERSION" node -e '
|
|
81
|
+
const floor = /^>=\s*(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec((process.env.DSH_FLOOR ?? "").trim());
|
|
82
|
+
const got = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec((process.env.DSH_VERSION ?? "").trim());
|
|
83
|
+
if (floor === null || got === null) process.exit(0);
|
|
84
|
+
const want = { nums: [+floor[1], +floor[2], +floor[3]], pre: floor[4] === undefined ? [] : floor[4].split(".") };
|
|
85
|
+
const have = { nums: [+got[1], +got[2], +got[3]], pre: got[4] === undefined ? [] : got[4].split(".") };
|
|
86
|
+
for (let i = 0; i < 3; i += 1) {
|
|
87
|
+
if (have.nums[i] !== want.nums[i]) process.exit(have.nums[i] > want.nums[i] ? 0 : 1);
|
|
88
|
+
}
|
|
89
|
+
if (have.pre.length === 0) process.exit(0);
|
|
90
|
+
if (want.pre.length === 0) process.exit(1);
|
|
91
|
+
for (let i = 0; i < Math.max(have.pre.length, want.pre.length); i += 1) {
|
|
92
|
+
const a = have.pre[i];
|
|
93
|
+
const b = want.pre[i];
|
|
94
|
+
if (a === undefined) process.exit(1);
|
|
95
|
+
if (b === undefined) process.exit(0);
|
|
96
|
+
const an = /^\d+$/.test(a);
|
|
97
|
+
const bn = /^\d+$/.test(b);
|
|
98
|
+
if (an && bn) { if (+a !== +b) process.exit(+a > +b ? 0 : 1); continue; }
|
|
99
|
+
if (an !== bn) process.exit(an ? 1 : 0);
|
|
100
|
+
if (a !== b) process.exit(a > b ? 0 : 1);
|
|
101
|
+
}
|
|
102
|
+
process.exit(0);
|
|
103
|
+
' || DSH_FLOOR_GUARD=$?
|
|
104
|
+
if [ "${DSH_FLOOR_GUARD:-0}" -eq 1 ]; then
|
|
105
|
+
echo "dsh-workbench: 这台机器上的 dsh 是 ${DSH_VERSION},这份外壳要 ${DSH_FLOOR}。" >&2
|
|
106
|
+
echo " 低于它时 DIY 模式(会话里的「工作台 DIY」)挂不起来,而 dsh 的模式选择器只列挂得起来的" >&2
|
|
107
|
+
echo " —— 它会**整个不出现**,不报错。所以这里直接拒绝启动。" >&2
|
|
108
|
+
echo " 升级 dsh(再重启一次工作台): npm i -g @deepseek-ai/dsh@latest" >&2
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
|
|
47
113
|
pkg_version() {
|
|
48
114
|
node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo "0.0.0"
|
|
49
115
|
}
|
package/client.js
CHANGED
|
@@ -2781,7 +2781,8 @@ window.__ModuleLoader__.load({
|
|
|
2781
2781
|
"env.stepFailed.shell": "执行通道不可用",
|
|
2782
2782
|
"env.bridge.ok": "已连接",
|
|
2783
2783
|
"env.bridge.short": "没连上",
|
|
2784
|
-
"env.bridge.why": "
|
|
2784
|
+
"env.bridge.why": "面板的数据要走你浏览器里的登录态:这个通道没连上,模块就取不到数据(不拦路:修好它,或者先进入工作台)。",
|
|
2785
|
+
"env.bridge.hold": "浏览器通道还没连上 —— 按下面修好后点「重试」,也可以先进工作台。",
|
|
2785
2786
|
"env.bridge.daemon": "后台服务没起来:正常它会自己起,先在终端跑下面这条命令。",
|
|
2786
2787
|
"env.bridge.extension": "浏览器扩展没连上:在 Chrome 应用商店装一次就好(装完回来点「重试」)。已经装过的话,先跑下面这条命令。",
|
|
2787
2788
|
"env.bridge.extensionLink": "去 Chrome 应用商店装扩展",
|
|
@@ -2799,11 +2800,21 @@ window.__ModuleLoader__.load({
|
|
|
2799
2800
|
"env.fix.network": "网络不通:检查网络或代理设置后重试。",
|
|
2800
2801
|
"env.fix.details": "详细输出(排障用)",
|
|
2801
2802
|
"env.retry": "重试",
|
|
2803
|
+
"env.bridge.continue": "先进入工作台",
|
|
2804
|
+
"env.diy.ok": "可用",
|
|
2805
|
+
"env.diy.short": "挂不起来",
|
|
2806
|
+
"env.diy.hold": "DIY 模式挂不起来 —— 升级 dsh 后点「重试」,也可以先进工作台。",
|
|
2807
|
+
"env.diy.why": "「工作台 DIY」是 dsh 的一个 agent preset,而它是照这份外壳验过的那一代 dsh 生成的:这台机器上的 dsh 比它旧,其中几行 dsh 根本没有,于是这个模式整个挂不起来。dsh 的模式选择器只列挂得起来的,进去之后它会整个不出现 —— 面板与其它功能不受影响。",
|
|
2808
|
+
"env.diy.rows": "这一代 dsh 缺这几行:{rows}",
|
|
2809
|
+
"env.diy.noRow": "体检没在 dsh 的 preset 名单里看到「工作台 DIY」—— 多半是这份 bundle 的 preset 补丁没生效。",
|
|
2810
|
+
"env.diy.step": "升级 dsh,然后重启一次工作台:",
|
|
2811
|
+
"env.diy.continue": "先进入工作台",
|
|
2802
2812
|
"env.copyCommand": "复制命令",
|
|
2803
2813
|
"env.copied": "已复制",
|
|
2804
2814
|
"env.step.opencli": "检测工作台的 opencli",
|
|
2805
2815
|
"env.step.codingPlugin": "检测 coding 插件",
|
|
2806
2816
|
"env.step.bridge": "检测浏览器通道",
|
|
2817
|
+
"env.step.diy": "检查 DIY 模式",
|
|
2807
2818
|
"env.step.warm": "预热站点登录态",
|
|
2808
2819
|
"env.warm.done": "已预热 {n} 个站点",
|
|
2809
2820
|
"env.warm.short": "这次没热上 —— 不影响用,面板第一次读取会自己补",
|
|
@@ -3225,7 +3236,8 @@ window.__ModuleLoader__.load({
|
|
|
3225
3236
|
"env.stepFailed.shell": "no shell channel",
|
|
3226
3237
|
"env.bridge.ok": "Connected",
|
|
3227
3238
|
"env.bridge.short": "Not connected",
|
|
3228
|
-
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup
|
|
3239
|
+
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup: fix it, or enter the workbench first.",
|
|
3240
|
+
"env.bridge.hold": "The browser bridge is not connected yet — fix it below and hit Retry, or enter the workbench first.",
|
|
3229
3241
|
"env.bridge.daemon": "The background service is not running. It usually starts by itself — run the command below in your terminal first.",
|
|
3230
3242
|
"env.bridge.extension": "The browser extension is not connected. Install it once from the Chrome Web Store (then hit Retry here). If it is already installed, run the command below first.",
|
|
3231
3243
|
"env.bridge.extensionLink": "Get the extension in the Chrome Web Store",
|
|
@@ -3243,11 +3255,21 @@ window.__ModuleLoader__.load({
|
|
|
3243
3255
|
"env.fix.network": "Network unreachable: check your network or proxy, then retry.",
|
|
3244
3256
|
"env.fix.details": "Details (for troubleshooting)",
|
|
3245
3257
|
"env.retry": "Retry",
|
|
3258
|
+
"env.bridge.continue": "Enter the workbench anyway",
|
|
3259
|
+
"env.diy.ok": "Available",
|
|
3260
|
+
"env.diy.short": "Cannot load",
|
|
3261
|
+
"env.diy.hold": "DIY mode cannot load — upgrade dsh, then hit Retry; or enter the workbench first.",
|
|
3262
|
+
"env.diy.why": "“Workbench DIY” is a dsh agent preset, and it is generated from the dsh generation this shell was verified against. The dsh on this machine is older and does not ship some of those rows, so the whole preset fails to mount. dsh lists only the presets that mount, so the mode will simply not appear — panels and everything else are unaffected.",
|
|
3263
|
+
"env.diy.rows": "This dsh is missing these rows: {rows}",
|
|
3264
|
+
"env.diy.noRow": "The health check did not find “Workbench DIY” in dsh’s preset roster — most likely this bundle’s preset patch did not apply.",
|
|
3265
|
+
"env.diy.step": "Upgrade dsh, then restart the workbench:",
|
|
3266
|
+
"env.diy.continue": "Enter the workbench anyway",
|
|
3246
3267
|
"env.copyCommand": "Copy command",
|
|
3247
3268
|
"env.copied": "Copied",
|
|
3248
3269
|
"env.step.opencli": "Checking the workbench opencli",
|
|
3249
3270
|
"env.step.codingPlugin": "Checking the coding plugin",
|
|
3250
3271
|
"env.step.bridge": "Checking the browser bridge",
|
|
3272
|
+
"env.step.diy": "Checking DIY mode",
|
|
3251
3273
|
"env.step.warm": "Warming up site sessions",
|
|
3252
3274
|
"env.warm.done": "{n} site(s) warmed up",
|
|
3253
3275
|
"env.warm.short": "Not warmed this time — harmless; the panel heals on first read",
|
|
@@ -7799,6 +7821,10 @@ window.__ModuleLoader__.load({
|
|
|
7799
7821
|
if (step.status === "ok") return t("env.bridge.ok");
|
|
7800
7822
|
if (step.status === "warn") return t("env.bridge.short");
|
|
7801
7823
|
}
|
|
7824
|
+
if (step.id === "diy") {
|
|
7825
|
+
if (step.status === "ok") return t("env.diy.ok");
|
|
7826
|
+
if (step.status === "warn") return t("env.diy.short");
|
|
7827
|
+
}
|
|
7802
7828
|
if (step.id === "warm") {
|
|
7803
7829
|
if (step.status === "ok") return t("env.warm.done", { n: step.detail ?? "0" });
|
|
7804
7830
|
if (step.status === "warn") return t("env.warm.short");
|
|
@@ -7894,6 +7920,7 @@ window.__ModuleLoader__.load({
|
|
|
7894
7920
|
function stepLabel$1(id, t) {
|
|
7895
7921
|
if (id === "opencli") return t("env.step.opencli");
|
|
7896
7922
|
if (id === "warm") return t("env.step.warm");
|
|
7923
|
+
if (id === "diy") return t("env.step.diy");
|
|
7897
7924
|
return t("env.step.bridge");
|
|
7898
7925
|
}
|
|
7899
7926
|
function stepMark(step) {
|
|
@@ -7921,6 +7948,18 @@ window.__ModuleLoader__.load({
|
|
|
7921
7948
|
}
|
|
7922
7949
|
};
|
|
7923
7950
|
}
|
|
7951
|
+
/**
|
|
7952
|
+
* 拿到这一拍之后门该怎么办:`close` 撤掉放行、`hold` 停在原地等用户动手、`wait` 接着轮询。
|
|
7953
|
+
* 桥接坏了(`bridgeIssues` 非空)与 DIY 模式挂不起来(`diyGap` 非空)都是**不拦路**的,
|
|
7954
|
+
* 但那块「怎么解决」得先真的画出来 —— 停在原地 + 一颗「先进入工作台」才叫不拦路,
|
|
7955
|
+
* 就绪即撤等于把那块提示连同修法一起丢掉,用户进去只会发现「工作台 DIY」整个不在。
|
|
7956
|
+
*/
|
|
7957
|
+
function gateActionOf(snapshot) {
|
|
7958
|
+
if (snapshot.phase === "failed") return "hold";
|
|
7959
|
+
if (snapshot.phase !== "ready") return "wait";
|
|
7960
|
+
if ((snapshot.bridgeIssues?.length ?? 0) > 0) return "hold";
|
|
7961
|
+
return snapshot.diyGap === void 0 ? "close" : "hold";
|
|
7962
|
+
}
|
|
7924
7963
|
function commandRow(t) {
|
|
7925
7964
|
const row = el("div", BootGate_module_default.commandRow);
|
|
7926
7965
|
const code = el("code", BootGate_module_default.command);
|
|
@@ -7993,13 +8032,32 @@ window.__ModuleLoader__.load({
|
|
|
7993
8032
|
const bridgeCommand = commandRow(t);
|
|
7994
8033
|
const bridgeRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
7995
8034
|
bridgeRetry.setAttribute("type", "button");
|
|
8035
|
+
const bridgeSkip = el("button", BootGate_module_default.button, t("env.bridge.continue"));
|
|
8036
|
+
bridgeSkip.setAttribute("type", "button");
|
|
7996
8037
|
const bridgeButtons = el("div", BootGate_module_default.buttons);
|
|
7997
|
-
bridgeButtons.append(bridgeRetry);
|
|
8038
|
+
bridgeButtons.append(bridgeRetry, bridgeSkip);
|
|
7998
8039
|
const bridgeRaw = el("details", BootGate_module_default.raw);
|
|
7999
8040
|
const bridgeRawText = el("pre", BootGate_module_default.error);
|
|
8000
8041
|
bridgeRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), bridgeRawText);
|
|
8001
8042
|
bridge.append(bridgeTitle, bridgeWhy, bridgeLines, bridgeCommand.row, bridgeButtons, bridgeRaw);
|
|
8002
|
-
|
|
8043
|
+
const diy = el("div", BootGate_module_default.bridge);
|
|
8044
|
+
diy.hidden = true;
|
|
8045
|
+
const diyTitle = el("h2", BootGate_module_default.fixTitle, t("env.fix.title"));
|
|
8046
|
+
const diyWhy = el("p", BootGate_module_default.hint, t("env.diy.why"));
|
|
8047
|
+
const diyLines = el("ul", BootGate_module_default.bridgeLines);
|
|
8048
|
+
const diyStep = el("p", BootGate_module_default.hint, t("env.diy.step"));
|
|
8049
|
+
const diyCommand = commandRow(t);
|
|
8050
|
+
const diyRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
8051
|
+
diyRetry.setAttribute("type", "button");
|
|
8052
|
+
const diySkip = el("button", BootGate_module_default.button, t("env.diy.continue"));
|
|
8053
|
+
diySkip.setAttribute("type", "button");
|
|
8054
|
+
const diyButtons = el("div", BootGate_module_default.buttons);
|
|
8055
|
+
diyButtons.append(diyRetry, diySkip);
|
|
8056
|
+
const diyRaw = el("details", BootGate_module_default.raw);
|
|
8057
|
+
const diyRawText = el("pre", BootGate_module_default.error);
|
|
8058
|
+
diyRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), diyRawText);
|
|
8059
|
+
diy.append(diyTitle, diyWhy, diyLines, diyStep, diyCommand.row, diyButtons, diyRaw);
|
|
8060
|
+
card.append(title, hint, list, progressRow, actions, bridge, diy);
|
|
8003
8061
|
overlay.append(card);
|
|
8004
8062
|
document.body.append(overlay);
|
|
8005
8063
|
const previousInert = root?.inert ?? false;
|
|
@@ -8057,7 +8115,7 @@ window.__ModuleLoader__.load({
|
|
|
8057
8115
|
return row;
|
|
8058
8116
|
}
|
|
8059
8117
|
function render(snapshot) {
|
|
8060
|
-
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : t("env.checking");
|
|
8118
|
+
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : gateActionOf(snapshot) === "hold" ? (snapshot.bridgeIssues?.length ?? 0) > 0 ? t("env.bridge.hold") : t("env.diy.hold") : t("env.checking");
|
|
8061
8119
|
const items = snapshot.steps.map((step) => {
|
|
8062
8120
|
const row = ensureRow(step);
|
|
8063
8121
|
row.item.dataset.status = step.status;
|
|
@@ -8115,6 +8173,15 @@ window.__ModuleLoader__.load({
|
|
|
8115
8173
|
bridgeRawText.textContent = bridgeStep.detail ?? "";
|
|
8116
8174
|
bridgeRaw.hidden = (bridgeStep.detail ?? "") === "";
|
|
8117
8175
|
}
|
|
8176
|
+
const gap = snapshot.diyGap;
|
|
8177
|
+
diy.hidden = gap === void 0;
|
|
8178
|
+
if (gap !== void 0) {
|
|
8179
|
+
const lines = gap.rows.length === 0 ? [t("env.diy.noRow")] : [t("env.diy.rows", { rows: gap.rows.join("、") })];
|
|
8180
|
+
diyLines.replaceChildren(...lines.map((text) => el("li", BootGate_module_default.bridgeLine, text)));
|
|
8181
|
+
diyCommand.set(gap.command);
|
|
8182
|
+
diyRawText.textContent = gap.detail;
|
|
8183
|
+
diyRaw.hidden = gap.detail === "";
|
|
8184
|
+
}
|
|
8118
8185
|
}
|
|
8119
8186
|
async function poll() {
|
|
8120
8187
|
if (cancelled) return;
|
|
@@ -8126,19 +8193,22 @@ window.__ModuleLoader__.load({
|
|
|
8126
8193
|
return;
|
|
8127
8194
|
}
|
|
8128
8195
|
render(response);
|
|
8129
|
-
|
|
8196
|
+
const action = gateActionOf(response);
|
|
8197
|
+
if (action === "close") {
|
|
8130
8198
|
close();
|
|
8131
8199
|
return;
|
|
8132
8200
|
}
|
|
8133
|
-
if (
|
|
8201
|
+
if (action === "hold") return;
|
|
8134
8202
|
timer = setTimeout(() => void poll(), POLL_MS);
|
|
8135
8203
|
}
|
|
8136
8204
|
function retryNow() {
|
|
8137
8205
|
forcePlan.again();
|
|
8138
8206
|
actions.hidden = true;
|
|
8139
8207
|
bridge.hidden = true;
|
|
8208
|
+
diy.hidden = true;
|
|
8140
8209
|
retry.setAttribute("disabled", "");
|
|
8141
8210
|
bridgeRetry.setAttribute("disabled", "");
|
|
8211
|
+
diyRetry.setAttribute("disabled", "");
|
|
8142
8212
|
hint.textContent = t("env.checking");
|
|
8143
8213
|
if (pinned !== null) {
|
|
8144
8214
|
render(pinned);
|
|
@@ -8148,6 +8218,9 @@ window.__ModuleLoader__.load({
|
|
|
8148
8218
|
}
|
|
8149
8219
|
retry.addEventListener("click", retryNow);
|
|
8150
8220
|
bridgeRetry.addEventListener("click", retryNow);
|
|
8221
|
+
bridgeSkip.addEventListener("click", close);
|
|
8222
|
+
diyRetry.addEventListener("click", retryNow);
|
|
8223
|
+
diySkip.addEventListener("click", close);
|
|
8151
8224
|
const search = typeof window === "undefined" ? "" : window.location?.search ?? "";
|
|
8152
8225
|
if (devSurface.on(search)) {
|
|
8153
8226
|
const strip = devSurface.gateStrip({ onPick: (snapshot) => {
|
|
@@ -2774,7 +2774,8 @@ var dictionaries = {
|
|
|
2774
2774
|
"env.stepFailed.shell": "执行通道不可用",
|
|
2775
2775
|
"env.bridge.ok": "已连接",
|
|
2776
2776
|
"env.bridge.short": "没连上",
|
|
2777
|
-
"env.bridge.why": "
|
|
2777
|
+
"env.bridge.why": "面板的数据要走你浏览器里的登录态:这个通道没连上,模块就取不到数据(不拦路:修好它,或者先进入工作台)。",
|
|
2778
|
+
"env.bridge.hold": "浏览器通道还没连上 —— 按下面修好后点「重试」,也可以先进工作台。",
|
|
2778
2779
|
"env.bridge.daemon": "后台服务没起来:正常它会自己起,先在终端跑下面这条命令。",
|
|
2779
2780
|
"env.bridge.extension": "浏览器扩展没连上:在 Chrome 应用商店装一次就好(装完回来点「重试」)。已经装过的话,先跑下面这条命令。",
|
|
2780
2781
|
"env.bridge.extensionLink": "去 Chrome 应用商店装扩展",
|
|
@@ -2792,11 +2793,21 @@ var dictionaries = {
|
|
|
2792
2793
|
"env.fix.network": "网络不通:检查网络或代理设置后重试。",
|
|
2793
2794
|
"env.fix.details": "详细输出(排障用)",
|
|
2794
2795
|
"env.retry": "重试",
|
|
2796
|
+
"env.bridge.continue": "先进入工作台",
|
|
2797
|
+
"env.diy.ok": "可用",
|
|
2798
|
+
"env.diy.short": "挂不起来",
|
|
2799
|
+
"env.diy.hold": "DIY 模式挂不起来 —— 升级 dsh 后点「重试」,也可以先进工作台。",
|
|
2800
|
+
"env.diy.why": "「工作台 DIY」是 dsh 的一个 agent preset,而它是照这份外壳验过的那一代 dsh 生成的:这台机器上的 dsh 比它旧,其中几行 dsh 根本没有,于是这个模式整个挂不起来。dsh 的模式选择器只列挂得起来的,进去之后它会整个不出现 —— 面板与其它功能不受影响。",
|
|
2801
|
+
"env.diy.rows": "这一代 dsh 缺这几行:{rows}",
|
|
2802
|
+
"env.diy.noRow": "体检没在 dsh 的 preset 名单里看到「工作台 DIY」—— 多半是这份 bundle 的 preset 补丁没生效。",
|
|
2803
|
+
"env.diy.step": "升级 dsh,然后重启一次工作台:",
|
|
2804
|
+
"env.diy.continue": "先进入工作台",
|
|
2795
2805
|
"env.copyCommand": "复制命令",
|
|
2796
2806
|
"env.copied": "已复制",
|
|
2797
2807
|
"env.step.opencli": "检测工作台的 opencli",
|
|
2798
2808
|
"env.step.codingPlugin": "检测 coding 插件",
|
|
2799
2809
|
"env.step.bridge": "检测浏览器通道",
|
|
2810
|
+
"env.step.diy": "检查 DIY 模式",
|
|
2800
2811
|
"env.step.warm": "预热站点登录态",
|
|
2801
2812
|
"env.warm.done": "已预热 {n} 个站点",
|
|
2802
2813
|
"env.warm.short": "这次没热上 —— 不影响用,面板第一次读取会自己补",
|
|
@@ -3218,7 +3229,8 @@ var dictionaries = {
|
|
|
3218
3229
|
"env.stepFailed.shell": "no shell channel",
|
|
3219
3230
|
"env.bridge.ok": "Connected",
|
|
3220
3231
|
"env.bridge.short": "Not connected",
|
|
3221
|
-
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup
|
|
3232
|
+
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup: fix it, or enter the workbench first.",
|
|
3233
|
+
"env.bridge.hold": "The browser bridge is not connected yet — fix it below and hit Retry, or enter the workbench first.",
|
|
3222
3234
|
"env.bridge.daemon": "The background service is not running. It usually starts by itself — run the command below in your terminal first.",
|
|
3223
3235
|
"env.bridge.extension": "The browser extension is not connected. Install it once from the Chrome Web Store (then hit Retry here). If it is already installed, run the command below first.",
|
|
3224
3236
|
"env.bridge.extensionLink": "Get the extension in the Chrome Web Store",
|
|
@@ -3236,11 +3248,21 @@ var dictionaries = {
|
|
|
3236
3248
|
"env.fix.network": "Network unreachable: check your network or proxy, then retry.",
|
|
3237
3249
|
"env.fix.details": "Details (for troubleshooting)",
|
|
3238
3250
|
"env.retry": "Retry",
|
|
3251
|
+
"env.bridge.continue": "Enter the workbench anyway",
|
|
3252
|
+
"env.diy.ok": "Available",
|
|
3253
|
+
"env.diy.short": "Cannot load",
|
|
3254
|
+
"env.diy.hold": "DIY mode cannot load — upgrade dsh, then hit Retry; or enter the workbench first.",
|
|
3255
|
+
"env.diy.why": "“Workbench DIY” is a dsh agent preset, and it is generated from the dsh generation this shell was verified against. The dsh on this machine is older and does not ship some of those rows, so the whole preset fails to mount. dsh lists only the presets that mount, so the mode will simply not appear — panels and everything else are unaffected.",
|
|
3256
|
+
"env.diy.rows": "This dsh is missing these rows: {rows}",
|
|
3257
|
+
"env.diy.noRow": "The health check did not find “Workbench DIY” in dsh’s preset roster — most likely this bundle’s preset patch did not apply.",
|
|
3258
|
+
"env.diy.step": "Upgrade dsh, then restart the workbench:",
|
|
3259
|
+
"env.diy.continue": "Enter the workbench anyway",
|
|
3239
3260
|
"env.copyCommand": "Copy command",
|
|
3240
3261
|
"env.copied": "Copied",
|
|
3241
3262
|
"env.step.opencli": "Checking the workbench opencli",
|
|
3242
3263
|
"env.step.codingPlugin": "Checking the coding plugin",
|
|
3243
3264
|
"env.step.bridge": "Checking the browser bridge",
|
|
3265
|
+
"env.step.diy": "Checking DIY mode",
|
|
3244
3266
|
"env.step.warm": "Warming up site sessions",
|
|
3245
3267
|
"env.warm.done": "{n} site(s) warmed up",
|
|
3246
3268
|
"env.warm.short": "Not warmed this time — harmless; the panel heals on first read",
|
|
@@ -7792,6 +7814,10 @@ function stepDetailText(step, snapshot, t) {
|
|
|
7792
7814
|
if (step.status === "ok") return t("env.bridge.ok");
|
|
7793
7815
|
if (step.status === "warn") return t("env.bridge.short");
|
|
7794
7816
|
}
|
|
7817
|
+
if (step.id === "diy") {
|
|
7818
|
+
if (step.status === "ok") return t("env.diy.ok");
|
|
7819
|
+
if (step.status === "warn") return t("env.diy.short");
|
|
7820
|
+
}
|
|
7795
7821
|
if (step.id === "warm") {
|
|
7796
7822
|
if (step.status === "ok") return t("env.warm.done", { n: step.detail ?? "0" });
|
|
7797
7823
|
if (step.status === "warn") return t("env.warm.short");
|
|
@@ -7887,6 +7913,7 @@ var GIVE_UP_MS = 12e4;
|
|
|
7887
7913
|
function stepLabel$1(id, t) {
|
|
7888
7914
|
if (id === "opencli") return t("env.step.opencli");
|
|
7889
7915
|
if (id === "warm") return t("env.step.warm");
|
|
7916
|
+
if (id === "diy") return t("env.step.diy");
|
|
7890
7917
|
return t("env.step.bridge");
|
|
7891
7918
|
}
|
|
7892
7919
|
function stepMark(step) {
|
|
@@ -7914,6 +7941,18 @@ function createForcePlan() {
|
|
|
7914
7941
|
}
|
|
7915
7942
|
};
|
|
7916
7943
|
}
|
|
7944
|
+
/**
|
|
7945
|
+
* 拿到这一拍之后门该怎么办:`close` 撤掉放行、`hold` 停在原地等用户动手、`wait` 接着轮询。
|
|
7946
|
+
* 桥接坏了(`bridgeIssues` 非空)与 DIY 模式挂不起来(`diyGap` 非空)都是**不拦路**的,
|
|
7947
|
+
* 但那块「怎么解决」得先真的画出来 —— 停在原地 + 一颗「先进入工作台」才叫不拦路,
|
|
7948
|
+
* 就绪即撤等于把那块提示连同修法一起丢掉,用户进去只会发现「工作台 DIY」整个不在。
|
|
7949
|
+
*/
|
|
7950
|
+
function gateActionOf(snapshot) {
|
|
7951
|
+
if (snapshot.phase === "failed") return "hold";
|
|
7952
|
+
if (snapshot.phase !== "ready") return "wait";
|
|
7953
|
+
if ((snapshot.bridgeIssues?.length ?? 0) > 0) return "hold";
|
|
7954
|
+
return snapshot.diyGap === void 0 ? "close" : "hold";
|
|
7955
|
+
}
|
|
7917
7956
|
function commandRow(t) {
|
|
7918
7957
|
const row = el("div", BootGate_module_default.commandRow);
|
|
7919
7958
|
const code = el("code", BootGate_module_default.command);
|
|
@@ -7986,13 +8025,32 @@ function createBootGate(ctx, options = {}) {
|
|
|
7986
8025
|
const bridgeCommand = commandRow(t);
|
|
7987
8026
|
const bridgeRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
7988
8027
|
bridgeRetry.setAttribute("type", "button");
|
|
8028
|
+
const bridgeSkip = el("button", BootGate_module_default.button, t("env.bridge.continue"));
|
|
8029
|
+
bridgeSkip.setAttribute("type", "button");
|
|
7989
8030
|
const bridgeButtons = el("div", BootGate_module_default.buttons);
|
|
7990
|
-
bridgeButtons.append(bridgeRetry);
|
|
8031
|
+
bridgeButtons.append(bridgeRetry, bridgeSkip);
|
|
7991
8032
|
const bridgeRaw = el("details", BootGate_module_default.raw);
|
|
7992
8033
|
const bridgeRawText = el("pre", BootGate_module_default.error);
|
|
7993
8034
|
bridgeRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), bridgeRawText);
|
|
7994
8035
|
bridge.append(bridgeTitle, bridgeWhy, bridgeLines, bridgeCommand.row, bridgeButtons, bridgeRaw);
|
|
7995
|
-
|
|
8036
|
+
const diy = el("div", BootGate_module_default.bridge);
|
|
8037
|
+
diy.hidden = true;
|
|
8038
|
+
const diyTitle = el("h2", BootGate_module_default.fixTitle, t("env.fix.title"));
|
|
8039
|
+
const diyWhy = el("p", BootGate_module_default.hint, t("env.diy.why"));
|
|
8040
|
+
const diyLines = el("ul", BootGate_module_default.bridgeLines);
|
|
8041
|
+
const diyStep = el("p", BootGate_module_default.hint, t("env.diy.step"));
|
|
8042
|
+
const diyCommand = commandRow(t);
|
|
8043
|
+
const diyRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
8044
|
+
diyRetry.setAttribute("type", "button");
|
|
8045
|
+
const diySkip = el("button", BootGate_module_default.button, t("env.diy.continue"));
|
|
8046
|
+
diySkip.setAttribute("type", "button");
|
|
8047
|
+
const diyButtons = el("div", BootGate_module_default.buttons);
|
|
8048
|
+
diyButtons.append(diyRetry, diySkip);
|
|
8049
|
+
const diyRaw = el("details", BootGate_module_default.raw);
|
|
8050
|
+
const diyRawText = el("pre", BootGate_module_default.error);
|
|
8051
|
+
diyRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), diyRawText);
|
|
8052
|
+
diy.append(diyTitle, diyWhy, diyLines, diyStep, diyCommand.row, diyButtons, diyRaw);
|
|
8053
|
+
card.append(title, hint, list, progressRow, actions, bridge, diy);
|
|
7996
8054
|
overlay.append(card);
|
|
7997
8055
|
document.body.append(overlay);
|
|
7998
8056
|
const previousInert = root?.inert ?? false;
|
|
@@ -8050,7 +8108,7 @@ function createBootGate(ctx, options = {}) {
|
|
|
8050
8108
|
return row;
|
|
8051
8109
|
}
|
|
8052
8110
|
function render(snapshot) {
|
|
8053
|
-
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : t("env.checking");
|
|
8111
|
+
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : gateActionOf(snapshot) === "hold" ? (snapshot.bridgeIssues?.length ?? 0) > 0 ? t("env.bridge.hold") : t("env.diy.hold") : t("env.checking");
|
|
8054
8112
|
const items = snapshot.steps.map((step) => {
|
|
8055
8113
|
const row = ensureRow(step);
|
|
8056
8114
|
row.item.dataset.status = step.status;
|
|
@@ -8108,6 +8166,15 @@ function createBootGate(ctx, options = {}) {
|
|
|
8108
8166
|
bridgeRawText.textContent = bridgeStep.detail ?? "";
|
|
8109
8167
|
bridgeRaw.hidden = (bridgeStep.detail ?? "") === "";
|
|
8110
8168
|
}
|
|
8169
|
+
const gap = snapshot.diyGap;
|
|
8170
|
+
diy.hidden = gap === void 0;
|
|
8171
|
+
if (gap !== void 0) {
|
|
8172
|
+
const lines = gap.rows.length === 0 ? [t("env.diy.noRow")] : [t("env.diy.rows", { rows: gap.rows.join("、") })];
|
|
8173
|
+
diyLines.replaceChildren(...lines.map((text) => el("li", BootGate_module_default.bridgeLine, text)));
|
|
8174
|
+
diyCommand.set(gap.command);
|
|
8175
|
+
diyRawText.textContent = gap.detail;
|
|
8176
|
+
diyRaw.hidden = gap.detail === "";
|
|
8177
|
+
}
|
|
8111
8178
|
}
|
|
8112
8179
|
async function poll() {
|
|
8113
8180
|
if (cancelled) return;
|
|
@@ -8119,19 +8186,22 @@ function createBootGate(ctx, options = {}) {
|
|
|
8119
8186
|
return;
|
|
8120
8187
|
}
|
|
8121
8188
|
render(response);
|
|
8122
|
-
|
|
8189
|
+
const action = gateActionOf(response);
|
|
8190
|
+
if (action === "close") {
|
|
8123
8191
|
close();
|
|
8124
8192
|
return;
|
|
8125
8193
|
}
|
|
8126
|
-
if (
|
|
8194
|
+
if (action === "hold") return;
|
|
8127
8195
|
timer = setTimeout(() => void poll(), POLL_MS);
|
|
8128
8196
|
}
|
|
8129
8197
|
function retryNow() {
|
|
8130
8198
|
forcePlan.again();
|
|
8131
8199
|
actions.hidden = true;
|
|
8132
8200
|
bridge.hidden = true;
|
|
8201
|
+
diy.hidden = true;
|
|
8133
8202
|
retry.setAttribute("disabled", "");
|
|
8134
8203
|
bridgeRetry.setAttribute("disabled", "");
|
|
8204
|
+
diyRetry.setAttribute("disabled", "");
|
|
8135
8205
|
hint.textContent = t("env.checking");
|
|
8136
8206
|
if (pinned !== null) {
|
|
8137
8207
|
render(pinned);
|
|
@@ -8141,6 +8211,9 @@ function createBootGate(ctx, options = {}) {
|
|
|
8141
8211
|
}
|
|
8142
8212
|
retry.addEventListener("click", retryNow);
|
|
8143
8213
|
bridgeRetry.addEventListener("click", retryNow);
|
|
8214
|
+
bridgeSkip.addEventListener("click", close);
|
|
8215
|
+
diyRetry.addEventListener("click", retryNow);
|
|
8216
|
+
diySkip.addEventListener("click", close);
|
|
8144
8217
|
const search = typeof window === "undefined" ? "" : window.location?.search ?? "";
|
|
8145
8218
|
if (devSurface.on(search)) {
|
|
8146
8219
|
const strip = devSurface.gateStrip({ onPick: (snapshot) => {
|
package/dist/client/index.js
CHANGED
|
@@ -2777,7 +2777,8 @@ var dictionaries = {
|
|
|
2777
2777
|
"env.stepFailed.shell": "执行通道不可用",
|
|
2778
2778
|
"env.bridge.ok": "已连接",
|
|
2779
2779
|
"env.bridge.short": "没连上",
|
|
2780
|
-
"env.bridge.why": "
|
|
2780
|
+
"env.bridge.why": "面板的数据要走你浏览器里的登录态:这个通道没连上,模块就取不到数据(不拦路:修好它,或者先进入工作台)。",
|
|
2781
|
+
"env.bridge.hold": "浏览器通道还没连上 —— 按下面修好后点「重试」,也可以先进工作台。",
|
|
2781
2782
|
"env.bridge.daemon": "后台服务没起来:正常它会自己起,先在终端跑下面这条命令。",
|
|
2782
2783
|
"env.bridge.extension": "浏览器扩展没连上:在 Chrome 应用商店装一次就好(装完回来点「重试」)。已经装过的话,先跑下面这条命令。",
|
|
2783
2784
|
"env.bridge.extensionLink": "去 Chrome 应用商店装扩展",
|
|
@@ -2795,11 +2796,21 @@ var dictionaries = {
|
|
|
2795
2796
|
"env.fix.network": "网络不通:检查网络或代理设置后重试。",
|
|
2796
2797
|
"env.fix.details": "详细输出(排障用)",
|
|
2797
2798
|
"env.retry": "重试",
|
|
2799
|
+
"env.bridge.continue": "先进入工作台",
|
|
2800
|
+
"env.diy.ok": "可用",
|
|
2801
|
+
"env.diy.short": "挂不起来",
|
|
2802
|
+
"env.diy.hold": "DIY 模式挂不起来 —— 升级 dsh 后点「重试」,也可以先进工作台。",
|
|
2803
|
+
"env.diy.why": "「工作台 DIY」是 dsh 的一个 agent preset,而它是照这份外壳验过的那一代 dsh 生成的:这台机器上的 dsh 比它旧,其中几行 dsh 根本没有,于是这个模式整个挂不起来。dsh 的模式选择器只列挂得起来的,进去之后它会整个不出现 —— 面板与其它功能不受影响。",
|
|
2804
|
+
"env.diy.rows": "这一代 dsh 缺这几行:{rows}",
|
|
2805
|
+
"env.diy.noRow": "体检没在 dsh 的 preset 名单里看到「工作台 DIY」—— 多半是这份 bundle 的 preset 补丁没生效。",
|
|
2806
|
+
"env.diy.step": "升级 dsh,然后重启一次工作台:",
|
|
2807
|
+
"env.diy.continue": "先进入工作台",
|
|
2798
2808
|
"env.copyCommand": "复制命令",
|
|
2799
2809
|
"env.copied": "已复制",
|
|
2800
2810
|
"env.step.opencli": "检测工作台的 opencli",
|
|
2801
2811
|
"env.step.codingPlugin": "检测 coding 插件",
|
|
2802
2812
|
"env.step.bridge": "检测浏览器通道",
|
|
2813
|
+
"env.step.diy": "检查 DIY 模式",
|
|
2803
2814
|
"env.step.warm": "预热站点登录态",
|
|
2804
2815
|
"env.warm.done": "已预热 {n} 个站点",
|
|
2805
2816
|
"env.warm.short": "这次没热上 —— 不影响用,面板第一次读取会自己补",
|
|
@@ -3221,7 +3232,8 @@ var dictionaries = {
|
|
|
3221
3232
|
"env.stepFailed.shell": "no shell channel",
|
|
3222
3233
|
"env.bridge.ok": "Connected",
|
|
3223
3234
|
"env.bridge.short": "Not connected",
|
|
3224
|
-
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup
|
|
3235
|
+
"env.bridge.why": "Panel data goes through the login state in your browser: with this channel down, modules cannot fetch data. It does not block startup: fix it, or enter the workbench first.",
|
|
3236
|
+
"env.bridge.hold": "The browser bridge is not connected yet — fix it below and hit Retry, or enter the workbench first.",
|
|
3225
3237
|
"env.bridge.daemon": "The background service is not running. It usually starts by itself — run the command below in your terminal first.",
|
|
3226
3238
|
"env.bridge.extension": "The browser extension is not connected. Install it once from the Chrome Web Store (then hit Retry here). If it is already installed, run the command below first.",
|
|
3227
3239
|
"env.bridge.extensionLink": "Get the extension in the Chrome Web Store",
|
|
@@ -3239,11 +3251,21 @@ var dictionaries = {
|
|
|
3239
3251
|
"env.fix.network": "Network unreachable: check your network or proxy, then retry.",
|
|
3240
3252
|
"env.fix.details": "Details (for troubleshooting)",
|
|
3241
3253
|
"env.retry": "Retry",
|
|
3254
|
+
"env.bridge.continue": "Enter the workbench anyway",
|
|
3255
|
+
"env.diy.ok": "Available",
|
|
3256
|
+
"env.diy.short": "Cannot load",
|
|
3257
|
+
"env.diy.hold": "DIY mode cannot load — upgrade dsh, then hit Retry; or enter the workbench first.",
|
|
3258
|
+
"env.diy.why": "“Workbench DIY” is a dsh agent preset, and it is generated from the dsh generation this shell was verified against. The dsh on this machine is older and does not ship some of those rows, so the whole preset fails to mount. dsh lists only the presets that mount, so the mode will simply not appear — panels and everything else are unaffected.",
|
|
3259
|
+
"env.diy.rows": "This dsh is missing these rows: {rows}",
|
|
3260
|
+
"env.diy.noRow": "The health check did not find “Workbench DIY” in dsh’s preset roster — most likely this bundle’s preset patch did not apply.",
|
|
3261
|
+
"env.diy.step": "Upgrade dsh, then restart the workbench:",
|
|
3262
|
+
"env.diy.continue": "Enter the workbench anyway",
|
|
3242
3263
|
"env.copyCommand": "Copy command",
|
|
3243
3264
|
"env.copied": "Copied",
|
|
3244
3265
|
"env.step.opencli": "Checking the workbench opencli",
|
|
3245
3266
|
"env.step.codingPlugin": "Checking the coding plugin",
|
|
3246
3267
|
"env.step.bridge": "Checking the browser bridge",
|
|
3268
|
+
"env.step.diy": "Checking DIY mode",
|
|
3247
3269
|
"env.step.warm": "Warming up site sessions",
|
|
3248
3270
|
"env.warm.done": "{n} site(s) warmed up",
|
|
3249
3271
|
"env.warm.short": "Not warmed this time — harmless; the panel heals on first read",
|
|
@@ -7795,6 +7817,10 @@ function stepDetailText(step, snapshot, t) {
|
|
|
7795
7817
|
if (step.status === "ok") return t("env.bridge.ok");
|
|
7796
7818
|
if (step.status === "warn") return t("env.bridge.short");
|
|
7797
7819
|
}
|
|
7820
|
+
if (step.id === "diy") {
|
|
7821
|
+
if (step.status === "ok") return t("env.diy.ok");
|
|
7822
|
+
if (step.status === "warn") return t("env.diy.short");
|
|
7823
|
+
}
|
|
7798
7824
|
if (step.id === "warm") {
|
|
7799
7825
|
if (step.status === "ok") return t("env.warm.done", { n: step.detail ?? "0" });
|
|
7800
7826
|
if (step.status === "warn") return t("env.warm.short");
|
|
@@ -7890,6 +7916,7 @@ var GIVE_UP_MS = 12e4;
|
|
|
7890
7916
|
function stepLabel$1(id, t) {
|
|
7891
7917
|
if (id === "opencli") return t("env.step.opencli");
|
|
7892
7918
|
if (id === "warm") return t("env.step.warm");
|
|
7919
|
+
if (id === "diy") return t("env.step.diy");
|
|
7893
7920
|
return t("env.step.bridge");
|
|
7894
7921
|
}
|
|
7895
7922
|
function stepMark(step) {
|
|
@@ -7917,6 +7944,18 @@ function createForcePlan() {
|
|
|
7917
7944
|
}
|
|
7918
7945
|
};
|
|
7919
7946
|
}
|
|
7947
|
+
/**
|
|
7948
|
+
* 拿到这一拍之后门该怎么办:`close` 撤掉放行、`hold` 停在原地等用户动手、`wait` 接着轮询。
|
|
7949
|
+
* 桥接坏了(`bridgeIssues` 非空)与 DIY 模式挂不起来(`diyGap` 非空)都是**不拦路**的,
|
|
7950
|
+
* 但那块「怎么解决」得先真的画出来 —— 停在原地 + 一颗「先进入工作台」才叫不拦路,
|
|
7951
|
+
* 就绪即撤等于把那块提示连同修法一起丢掉,用户进去只会发现「工作台 DIY」整个不在。
|
|
7952
|
+
*/
|
|
7953
|
+
function gateActionOf(snapshot) {
|
|
7954
|
+
if (snapshot.phase === "failed") return "hold";
|
|
7955
|
+
if (snapshot.phase !== "ready") return "wait";
|
|
7956
|
+
if ((snapshot.bridgeIssues?.length ?? 0) > 0) return "hold";
|
|
7957
|
+
return snapshot.diyGap === void 0 ? "close" : "hold";
|
|
7958
|
+
}
|
|
7920
7959
|
function commandRow(t) {
|
|
7921
7960
|
const row = el("div", BootGate_module_default.commandRow);
|
|
7922
7961
|
const code = el("code", BootGate_module_default.command);
|
|
@@ -7989,13 +8028,32 @@ function createBootGate(ctx, options = {}) {
|
|
|
7989
8028
|
const bridgeCommand = commandRow(t);
|
|
7990
8029
|
const bridgeRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
7991
8030
|
bridgeRetry.setAttribute("type", "button");
|
|
8031
|
+
const bridgeSkip = el("button", BootGate_module_default.button, t("env.bridge.continue"));
|
|
8032
|
+
bridgeSkip.setAttribute("type", "button");
|
|
7992
8033
|
const bridgeButtons = el("div", BootGate_module_default.buttons);
|
|
7993
|
-
bridgeButtons.append(bridgeRetry);
|
|
8034
|
+
bridgeButtons.append(bridgeRetry, bridgeSkip);
|
|
7994
8035
|
const bridgeRaw = el("details", BootGate_module_default.raw);
|
|
7995
8036
|
const bridgeRawText = el("pre", BootGate_module_default.error);
|
|
7996
8037
|
bridgeRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), bridgeRawText);
|
|
7997
8038
|
bridge.append(bridgeTitle, bridgeWhy, bridgeLines, bridgeCommand.row, bridgeButtons, bridgeRaw);
|
|
7998
|
-
|
|
8039
|
+
const diy = el("div", BootGate_module_default.bridge);
|
|
8040
|
+
diy.hidden = true;
|
|
8041
|
+
const diyTitle = el("h2", BootGate_module_default.fixTitle, t("env.fix.title"));
|
|
8042
|
+
const diyWhy = el("p", BootGate_module_default.hint, t("env.diy.why"));
|
|
8043
|
+
const diyLines = el("ul", BootGate_module_default.bridgeLines);
|
|
8044
|
+
const diyStep = el("p", BootGate_module_default.hint, t("env.diy.step"));
|
|
8045
|
+
const diyCommand = commandRow(t);
|
|
8046
|
+
const diyRetry = el("button", BootGate_module_default.button, t("env.retry"));
|
|
8047
|
+
diyRetry.setAttribute("type", "button");
|
|
8048
|
+
const diySkip = el("button", BootGate_module_default.button, t("env.diy.continue"));
|
|
8049
|
+
diySkip.setAttribute("type", "button");
|
|
8050
|
+
const diyButtons = el("div", BootGate_module_default.buttons);
|
|
8051
|
+
diyButtons.append(diyRetry, diySkip);
|
|
8052
|
+
const diyRaw = el("details", BootGate_module_default.raw);
|
|
8053
|
+
const diyRawText = el("pre", BootGate_module_default.error);
|
|
8054
|
+
diyRaw.append(el("summary", BootGate_module_default.rawSummary, t("env.fix.details")), diyRawText);
|
|
8055
|
+
diy.append(diyTitle, diyWhy, diyLines, diyStep, diyCommand.row, diyButtons, diyRaw);
|
|
8056
|
+
card.append(title, hint, list, progressRow, actions, bridge, diy);
|
|
7999
8057
|
overlay.append(card);
|
|
8000
8058
|
document.body.append(overlay);
|
|
8001
8059
|
const previousInert = root?.inert ?? false;
|
|
@@ -8053,7 +8111,7 @@ function createBootGate(ctx, options = {}) {
|
|
|
8053
8111
|
return row;
|
|
8054
8112
|
}
|
|
8055
8113
|
function render(snapshot) {
|
|
8056
|
-
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : t("env.checking");
|
|
8114
|
+
hint.textContent = snapshot.phase === "installing" ? t("env.installing") : snapshot.phase === "failed" ? t("env.failed") : gateActionOf(snapshot) === "hold" ? (snapshot.bridgeIssues?.length ?? 0) > 0 ? t("env.bridge.hold") : t("env.diy.hold") : t("env.checking");
|
|
8057
8115
|
const items = snapshot.steps.map((step) => {
|
|
8058
8116
|
const row = ensureRow(step);
|
|
8059
8117
|
row.item.dataset.status = step.status;
|
|
@@ -8111,6 +8169,15 @@ function createBootGate(ctx, options = {}) {
|
|
|
8111
8169
|
bridgeRawText.textContent = bridgeStep.detail ?? "";
|
|
8112
8170
|
bridgeRaw.hidden = (bridgeStep.detail ?? "") === "";
|
|
8113
8171
|
}
|
|
8172
|
+
const gap = snapshot.diyGap;
|
|
8173
|
+
diy.hidden = gap === void 0;
|
|
8174
|
+
if (gap !== void 0) {
|
|
8175
|
+
const lines = gap.rows.length === 0 ? [t("env.diy.noRow")] : [t("env.diy.rows", { rows: gap.rows.join("、") })];
|
|
8176
|
+
diyLines.replaceChildren(...lines.map((text) => el("li", BootGate_module_default.bridgeLine, text)));
|
|
8177
|
+
diyCommand.set(gap.command);
|
|
8178
|
+
diyRawText.textContent = gap.detail;
|
|
8179
|
+
diyRaw.hidden = gap.detail === "";
|
|
8180
|
+
}
|
|
8114
8181
|
}
|
|
8115
8182
|
async function poll() {
|
|
8116
8183
|
if (cancelled) return;
|
|
@@ -8122,19 +8189,22 @@ function createBootGate(ctx, options = {}) {
|
|
|
8122
8189
|
return;
|
|
8123
8190
|
}
|
|
8124
8191
|
render(response);
|
|
8125
|
-
|
|
8192
|
+
const action = gateActionOf(response);
|
|
8193
|
+
if (action === "close") {
|
|
8126
8194
|
close();
|
|
8127
8195
|
return;
|
|
8128
8196
|
}
|
|
8129
|
-
if (
|
|
8197
|
+
if (action === "hold") return;
|
|
8130
8198
|
timer = setTimeout(() => void poll(), POLL_MS);
|
|
8131
8199
|
}
|
|
8132
8200
|
function retryNow() {
|
|
8133
8201
|
forcePlan.again();
|
|
8134
8202
|
actions.hidden = true;
|
|
8135
8203
|
bridge.hidden = true;
|
|
8204
|
+
diy.hidden = true;
|
|
8136
8205
|
retry.setAttribute("disabled", "");
|
|
8137
8206
|
bridgeRetry.setAttribute("disabled", "");
|
|
8207
|
+
diyRetry.setAttribute("disabled", "");
|
|
8138
8208
|
hint.textContent = t("env.checking");
|
|
8139
8209
|
if (pinned !== null) {
|
|
8140
8210
|
render(pinned);
|
|
@@ -8144,6 +8214,9 @@ function createBootGate(ctx, options = {}) {
|
|
|
8144
8214
|
}
|
|
8145
8215
|
retry.addEventListener("click", retryNow);
|
|
8146
8216
|
bridgeRetry.addEventListener("click", retryNow);
|
|
8217
|
+
bridgeSkip.addEventListener("click", close);
|
|
8218
|
+
diyRetry.addEventListener("click", retryNow);
|
|
8219
|
+
diySkip.addEventListener("click", close);
|
|
8147
8220
|
const search = typeof window === "undefined" ? "" : window.location?.search ?? "";
|
|
8148
8221
|
if (devSurface.on(search)) {
|
|
8149
8222
|
const strip = devSurface.gateStrip({ onPick: (snapshot) => {
|
package/dist/host/index.js
CHANGED
|
@@ -2364,6 +2364,9 @@ function createWorkspaceStore(options) {
|
|
|
2364
2364
|
}
|
|
2365
2365
|
};
|
|
2366
2366
|
}
|
|
2367
|
+
//#endregion
|
|
2368
|
+
//#region src/shared/api/diy.ts
|
|
2369
|
+
var DIY_PRESET_ID = "workbench-diy";
|
|
2367
2370
|
var WORKSHOP_RELATIVE = "workbench/diy";
|
|
2368
2371
|
var WORKSHOP_TITLE = "工作台DIY";
|
|
2369
2372
|
//#endregion
|
|
@@ -2712,8 +2715,23 @@ function readInstalledModule(profileDir, pkg) {
|
|
|
2712
2715
|
...registry === "" ? {} : { registry }
|
|
2713
2716
|
};
|
|
2714
2717
|
}
|
|
2718
|
+
var PACKED_ARTIFACT = /\.(?:tgz|tar\.gz|tar)$/i;
|
|
2719
|
+
/**
|
|
2720
|
+
* 这份依赖是不是"从一个**打好的包**装的"(`file:/tmp/dsh-workbench-core-0.3.8.tgz`、
|
|
2721
|
+
* `./x-0.3.8.tgz`)。它跟 `link:` / 目录装的**不是**一回事:那是工作树(改了立刻生效,
|
|
2722
|
+
* 也不可能跟源上比出个新旧),而 tgz 是打包产物 —— 包名与版本就是源上那一套,能比、
|
|
2723
|
+
* 也该让人更新。混在一起会把"从文件装的发布产物"说成"本仓正在开发的那一份",
|
|
2724
|
+
* 顺带把版本比对与更新一起关掉。
|
|
2725
|
+
*/
|
|
2726
|
+
function isPackedArtifactSpec(spec) {
|
|
2727
|
+
const trimmed = spec.trim();
|
|
2728
|
+
const pkgPath = trimmed.startsWith("file:") ? trimmed.slice(5) : trimmed;
|
|
2729
|
+
return PACKED_ARTIFACT.test(pkgPath);
|
|
2730
|
+
}
|
|
2731
|
+
/** 这份依赖是不是"本仓那份"(`link:` / 目录 / 相对或绝对路径)—— 只有工作树才算。 */
|
|
2715
2732
|
function isLocalDependencySpec(spec) {
|
|
2716
2733
|
const trimmed = spec.trim();
|
|
2734
|
+
if (isPackedArtifactSpec(trimmed)) return false;
|
|
2717
2735
|
return trimmed.startsWith("link:") || trimmed.startsWith("file:") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/");
|
|
2718
2736
|
}
|
|
2719
2737
|
function readProfilePackage(profileDir, pkg) {
|
|
@@ -11891,6 +11909,75 @@ function createContextInjector(options) {
|
|
|
11891
11909
|
} };
|
|
11892
11910
|
}
|
|
11893
11911
|
//#endregion
|
|
11912
|
+
//#region src/shared/api/host/diy-preset-health.ts
|
|
11913
|
+
/**
|
|
11914
|
+
* DIY 模式挂不起来时给用户的那条修法:升级 dsh,再重启一次工作台(启动器那道拒绝用的是同一条)。
|
|
11915
|
+
*/
|
|
11916
|
+
var DIY_UPGRADE_COMMAND = "npm i -g @deepseek-ai/dsh@latest";
|
|
11917
|
+
/**
|
|
11918
|
+
* 把 dsh 名册里那句 `broken` 原文拆成一项一个行名。
|
|
11919
|
+
* @param detail - registry 给的原文,一行一项,形如 `tool-schedule (@deepseek-ai/dsh-tool-schedule): never started`。
|
|
11920
|
+
* @returns 每行 `(` 之前那一段;空原文给空数组。
|
|
11921
|
+
*/
|
|
11922
|
+
function brokenRowsOf(detail) {
|
|
11923
|
+
const rows = [];
|
|
11924
|
+
for (const line of String(detail ?? "").split("\n")) {
|
|
11925
|
+
const text = line.trim();
|
|
11926
|
+
if (text === "") continue;
|
|
11927
|
+
const at = text.indexOf(" (");
|
|
11928
|
+
rows.push(at < 0 ? text : text.slice(0, at));
|
|
11929
|
+
}
|
|
11930
|
+
return rows;
|
|
11931
|
+
}
|
|
11932
|
+
/**
|
|
11933
|
+
* 从 preset 名册判 DIY 模式挂不挂得起来。
|
|
11934
|
+
* @param presets - `agentPresets.list()` 的回值。
|
|
11935
|
+
* @returns 名册还空着(preset 行还没登记完)给 `undefined` —— 那是"还不知道",不是故障;否则给结论。
|
|
11936
|
+
*/
|
|
11937
|
+
function diyPresetVerdictOf(presets) {
|
|
11938
|
+
if (!Array.isArray(presets) || presets.length === 0) return void 0;
|
|
11939
|
+
const mine = presets.find((row) => row?.id === DIY_PRESET_ID);
|
|
11940
|
+
if (mine === void 0) return {
|
|
11941
|
+
ok: false,
|
|
11942
|
+
rows: [],
|
|
11943
|
+
detail: `preset 名单里没有 ${DIY_PRESET_ID}`
|
|
11944
|
+
};
|
|
11945
|
+
const broken = typeof mine.broken === "string" ? mine.broken.trim() : "";
|
|
11946
|
+
if (broken === "") return {
|
|
11947
|
+
ok: true,
|
|
11948
|
+
rows: [],
|
|
11949
|
+
detail: ""
|
|
11950
|
+
};
|
|
11951
|
+
return {
|
|
11952
|
+
ok: false,
|
|
11953
|
+
rows: brokenRowsOf(broken),
|
|
11954
|
+
detail: broken
|
|
11955
|
+
};
|
|
11956
|
+
}
|
|
11957
|
+
/**
|
|
11958
|
+
* 造一个读 preset 名册的探针:名册还没登记完就每 `everyMs` 重读,到 `deadlineMs` 还没读到就放弃。
|
|
11959
|
+
* @param ctx - 宿主上下文(取 `agentPresets` 服务;那个服务不在时给一个恒回 `undefined` 的探针)。
|
|
11960
|
+
* @param options - 两个时间旋钮(测试用):多久放弃、隔多久重读。
|
|
11961
|
+
* @returns 探针:调一次读一轮,`undefined` 表示"这次没读到"。
|
|
11962
|
+
*/
|
|
11963
|
+
function createDiyPresetProbe(ctx, options = {}) {
|
|
11964
|
+
const deadlineMs = options.deadlineMs ?? 15e3;
|
|
11965
|
+
const everyMs = options.everyMs ?? 400;
|
|
11966
|
+
return async () => {
|
|
11967
|
+
const registry = ctx.get("agentPresets");
|
|
11968
|
+
if (registry === void 0 || typeof registry.list !== "function") return void 0;
|
|
11969
|
+
const until = Date.now() + deadlineMs;
|
|
11970
|
+
for (;;) {
|
|
11971
|
+
const rows = await registry.list().catch(() => void 0);
|
|
11972
|
+
if (rows === void 0) return void 0;
|
|
11973
|
+
const verdict = diyPresetVerdictOf(rows);
|
|
11974
|
+
if (verdict !== void 0) return verdict;
|
|
11975
|
+
if (Date.now() >= until) return void 0;
|
|
11976
|
+
await new Promise((resolve) => setTimeout(resolve, everyMs));
|
|
11977
|
+
}
|
|
11978
|
+
};
|
|
11979
|
+
}
|
|
11980
|
+
//#endregion
|
|
11894
11981
|
//#region src/shared/api/host/preflight.ts
|
|
11895
11982
|
function classifyFailure(text) {
|
|
11896
11983
|
const lower = text.toLowerCase();
|
|
@@ -11953,6 +12040,7 @@ function createPreflight(options) {
|
|
|
11953
12040
|
const steps = () => [
|
|
11954
12041
|
step("opencli"),
|
|
11955
12042
|
step("bridge"),
|
|
12043
|
+
...options.probeDiy === void 0 ? [] : [step("diy")],
|
|
11956
12044
|
...warmReads().length === 0 ? [] : [step("warm")]
|
|
11957
12045
|
];
|
|
11958
12046
|
let current = {
|
|
@@ -12067,6 +12155,31 @@ function createPreflight(options) {
|
|
|
12067
12155
|
});
|
|
12068
12156
|
return again;
|
|
12069
12157
|
}
|
|
12158
|
+
async function settleDiy(started) {
|
|
12159
|
+
if (started === void 0) return void 0;
|
|
12160
|
+
const verdict = await started.catch(() => void 0);
|
|
12161
|
+
if (verdict === void 0) {
|
|
12162
|
+
set({ steps: current.steps.filter((item) => item.id !== "diy") });
|
|
12163
|
+
return;
|
|
12164
|
+
}
|
|
12165
|
+
if (verdict.ok) {
|
|
12166
|
+
patchStep("diy", {
|
|
12167
|
+
status: "ok",
|
|
12168
|
+
detail: verdict.detail
|
|
12169
|
+
});
|
|
12170
|
+
return;
|
|
12171
|
+
}
|
|
12172
|
+
patchStep("diy", {
|
|
12173
|
+
status: "warn",
|
|
12174
|
+
detail: verdict.detail,
|
|
12175
|
+
command: DIY_UPGRADE_COMMAND
|
|
12176
|
+
});
|
|
12177
|
+
return {
|
|
12178
|
+
rows: verdict.rows,
|
|
12179
|
+
detail: verdict.detail,
|
|
12180
|
+
command: DIY_UPGRADE_COMMAND
|
|
12181
|
+
};
|
|
12182
|
+
}
|
|
12070
12183
|
async function doRun() {
|
|
12071
12184
|
set({
|
|
12072
12185
|
phase: "checking",
|
|
@@ -12078,6 +12191,8 @@ function createPreflight(options) {
|
|
|
12078
12191
|
status: "running",
|
|
12079
12192
|
action: `${command} --version`
|
|
12080
12193
|
});
|
|
12194
|
+
const diyStarted = options.probeDiy?.();
|
|
12195
|
+
if (diyStarted !== void 0) patchStep("diy", { status: "running" });
|
|
12081
12196
|
let version = await probeVersion();
|
|
12082
12197
|
if (!version.ok && !version.missing) {
|
|
12083
12198
|
patchStep("opencli", {
|
|
@@ -12149,10 +12264,12 @@ function createPreflight(options) {
|
|
|
12149
12264
|
...bridge.command === void 0 ? {} : { command: bridge.command }
|
|
12150
12265
|
});
|
|
12151
12266
|
const settled = await warmSites(bridge);
|
|
12267
|
+
const diyGap = await settleDiy(diyStarted);
|
|
12152
12268
|
logger?.warn?.(`[dsh-workbench] 环境体检:${current.steps.map((item) => `${item.id}=${item.status}`).join(" ")}`);
|
|
12153
12269
|
return set({
|
|
12154
12270
|
phase: "ready",
|
|
12155
|
-
...settled.ok ? {} : { bridgeIssues: settled.issues }
|
|
12271
|
+
...settled.ok ? {} : { bridgeIssues: settled.issues },
|
|
12272
|
+
...diyGap === void 0 ? {} : { diyGap }
|
|
12156
12273
|
});
|
|
12157
12274
|
}
|
|
12158
12275
|
function freshReady() {
|
|
@@ -12175,6 +12292,24 @@ function createPreflight(options) {
|
|
|
12175
12292
|
};
|
|
12176
12293
|
}
|
|
12177
12294
|
//#endregion
|
|
12295
|
+
//#region src/shared/api/host/preflight-route.ts
|
|
12296
|
+
/**
|
|
12297
|
+
* 把启动体检接上 `preflight` 那条路由。
|
|
12298
|
+
* @param input - 路由表、宿主上下文、执行缝,以及 opencli 命令 / 装它用的命令(可选)与各套件声明的预热读命令。
|
|
12299
|
+
* @returns 无(路由挂在 `input.routes.preflight` 上)。
|
|
12300
|
+
*/
|
|
12301
|
+
function registerPreflight(input) {
|
|
12302
|
+
const preflight = createPreflight({
|
|
12303
|
+
shell: input.shell,
|
|
12304
|
+
command: input.opencliCommand,
|
|
12305
|
+
...input.installCommand === void 0 ? {} : { installCommand: input.installCommand },
|
|
12306
|
+
logger: input.ctx.logger,
|
|
12307
|
+
warmReads: input.warmReads,
|
|
12308
|
+
probeDiy: createDiyPresetProbe(input.ctx)
|
|
12309
|
+
});
|
|
12310
|
+
input.routes.preflight = preflightHandler(preflight);
|
|
12311
|
+
}
|
|
12312
|
+
//#endregion
|
|
12178
12313
|
//#region src/shared/api/host/probe-cache.ts
|
|
12179
12314
|
function createTimedMemo(ttlMs) {
|
|
12180
12315
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -13980,13 +14115,14 @@ function apply(ctx) {
|
|
|
13980
14115
|
}
|
|
13981
14116
|
if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
|
|
13982
14117
|
else {
|
|
13983
|
-
|
|
14118
|
+
registerPreflight({
|
|
14119
|
+
routes,
|
|
14120
|
+
ctx,
|
|
13984
14121
|
shell,
|
|
13985
|
-
|
|
14122
|
+
opencliCommand: opencli.command,
|
|
13986
14123
|
...installEnv === void 0 ? {} : { installCommand: installEnv },
|
|
13987
|
-
logger: ctx.logger,
|
|
13988
14124
|
warmReads: () => [...mounted.values()].flatMap((item) => item.plan.warmReads())
|
|
13989
|
-
})
|
|
14125
|
+
});
|
|
13990
14126
|
routes.capability = capabilityHandler(async (unit, force) => {
|
|
13991
14127
|
if (unit === void 0) return { units: (await probeUnits({
|
|
13992
14128
|
shell,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Translate } from '@dsh-workbench/shared';
|
|
2
|
+
import type { PreflightSnapshot } from '../../../shared/api/host/preflight';
|
|
2
3
|
import { apiGet } from '@dsh-workbench/shared';
|
|
3
4
|
/**
|
|
4
5
|
* 每一拍轮询带什么参数:平时什么都不带(宿主那边跟着正在跑的那一轮,刚跑出 `ready` 的直接回上一次),
|
|
@@ -14,6 +15,13 @@ export declare function createForcePlan(): {
|
|
|
14
15
|
export interface BootGate {
|
|
15
16
|
close(): void;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 拿到这一拍之后门该怎么办:`close` 撤掉放行、`hold` 停在原地等用户动手、`wait` 接着轮询。
|
|
20
|
+
* 桥接坏了(`bridgeIssues` 非空)与 DIY 模式挂不起来(`diyGap` 非空)都是**不拦路**的,
|
|
21
|
+
* 但那块「怎么解决」得先真的画出来 —— 停在原地 + 一颗「先进入工作台」才叫不拦路,
|
|
22
|
+
* 就绪即撤等于把那块提示连同修法一起丢掉,用户进去只会发现「工作台 DIY」整个不在。
|
|
23
|
+
*/
|
|
24
|
+
export declare function gateActionOf(snapshot: PreflightSnapshot): 'close' | 'hold' | 'wait';
|
|
17
25
|
export declare function createBootGate(ctx: {
|
|
18
26
|
locale: {
|
|
19
27
|
bind(namespace: string): Translate;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DshAgentPresetRow, DshHostContext } from '@dsh-workbench/shared';
|
|
2
|
+
/**
|
|
3
|
+
* DIY 模式挂不起来时给用户的那条修法:升级 dsh,再重启一次工作台(启动器那道拒绝用的是同一条)。
|
|
4
|
+
*/
|
|
5
|
+
export declare const DIY_UPGRADE_COMMAND = "npm i -g @deepseek-ai/dsh@latest";
|
|
6
|
+
/**
|
|
7
|
+
* 一次 DIY 模式体检的结论:`ok` 为真 = 名册里有那一行且挂着;为假时 `rows` 是挂不起来的行名、`detail` 是 dsh 给的原文。
|
|
8
|
+
*/
|
|
9
|
+
export interface DiyPresetVerdict {
|
|
10
|
+
readonly ok: boolean;
|
|
11
|
+
readonly rows: readonly string[];
|
|
12
|
+
readonly detail: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 把 dsh 名册里那句 `broken` 原文拆成一项一个行名。
|
|
16
|
+
* @param detail - registry 给的原文,一行一项,形如 `tool-schedule (@deepseek-ai/dsh-tool-schedule): never started`。
|
|
17
|
+
* @returns 每行 `(` 之前那一段;空原文给空数组。
|
|
18
|
+
*/
|
|
19
|
+
export declare function brokenRowsOf(detail: string): readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* 从 preset 名册判 DIY 模式挂不挂得起来。
|
|
22
|
+
* @param presets - `agentPresets.list()` 的回值。
|
|
23
|
+
* @returns 名册还空着(preset 行还没登记完)给 `undefined` —— 那是"还不知道",不是故障;否则给结论。
|
|
24
|
+
*/
|
|
25
|
+
export declare function diyPresetVerdictOf(presets: readonly DshAgentPresetRow[]): DiyPresetVerdict | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* 造一个读 preset 名册的探针:名册还没登记完就每 `everyMs` 重读,到 `deadlineMs` 还没读到就放弃。
|
|
28
|
+
* @param ctx - 宿主上下文(取 `agentPresets` 服务;那个服务不在时给一个恒回 `undefined` 的探针)。
|
|
29
|
+
* @param options - 两个时间旋钮(测试用):多久放弃、隔多久重读。
|
|
30
|
+
* @returns 探针:调一次读一轮,`undefined` 表示"这次没读到"。
|
|
31
|
+
*/
|
|
32
|
+
export declare function createDiyPresetProbe(ctx: DshHostContext, options?: {
|
|
33
|
+
deadlineMs?: number;
|
|
34
|
+
everyMs?: number;
|
|
35
|
+
}): () => Promise<DiyPresetVerdict | undefined>;
|
|
@@ -34,6 +34,15 @@ export interface DeclarationGap {
|
|
|
34
34
|
export declare function declarationGaps(profileDir: string | null): readonly DeclarationGap[] | undefined;
|
|
35
35
|
export declare function declaredPluginCommands(profileDir: string | null): ReadonlyMap<string, ReadonlySet<string>>;
|
|
36
36
|
export declare function readInstalledModule(profileDir: string | null, pkg: string): InstalledModule;
|
|
37
|
+
/**
|
|
38
|
+
* 这份依赖是不是"从一个**打好的包**装的"(`file:/tmp/dsh-workbench-core-0.3.8.tgz`、
|
|
39
|
+
* `./x-0.3.8.tgz`)。它跟 `link:` / 目录装的**不是**一回事:那是工作树(改了立刻生效,
|
|
40
|
+
* 也不可能跟源上比出个新旧),而 tgz 是打包产物 —— 包名与版本就是源上那一套,能比、
|
|
41
|
+
* 也该让人更新。混在一起会把"从文件装的发布产物"说成"本仓正在开发的那一份",
|
|
42
|
+
* 顺带把版本比对与更新一起关掉。
|
|
43
|
+
*/
|
|
44
|
+
export declare function isPackedArtifactSpec(spec: string): boolean;
|
|
45
|
+
/** 这份依赖是不是"本仓那份"(`link:` / 目录 / 相对或绝对路径)—— 只有工作树才算。 */
|
|
37
46
|
export declare function isLocalDependencySpec(spec: string): boolean;
|
|
38
47
|
export interface ProfilePackage {
|
|
39
48
|
readonly pkg: string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DshHostContext, DshShell } from '@dsh-workbench/shared';
|
|
2
|
+
import type { RouteTable } from '@dsh-workbench/shared/host';
|
|
3
|
+
/**
|
|
4
|
+
* 把启动体检接上 `preflight` 那条路由。
|
|
5
|
+
* @param input - 路由表、宿主上下文、执行缝,以及 opencli 命令 / 装它用的命令(可选)与各套件声明的预热读命令。
|
|
6
|
+
* @returns 无(路由挂在 `input.routes.preflight` 上)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerPreflight(input: {
|
|
9
|
+
routes: RouteTable;
|
|
10
|
+
ctx: DshHostContext;
|
|
11
|
+
shell: DshShell;
|
|
12
|
+
opencliCommand: string;
|
|
13
|
+
installCommand?: string | undefined;
|
|
14
|
+
warmReads: () => readonly (readonly string[])[];
|
|
15
|
+
}): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DshShell } from '@dsh-workbench/shared';
|
|
2
|
-
|
|
2
|
+
import type { DiyPresetVerdict } from './diy-preset-health';
|
|
3
|
+
export type PreflightStepId = 'opencli' | 'bridge' | 'diy' | 'warm';
|
|
3
4
|
export type PreflightStepStatus = 'pending' | 'running' | 'ok' | 'warn' | 'failed';
|
|
4
5
|
export interface PreflightStep {
|
|
5
6
|
readonly id: PreflightStepId;
|
|
@@ -17,6 +18,14 @@ export declare function readBridgeReport(stdout: string): {
|
|
|
17
18
|
readonly issues: readonly PreflightBridgeIssue[];
|
|
18
19
|
readonly detail: string;
|
|
19
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* DIY 模式挂不起来时随快照送出去的那三件:挂不起来的行、dsh 给的原文、修法(升级 dsh)。
|
|
23
|
+
*/
|
|
24
|
+
export interface PreflightDiyGap {
|
|
25
|
+
readonly rows: readonly string[];
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
readonly command: string;
|
|
28
|
+
}
|
|
20
29
|
export interface PreflightSnapshot {
|
|
21
30
|
readonly phase: 'idle' | 'checking' | 'installing' | 'ready' | 'failed';
|
|
22
31
|
readonly steps: readonly PreflightStep[];
|
|
@@ -25,6 +34,7 @@ export interface PreflightSnapshot {
|
|
|
25
34
|
readonly failure?: PreflightFailure;
|
|
26
35
|
readonly guidance?: PreflightGuidance;
|
|
27
36
|
readonly bridgeIssues?: readonly PreflightBridgeIssue[];
|
|
37
|
+
readonly diyGap?: PreflightDiyGap;
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
30
40
|
* 环境体检:`run()` 跑一次并回快照,`snapshot()` 看眼下这一拍。`run` 的取舍是:
|
|
@@ -61,4 +71,5 @@ export declare function createPreflight(options: {
|
|
|
61
71
|
detail: string;
|
|
62
72
|
}>) | undefined;
|
|
63
73
|
warmReads?: (() => readonly (readonly string[])[]) | undefined;
|
|
74
|
+
probeDiy?: (() => Promise<DiyPresetVerdict | undefined>) | undefined;
|
|
64
75
|
}): Preflight;
|
package/docs/HANDOFF.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
| # | 东西 | 为什么 | 怎么有 |
|
|
21
21
|
| --- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
|
|
22
|
-
| 1 | `dsh` | 本体。本仓库在 `0.1
|
|
22
|
+
| 1 | `dsh` | 本体。本仓库在 `0.2.1-rc.2` 上验过,**低于它的会被拒绝** —— 见第八节 | `npm i -g @deepseek-ai/dsh@latest`(公网 npm 上有) |
|
|
23
23
|
| 2 | `node` + **`pnpm`** | dsh 的 `plugin` 子命令(以及 web 的 Plugins 页)都是**转发给 pnpm** 的;没有 pnpm 会直接报 `dsh: pnpm was not found` | 各自装 |
|
|
24
24
|
| 3 | 跑过一次 `dsh web` | 走 A 需要它给出那个 profile;走 B 时 `setup` 拿 `~/.dsh/profiles/web` 当模板。没有它会**明确报错**,不会静默建出半个 profile | `dsh web` |
|
|
25
25
|
| 4 | 一个能解析这个包的源 | 包在**公网 npm** 上,默认源即可。只有将来改发内网源时,才要 `npm config set @<scope>:registry …` + `npm login`(带 scope 的包只去 `.npmrc` 里给这个 scope 指的源找) | `npm view @dsh-workbench/core version` 出号即可 |
|
|
@@ -239,9 +239,26 @@ manifest;彩排结束、以及「零上传」的失败,都会自动回滚。
|
|
|
239
239
|
|
|
240
240
|
## 八、版本与兼容(实话)
|
|
241
241
|
|
|
242
|
-
`package.json`
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
外壳在 `package.json` 里声明了 **`engines.dsh: ">=0.2.1-rc.2"`**(本仓验过的那一代,2026-10-18)。
|
|
243
|
+
这条声明是后补的,来历见
|
|
244
|
+
[`.scratch/dsh-version-floor/spec.md`](../../../.scratch/dsh-version-floor/spec.md):真实用户装了
|
|
245
|
+
`dsh 0.2.0-rc.2`(当时 npm `latest` 还是它)+ 工作台,工作台照常起得来,**只有 DIY 模式整个不见、
|
|
246
|
+
一句错都不报** —— `preset/diy.patch.yml` 是从 dsh 0.2.1-rc.2 那份 `standard.patch.yml` 派生的,
|
|
247
|
+
里面一行 `tool-schedule` 在 0.2.0 上根本不存在,preset 挂不起来;而 dsh 的模式选择器**只列挂得起来的**
|
|
248
|
+
(healthy presets only),于是"工作台 DIY"从用户面前消失。同一代的 `uiWorkspace.startSession()`
|
|
249
|
+
也**不收** `agentPreset`(0.2.1-alpha.3 才收),所以这不是"少一把工具"的降级档,是 DIY 模式整块不可用。
|
|
250
|
+
|
|
251
|
+
因此**声明一个下限**(而不是"谁都能装"):dsh 自 `0.2.1-alpha.4` 起预检**先读 `engines.dsh`**
|
|
252
|
+
(声明了它,peer 就被整体忽略;写 `workspace:` 一律判不兼容;`engines` 不是对象或 `engines.dsh`
|
|
253
|
+
不是字符串会让清单不可读并**直接拒绝**),不满足就拒绝安装,
|
|
254
|
+
可用 `dsh plugin --profile workbench allow-version <包@版本> --dsh-version <版本> --accept-risk` 豁免。
|
|
255
|
+
旧到不认识 `engines.dsh` 的 dsh(0.2.0 及更早只读 `peerDependencies`)由**启动器那道硬门禁**兜
|
|
256
|
+
(`bin/dsh-workbench`,与"bundle 没被选中"同一档:拒绝启动 + 打印升级命令)。
|
|
257
|
+
|
|
258
|
+
⚠️ **别把下限改写成 `peerDependencies['@deepseek-ai/dsh']`**:pnpm 的 `auto-install-peers`
|
|
259
|
+
(v11 实测:连 `peerDependenciesMeta.optional` 的 peer 也照装)会把**整套 dsh**拖进 profile。
|
|
260
|
+
下限只有一处真相(`package.json` 的 `engines.dsh`),启动器读它、不另写版本号 —— selfcheck 有一条判据
|
|
261
|
+
钉着"下限 = `@deepseek-ai/dsh-client-ui-primitives` 那个 pin"。
|
|
245
262
|
|
|
246
263
|
代价要清楚:工作台依赖 dsh 的注入面(`@deepseek-ai/dsh-client-ui-slots`、
|
|
247
264
|
`@deepseek-ai/dsh-client-locale`,见 `dsh.client.inject`)与一批**按名字取的服务**
|
|
@@ -253,20 +270,8 @@ dsh 现在发的是预发布(npm 的 `next` 频道;本仓**验过的**是 `0
|
|
|
253
270
|
**devDependency 里的 `@deepseek-ai/dsh-client-ui-primitives` 跟着"验过的 dsh 版本"走**
|
|
254
271
|
([ADR-0028 §4.1](../../../docs/adr/0028-workbench-ui-uses-dsh-primitives.md)):它只出类型,
|
|
255
272
|
但类型比运行时旧一代,编译期就替不了你把关。当前两份都钉 `0.2.1-rc.2`
|
|
256
|
-
(`packages/workbench` / `packages/shared`;改完要跑一次 `pnpm install` 更新 `pnpm-lock.yaml
|
|
257
|
-
|
|
258
|
-
想改成"装的时候就拦住",在 `package.json` 里声明:
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
"peerDependencies": { "@deepseek-ai/dsh": "^0.2.1-rc.2" }
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
dsh 会用这条做**安装预检**,不满足就拒绝安装,
|
|
265
|
-
可用 `dsh plugin --profile workbench allow-version <包@版本> --dsh-version <版本> --accept-risk` 豁免。
|
|
266
|
-
**自 0.2.1-alpha.4 起预检先读包自己的 `engines.dsh`**:声明了它,上面这条 peer 就被整体忽略
|
|
267
|
-
(只有没有 `engines.dsh` 时才看 peer;`engines.dsh` 里写 `workspace:` 一律判不兼容,
|
|
268
|
-
`engines` 不是对象或 `engines.dsh` 不是字符串会让清单不可读并**直接拒绝**)。要支持一段 dsh 版本,
|
|
269
|
-
写 `engines.dsh` 比写 peer 更直接。
|
|
273
|
+
(`packages/workbench` / `packages/shared`;改完要跑一次 `pnpm install` 更新 `pnpm-lock.yaml`),
|
|
274
|
+
**升这一份时要连 `engines.dsh` 一起升**(selfcheck 那条判据把两者钉在一起)。
|
|
270
275
|
|
|
271
276
|
> `NPM_REGISTRY` 是**你们自己的**内网 npm 源地址(形如 `https://<源主机>/<路径>/`)。
|
|
272
277
|
> 本仓不写它:开源包里不该出现任何内网地址。
|
package/docs/OPERATIONS.md
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
`OPENCLI_PIN` 锁),但它连的 **daemon 是整机一个** —— 见下面「隔离边界」。
|
|
27
27
|
|
|
28
28
|
前提:`dsh` 和 `pnpm` 在 PATH 上(`dsh plugin` 是转发给 pnpm 的),
|
|
29
|
+
**`dsh` 不低于验过的那一代**(`engines.dsh`;旧到不认识它的 dsh 由启动器当场拒绝并给
|
|
30
|
+
`npm i -g @deepseek-ai/dsh@latest` —— 低于它的 dsh 上 DIY 模式整个不出现且不报错,见
|
|
31
|
+
[`.scratch/dsh-version-floor/spec.md`](../../../.scratch/dsh-version-floor/spec.md)),
|
|
29
32
|
且 `~/.dsh/profiles/web` 已存在(跑过一次 `dsh web` 即可)—— seed 拿它当**模板**
|
|
30
33
|
(bundle 清单),之后也是「配置同步」的默认来源。
|
|
31
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
|
+
"//engines": "DIY 模式(preset/diy.patch.yml)是从 **dsh 出厂那份 standard.patch.yml** 派生的,所以这份外壳只跟着它派生的那一代 dsh 走:dsh 太旧时那个 preset 挂不起来,而 dsh 的模式选择器**只列挂得起来的** ⇒ 用户面前「工作台 DIY」整个不见,一句错都不报(实测 0.2.0-rc.2:缺 dsh-tool-schedule)。这里声明下限,让 dsh 自己的兼容性预检在**装的时候**拒绝(自 0.2.1-alpha.4 起 engines.dsh 优先于 peer)。⚠️ **不写 peerDependencies['@deepseek-ai/dsh']**:pnpm 的 auto-install-peers(v11 连 optional 也装)会把整套 dsh 拖进 profile。旧到不认识 engines.dsh 的 dsh(0.2.0 及更早)由启动器那道硬门禁兜 —— 见 bin/dsh-workbench。判据:selfcheck 的「声明下限 = 验过的 dsh 版本」那条。",
|
|
66
|
+
"engines": {
|
|
67
|
+
"dsh": ">=0.2.1-rc.2"
|
|
68
|
+
},
|
|
65
69
|
"peerDependencies": {
|
|
66
70
|
"react": "*"
|
|
67
71
|
},
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
"access": "public"
|
|
88
92
|
},
|
|
89
93
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.3.
|
|
94
|
+
"@dsh-workbench/shared": "0.3.9"
|
|
91
95
|
},
|
|
92
96
|
"scripts": {
|
|
93
97
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
package/scripts/seed-profile.sh
CHANGED
|
@@ -28,6 +28,12 @@ DST_PROFILE="$DSH_HOME/profiles/$PROFILE"
|
|
|
28
28
|
PKG='@dsh-workbench/core'
|
|
29
29
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
30
30
|
GIT_TOP="$(git -C "$REPO_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
31
|
+
# 同 bin/dsh-workbench 那一处(那边写清了为什么):`--show-toplevel` 只说明**某个祖先**是仓库,
|
|
32
|
+
# 装出来的那份可能刚好落在某个仓库里(`$HOME` 是 dotfiles 仓库时最常见)—— 还要证明这份
|
|
33
|
+
# package.json 本身被那个仓库跟踪,才算是仓库副本。
|
|
34
|
+
if [ -n "$GIT_TOP" ] && ! git -C "$REPO_DIR" ls-files --error-unmatch package.json >/dev/null 2>&1; then
|
|
35
|
+
GIT_TOP=""
|
|
36
|
+
fi
|
|
31
37
|
ISOLATION="$REPO_DIR/profile/isolation.patch.yml"
|
|
32
38
|
|
|
33
39
|
# `verify.sh` 留在**仓库根**(它证明的是"dsh web 未被动过 / 组合树差异 / 数据分家",
|
|
@@ -134,6 +140,34 @@ fs.writeFileSync(process.env.WB_MANIFEST, JSON.stringify(wb, null, 2) + "\n");
|
|
|
134
140
|
console.log("→ 已继承 web 的 bundle 列表: " + merged.join(", "));
|
|
135
141
|
'
|
|
136
142
|
|
|
143
|
+
# ── 第 2.5 步:修掉旧版启动器留下的"装出来的那份被当成仓库副本" ──────────
|
|
144
|
+
#
|
|
145
|
+
# 旧判据只问 `git rev-parse --show-toplevel`("**某个祖先**是不是仓库"),于是包目录落在任何
|
|
146
|
+
# 仓库里的机器(最常见:`$HOME` 是 dotfiles 仓库,而 npm/nvm 的前缀就在 `$HOME` 下)在 setup
|
|
147
|
+
# 时会把**全局安装目录**以 `link:` 装进 profile —— 市场里工作台那一行因此说「这份是本仓正在
|
|
148
|
+
# 开发的那一份,不从源上取。」(不比对源、不提示更新),而 `npm i -g …@latest` 还会悄悄换掉
|
|
149
|
+
# profile 跑的东西。判据窄到只认那一个坏形状:**我自己是装出来的那份**(GIT_TOP 为空)
|
|
150
|
+
# **且 profile 里那条 `link:` 指着的正是我自己这份目录** —— 指别处的 link:(用户自己链的)不动。
|
|
151
|
+
if [ -z "$GIT_TOP" ] && [ -f "$DST_PROFILE/package.json" ]; then
|
|
152
|
+
WB_MANIFEST="$DST_PROFILE/package.json" WB_PKG="$PKG" WB_SELF="$REPO_DIR" \
|
|
153
|
+
WB_TARGET="$PKG@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)" \
|
|
154
|
+
node -e '
|
|
155
|
+
const fs = require("node:fs");
|
|
156
|
+
const path = require("node:path");
|
|
157
|
+
const file = process.env.WB_MANIFEST;
|
|
158
|
+
const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
159
|
+
const deps = manifest.dependencies ?? {};
|
|
160
|
+
const spec = String(deps[process.env.WB_PKG] ?? "");
|
|
161
|
+
if (!spec.startsWith("link:")) process.exit(0);
|
|
162
|
+
const real = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
|
|
163
|
+
const target = path.resolve(path.dirname(file), spec.slice("link:".length));
|
|
164
|
+
if (real(target) !== real(process.env.WB_SELF)) process.exit(0);
|
|
165
|
+
manifest.dependencies = Object.assign({}, deps, { [process.env.WB_PKG]: process.env.WB_TARGET });
|
|
166
|
+
fs.writeFileSync(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
167
|
+
console.log("→ profile 里那条 link: 指的是我这份装出来的副本(旧启动器判错的产物)—— 已改回 " + process.env.WB_TARGET);
|
|
168
|
+
'
|
|
169
|
+
fi
|
|
170
|
+
|
|
137
171
|
# ── 第 3 步:追加会话 / 存储 / 附件隔离层 ───────────────────────────────
|
|
138
172
|
if grep -q 'id: session-persistence-jsonl' "$DST_PATCH"; then
|
|
139
173
|
echo "→ 隔离层已存在,跳过"
|