@dsh-workbench/core 1.1.14-beta.1 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/bin/dsh-workbench +6 -52
  3. package/client.js +59 -16
  4. package/cordis.patch.yml +5 -6
  5. package/dist/client/client.body.js +59 -16
  6. package/dist/client/index.js +59 -16
  7. package/dist/host/index.js +1665 -623
  8. package/dist/types/app/layouts/env-gate/gate-text.d.ts +2 -3
  9. package/dist/types/app/units/manifests.d.ts +2 -0
  10. package/dist/types/kernel/host/mount-units.d.ts +2 -1
  11. package/dist/types/modules/panels/model/diy-browser-act.d.ts +3 -3
  12. package/dist/types/modules/panels/model/diy-browser-clip.d.ts +14 -0
  13. package/dist/types/modules/panels/model/diy-browser-tools.d.ts +1 -14
  14. package/dist/types/modules/panels/model/diy-browser.d.ts +6 -6
  15. package/dist/types/modules/panels/model/diy-page.d.ts +3 -2
  16. package/dist/types/modules/panels/model/diy-record-open.d.ts +2 -2
  17. package/dist/types/modules/panels/model/diy-record.d.ts +12 -11
  18. package/dist/types/modules/panels/model/market-discover.d.ts +2 -2
  19. package/dist/types/modules/panels/model/market-follow-up.d.ts +2 -1
  20. package/dist/types/modules/panels/model/market-route.d.ts +2 -3
  21. package/dist/types/modules/panels/model/market-update-bundle.d.ts +2 -3
  22. package/dist/types/modules/panels/model/market-workbench-update.d.ts +2 -2
  23. package/dist/types/modules/panels/model/market.d.ts +2 -2
  24. package/dist/types/modules/panels/model/plugin-actions.d.ts +2 -3
  25. package/dist/types/modules/panels/model/plugin-section.d.ts +3 -4
  26. package/dist/types/shared/api/host/bundle-routes.d.ts +3 -3
  27. package/dist/types/shared/api/host/diy-artifacts.d.ts +1 -2
  28. package/dist/types/shared/api/host/diy-components.d.ts +2 -2
  29. package/dist/types/shared/api/host/diy-release-staging.d.ts +6 -0
  30. package/dist/types/shared/api/host/diy.d.ts +1 -1
  31. package/dist/types/shared/api/host/git-exec.d.ts +1 -1
  32. package/dist/types/shared/api/host/git-process.d.ts +3 -0
  33. package/dist/types/shared/api/host/shipped-adapter-set.d.ts +4 -0
  34. package/dist/types/shared/api/host/workbench-seam.d.ts +3 -0
  35. package/package.json +3 -6
  36. package/scripts/seed-profile.sh +3 -3
  37. package/scripts/share-data-root.mjs +10 -13
  38. package/skills/workbench-module-diy/BUILD.md +38 -0
  39. package/skills/workbench-module-diy/SKILL.md +1 -0
  40. package/profile/sandbox.patch.yml +0 -28
package/README.md CHANGED
@@ -203,7 +203,7 @@ HMR 热换插件(不刷新页面),宿主半侧按模块路径热重载。
203
203
  | 装法、界面、适合谁 | [根 README](../../README.md) |
204
204
  | 架构、两个运行时、槽位、数据通路、门禁 | [ARCHITECTURE.md](../../ARCHITECTURE.md) |
205
205
  | 写一个模块 | [DIY 指引](skills/workbench-module-diy/SKILL.md) |
206
- | 模块的构建与十三条判据 | [module-tools](../module-tools/README.md) |
206
+ | 模块的构建与十四条判据 | [module-tools](../module-tools/README.md) |
207
207
  | 共享包(契约、哑组件、宿主工具) | [shared](../shared/README.md) |
208
208
  | 最小模块样本 | [module-hello](../module-hello/README.md) |
209
209
  | 环境、门禁、提交 | [CONTRIBUTING.md](../../CONTRIBUTING.md) |
package/bin/dsh-workbench CHANGED
@@ -403,58 +403,12 @@ fi
403
403
  # 现在改成:**工作台自己的 patch 是唯一真相**。想要 web 的哪几行,去
404
404
  # 设置 → 「配置同步」里勾(它读源 profile 的 cordis.patch.yml,按行搬运,
405
405
  # 见 内部设计记录 与 ADR-0044)。启动器只负责把工作台起起来。
406
- PATCH_ARGS=()
407
-
408
- # ── 声明工作台运行时的**可写根**(执行缝的沙箱只放行 process.cwd())──────────
409
- #
410
- # 工作台自己要跑命令:启动体检自动装运行时、模块市场装/升插件、查源上新版。
411
- # 这些命令走 dsh 的执行缝,而那条缝带**文件沙箱**;插件发起的调用**没有会话**,
412
- # 回落到部署策略,出厂值是 `workspaceRoot: process.cwd()` —— 安装目标
413
- # `$DSH_HOME/workbench/...` 不在启动目录下,被拒写(EPERM),"自动装"死在第一步,
414
- # 界面只能退化成"让用户去终端抄一条命令"。这一层把可写根指到工作台运行时,
415
- # 那一整棵树(隔离工具链 + npm 缓存 + 模块专属文件夹 + 市场 prefix)就都放行了。
416
406
  #
417
- # 为什么是 `--patch` 叠加而不是改 profile 的 cordis.patch.yml:profile 可能是旧的
418
- # (交接出去的机器尤其如此),没人会回头重跑 seed;叠加层每次启动都生效。
419
- # ⚠️ 必须排在 profile 自己那一层**之后**:工作台的可写根要赢。
420
- SANDBOX_PATCH="$REPO_DIR/profile/sandbox.patch.yml"
421
- if [ -f "$SANDBOX_PATCH" ]; then
422
- PATCH_ARGS+=(--patch "$SANDBOX_PATCH")
423
- else
424
- echo "dsh-workbench: 提醒 —— 找不到 ${SANDBOX_PATCH},这次不声明可写根(自动装会失败)。" >&2
425
- fi
426
-
427
- # DIY 工坊的目录必须**每次启动都在**:`cordis.patch.yml` 的 hmr 行把它列成一个监听根
428
- # (工坊里的模块是被 link 进 profile 的仓外目录,Node 解析到的是工坊里的真路径,
429
- # 所以不把它列进去,模块重建 dist/host.js 就不热换 —— 而且**不报错**)。
430
- # chokidar 对不存在的根收不到事件,所以这里保住它,不必等用户重跑 setup。
431
- # 只建目录:骨架与依赖是 seed-profile.sh 第 6 步的事。
407
+ # 工坊骨架 / `dsh-wb-module` 入口 / DIY 指引软链 / 隔离运行时的稳定入口:**一个写口**,
408
+ # 全在宿主自举里(`runtime.seed()` → `scripts/seed-workshop.sh`),三条安装路径每次启动都跑它
409
+ # (独立实例 / `dsh web` / Desktop 插进任意 profile 是同一份实现)。启动器在这里**只保目录**:
410
+ # hmr 行把它当监听根,而 chokidar 对不存在的根收不到事件(模块重建 dist/host.js 就不热换,
411
+ # 而且不报错)。骨架与依赖在建 profile 那一步装(`seed-profile.sh` → `seed-workshop.sh` 完整档)。
432
412
  mkdir -p "$WORKBENCH_HOME/diy/packages"
433
413
 
434
- # 工坊的**稳定入口**:每次启动都重写一遍(幂等、几乎不花时间),挂进 PATH。
435
- # 于是 agent 在工坊里(或任何目录里)都能直接叫 `dsh-wb-module`,不必记着 `pnpm exec` 前缀 ——
436
- # 指令里那句"工坊里没有全局 dsh-wb-module"是**旧**状态,现在有一个我们自己的转发脚本。
437
- bash "$REPO_DIR/scripts/seed-workshop.sh" entry || true
438
- export PATH="$WORKBENCH_HOME/bin:$PATH"
439
-
440
- # 骨架不在就**当场建**(不是只提醒):工坊看着"能用",可模块的 build / check 会把
441
- # "不在 workspace 里"说成"声明写错了"——实测就是在这一步卡住过(干净机器上 seed 第 6 步没跑)。
442
- # 提醒是对人说的,而先撞上它的是 agent:它读到的报错指着别的地方,于是开始自由发挥。
443
- if [ ! -f "$WORKBENCH_HOME/diy/package.json" ]; then
444
- bash "$REPO_DIR/scripts/seed-workshop.sh" || {
445
- echo "dsh-workbench: 提醒 —— DIY 工坊的骨架没建起来($WORKBENCH_HOME/diy/package.json)。" >&2
446
- echo " 跑一次 dsh-workbench setup;在那之前工坊里的模块 build / check 都会报错。" >&2
447
- }
448
- fi
449
-
450
- # 工坊**自己带着** DIY 的指引(软链到随包出货的那份 `skills/workbench-module-diy/`)。
451
- # 这条链**不在这里挂** —— 写口只有一处,在 `scripts/seed-workshop.sh` 的 `link_workshop_skills`
452
- # (上面那次 `entry` 已经跑过它)。为什么收成一处:启动器与宿主自举都跑那个脚本,从前两处
453
- # 各写一遍(这里一段 bash + 宿主 TS 的 `linkSkills`),目标虽一致,却是**同一件事两个实现** ——
454
- # 哪天只有一边改了源或判据就会互相改指,而软链指到哪 skill 发现都**不报错**。
455
- # 运行时的**稳定入口**不在这里建:它属于适配器(隔离包装随适配器出货),
456
- # 由宿主自举在每次启动时写到自己的隔离根里 —— 写口只有那一处。
457
- # ⚠️ 顺序有讲究:`--patch` 是 **dsh 本体**的选项,必须排在 `--port`(应用参数)**之前** ——
458
- # 参数一旦进了应用的透传区(第一个应用自己的选项开始),后面的都直接交给应用,
459
- # `--patch` 会被应用当成未知选项(实测:`--port` 在前时报 `unknown option '--patch'`)。
460
- exec dsh "$PROFILE" ${PATCH_ARGS[@]+"${PATCH_ARGS[@]}"} --port "$PORT" ${DSH_ARGS[@]+"${DSH_ARGS[@]}"}
414
+ exec dsh "$PROFILE" --port "$PORT" ${DSH_ARGS[@]+"${DSH_ARGS[@]}"}
package/client.js CHANGED
@@ -3595,9 +3595,8 @@ window.__ModuleLoader__.load({
3595
3595
  "env.title": "Preparing the workbench",
3596
3596
  "env.checking": "Getting the runtime ready…",
3597
3597
  "env.installing": "Installing background tools — the first run takes a moment…",
3598
- "env.installingToolchain": "Installing the workbench’s own npm / pnpm — the first run downloads them, so it can take a while.",
3598
+ "env.installingToolchain": "Installing the workbench’s own pnpm — the first run downloads it, so it can take a while.",
3599
3599
  "env.failed": "One step away",
3600
- "env.elapsed": "Waiting {seconds}s",
3601
3600
  "env.stepFailed.install": "install failed",
3602
3601
  "env.stepFailed.path": "installed but not found",
3603
3602
  "env.stepFailed.shell": "cannot reach the command line",
@@ -3616,14 +3615,15 @@ window.__ModuleLoader__.load({
3616
3615
  "env.fix.whyShell": "The workbench cannot reach the command line on this machine, so it cannot fetch data — you cannot install this from here; someone needs to fix the background environment.",
3617
3616
  "env.fix.whyHost": "The workbench runs a few checks on the command line and collects what they return; this time the command went out and nothing came back. The tools themselves are most likely fine — reinstalling Node / npm / pnpm will not help.",
3618
3617
  "env.fix.step": "Run this in a terminal, then hit Retry:",
3618
+ "env.fix.stepRetry": "Hit Retry to try again; if it still fails you can enter the workbench anyway and restart it once the install succeeds.",
3619
3619
  "env.fix.stepPath": "Open a new terminal window (or make sure npm’s global bin directory is on PATH), then hit Retry.",
3620
3620
  "env.fix.stepShell": "Restart the workbench (or dsh); if it keeps happening, the workbench still cannot reach the command line.",
3621
3621
  "env.fix.stepHost": "Quit the workbench completely (including the tray icon), clear the dsh-* leftovers in the system temp folder, then open it again. If it keeps happening, update dsh.",
3622
3622
  "env.fix.permission": "Permission denied: rerun with sudo, or point npm’s global directory at your home.",
3623
3623
  "env.fix.network": "Network unreachable: check your network or proxy, then retry.",
3624
3624
  "env.fix.details": "Details (for troubleshooting)",
3625
- "env.log": "Detailed output",
3626
3625
  "env.retry": "Retry",
3626
+ "env.failed.continue": "Enter workbench anyway",
3627
3627
  "env.wake": "Open browser",
3628
3628
  "env.wake.opening": "Opening the browser…",
3629
3629
  "env.wake.browserOpen": "The browser is already running but the extension is not connected — check that it is enabled.",
@@ -4365,9 +4365,8 @@ window.__ModuleLoader__.load({
4365
4365
  "env.title": "准备工作台",
4366
4366
  "env.checking": "正在准备运行环境…",
4367
4367
  "env.installing": "正在安装后台工具,第一次会慢一点…",
4368
- "env.installingToolchain": "正在装工作台自己那份 npm / pnpm —— 第一次要下载,慢一点正常。",
4368
+ "env.installingToolchain": "正在装工作台自己那份 pnpm —— 第一次要下载,慢一点正常。",
4369
4369
  "env.failed": "还差一步就能用了",
4370
- "env.elapsed": "已等待 {seconds} 秒",
4371
4370
  "env.stepFailed.install": "装不上",
4372
4371
  "env.stepFailed.path": "装了但还找不到",
4373
4372
  "env.stepFailed.shell": "连不上命令行",
@@ -4386,14 +4385,15 @@ window.__ModuleLoader__.load({
4386
4385
  "env.fix.whyShell": "工作台拿不到这台机器的命令行,所以取不了数据 —— 这一步你装不了,需要有人处理后台环境。",
4387
4386
  "env.fix.whyHost": "工作台要靠命令行跑几条检查,再把结果收回来;这次命令发出去了,结果一直没回来。工具本身多半是好的 —— 重装 Node / npm / pnpm 不会有帮助。",
4388
4387
  "env.fix.step": "在终端里运行下面这条命令,装好之后点「重试」:",
4388
+ "env.fix.stepRetry": "点「重试」再装一次;还装不上也可以先进入工作台 —— 装好之后重启工作台就会用上。",
4389
4389
  "env.fix.stepPath": "新开一个终端窗口(或确认 npm 的全局 bin 目录在 PATH 里),然后点「重试」。",
4390
4390
  "env.fix.stepShell": "重启一次工作台(或 dsh);如果反复出现,说明工作台还是拿不到命令行。",
4391
4391
  "env.fix.stepHost": "完全退出工作台(托盘图标也要退),清掉系统临时目录里 dsh- 开头的残留,再重新打开;反复出现就升级 dsh。",
4392
4392
  "env.fix.permission": "权限不够:在命令前加 sudo 重跑,或先把 npm 的全局目录配到你的用户目录。",
4393
4393
  "env.fix.network": "网络不通:检查网络或代理设置后重试。",
4394
4394
  "env.fix.details": "详细输出(排障用)",
4395
- "env.log": "详细输出",
4396
4395
  "env.retry": "重试",
4396
+ "env.failed.continue": "先进入工作台",
4397
4397
  "env.wake": "打开浏览器",
4398
4398
  "env.wake.opening": "正在打开浏览器…",
4399
4399
  "env.wake.browserOpen": "浏览器已经开着,但扩展没连上 —— 检查扩展是否启用。",
@@ -13018,10 +13018,16 @@ window.__ModuleLoader__.load({
13018
13018
  }
13019
13019
  //#endregion
13020
13020
  //#region src/app/units/manifests.ts
13021
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
13022
+ var DEFAULT_MAX_RETRIES = 30;
13021
13023
  function createUnitManifestLoader(options = {}) {
13022
13024
  let inFlight = false;
13023
13025
  let queued = false;
13024
13026
  let watching = false;
13027
+ let retries = 0;
13028
+ let timer = null;
13029
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
13030
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
13025
13031
  const subscribeFacts = options.subscribeFacts ?? subscribeFactRevision;
13026
13032
  const load = async (registry) => {
13027
13033
  if (inFlight) {
@@ -13029,11 +13035,23 @@ window.__ModuleLoader__.load({
13029
13035
  return;
13030
13036
  }
13031
13037
  inFlight = true;
13038
+ let settled = false;
13032
13039
  try {
13033
13040
  const response = await apiGet(UNIT_MANIFESTS_ROUTE).catch(() => null);
13034
- if (response?.ok === true && Array.isArray(response.manifests)) registry.setManifests(response.manifests, Array.isArray(response.installable) ? response.installable : void 0);
13041
+ if (response?.ok === true && Array.isArray(response.manifests)) {
13042
+ registry.setManifests(response.manifests, Array.isArray(response.installable) ? response.installable : void 0);
13043
+ settled = !registry.refusals().some((item) => item.kind === "no-manifest");
13044
+ }
13035
13045
  } finally {
13036
13046
  inFlight = false;
13047
+ if (settled) retries = 0;
13048
+ else if (retries < maxRetries && timer === null) {
13049
+ retries += 1;
13050
+ timer = setTimeout(() => {
13051
+ timer = null;
13052
+ load(registry);
13053
+ }, retryDelayMs);
13054
+ }
13037
13055
  if (queued) {
13038
13056
  queued = false;
13039
13057
  load(registry);
@@ -13043,7 +13061,10 @@ window.__ModuleLoader__.load({
13043
13061
  return (registry) => {
13044
13062
  if (!watching) {
13045
13063
  watching = true;
13046
- subscribeFacts(factOfRoute(UNIT_MANIFESTS_ROUTE), () => void load(registry));
13064
+ subscribeFacts(factOfRoute(UNIT_MANIFESTS_ROUTE), () => {
13065
+ retries = 0;
13066
+ load(registry);
13067
+ });
13047
13068
  }
13048
13069
  load(registry);
13049
13070
  };
@@ -13088,7 +13109,7 @@ window.__ModuleLoader__.load({
13088
13109
  if (guidance === "host") return t("env.stepFailed.host");
13089
13110
  return t("env.stepFailed.install");
13090
13111
  }
13091
- function guidanceTextOf(guidance, t) {
13112
+ function guidanceTextOf(guidance, t, hasCommand = true) {
13092
13113
  if (guidance === "path") return {
13093
13114
  why: t("env.fix.whyPath"),
13094
13115
  step: t("env.fix.stepPath")
@@ -13103,7 +13124,7 @@ window.__ModuleLoader__.load({
13103
13124
  };
13104
13125
  return {
13105
13126
  why: t("env.fix.why"),
13106
- step: t("env.fix.step")
13127
+ step: hasCommand ? t("env.fix.step") : t("env.fix.stepRetry")
13107
13128
  };
13108
13129
  }
13109
13130
  function stepDetailText(step, snapshot, t) {
@@ -13128,6 +13149,19 @@ window.__ModuleLoader__.load({
13128
13149
  };
13129
13150
  return { text: t("env.bridge.unknown") };
13130
13151
  }
13152
+ function bridgeAdviceLines(issues, t, fixes) {
13153
+ const listed = issues.length === 0 ? ["unknown"] : issues;
13154
+ const seen = /* @__PURE__ */ new Set();
13155
+ const out = [];
13156
+ for (const issue of listed) {
13157
+ const advice = bridgeAdvice(issue, t, fixes);
13158
+ const key = `${advice.text}|${advice.link?.href ?? ""}`;
13159
+ if (seen.has(key)) continue;
13160
+ seen.add(key);
13161
+ out.push(advice);
13162
+ }
13163
+ return out;
13164
+ }
13131
13165
  //#endregion
13132
13166
  //#region src/app/layouts/env-gate/gate-deadline.ts
13133
13167
  function createGateDeadline(giveUpMs, expire) {
@@ -13288,8 +13322,10 @@ window.__ModuleLoader__.load({
13288
13322
  const buttons = el("div", BootGate_module_default.buttons);
13289
13323
  const retry = el("button", BootGate_module_default.button, t("env.retry"));
13290
13324
  retry.setAttribute("type", "button");
13325
+ const failedSkip = el("button", BootGate_module_default.button, t("env.failed.continue"));
13326
+ failedSkip.setAttribute("type", "button");
13291
13327
  const fixCommand = commandRow(t);
13292
- buttons.append(retry);
13328
+ buttons.append(retry, failedSkip);
13293
13329
  actions.append(fixTitle, fixWhy, fixStep, fixCommand.row, fixAdvice, buttons);
13294
13330
  const bridge = el("div", BootGate_module_default.bridge);
13295
13331
  bridge.hidden = true;
@@ -13333,6 +13369,8 @@ window.__ModuleLoader__.load({
13333
13369
  let cancelled = false;
13334
13370
  const forcePlan = createForcePlan();
13335
13371
  let pinned = null;
13372
+ let lastSnapshot = null;
13373
+ let runtimeProfile;
13336
13374
  function close() {
13337
13375
  if (closed) return;
13338
13376
  closed = true;
@@ -13389,6 +13427,7 @@ window.__ModuleLoader__.load({
13389
13427
  return row;
13390
13428
  }
13391
13429
  function render(snapshot) {
13430
+ lastSnapshot = snapshot;
13392
13431
  wakeNote.hidden = true;
13393
13432
  if (bootHintShown && (snapshot.phase !== "idle" || snapshot.steps.length > 0)) {
13394
13433
  bootHintShown = false;
@@ -13429,10 +13468,10 @@ window.__ModuleLoader__.load({
13429
13468
  bridgeRetry.removeAttribute("disabled");
13430
13469
  const commandText = snapshot.steps.find((step) => step.status === "failed" && step.command !== void 0)?.command;
13431
13470
  const guidance = snapshot.guidance ?? "install";
13432
- const advice = guidanceTextOf(guidance, t);
13471
+ const showCommand = guidance === "install" && commandText !== void 0;
13472
+ const advice = guidanceTextOf(guidance, t, showCommand);
13433
13473
  fixWhy.textContent = advice.why;
13434
13474
  fixStep.textContent = advice.step;
13435
- const showCommand = guidance === "install" && commandText !== void 0;
13436
13475
  fixCommand.set(showCommand ? commandText : void 0);
13437
13476
  fixAdvice.textContent = !showCommand ? "" : snapshot.failure === "permission" ? t("env.fix.permission") : snapshot.failure === "network" ? t("env.fix.network") : "";
13438
13477
  const bridgeStep = snapshot.steps.find((step) => step.id === "bridge");
@@ -13440,9 +13479,7 @@ window.__ModuleLoader__.load({
13440
13479
  const bridgeBroken = bridgeIssues.length > 0 || bridgeStep !== void 0 && (bridgeStep.status === "warn" || bridgeStep.status === "failed");
13441
13480
  bridge.hidden = !bridgeBroken;
13442
13481
  if (bridgeBroken) {
13443
- const listed = bridgeIssues.length === 0 ? ["unknown"] : bridgeIssues;
13444
- bridgeLines.replaceChildren(...listed.map((issue) => {
13445
- const advice = bridgeAdvice(issue, t, runtimeProfileValue()?.bridge);
13482
+ bridgeLines.replaceChildren(...bridgeAdviceLines(bridgeIssues, t, runtimeProfile?.bridge ?? runtimeProfileValue()?.bridge).map((advice) => {
13446
13483
  const item = el("li", BootGate_module_default.bridgeLine, advice.text);
13447
13484
  if (advice.link !== void 0) {
13448
13485
  const link = document.createElement("a");
@@ -13524,6 +13561,7 @@ window.__ModuleLoader__.load({
13524
13561
  wakeNote.hidden = false;
13525
13562
  }
13526
13563
  retry.addEventListener("click", retryNow);
13564
+ failedSkip.addEventListener("click", close);
13527
13565
  bridgeWake.addEventListener("click", () => void wakeBrowserNow());
13528
13566
  bridgeRetry.addEventListener("click", retryNow);
13529
13567
  bridgeSkip.addEventListener("click", close);
@@ -13541,6 +13579,11 @@ window.__ModuleLoader__.load({
13541
13579
  } });
13542
13580
  if (strip !== null) overlay.append(strip);
13543
13581
  }
13582
+ readRuntimeCapabilities().then((answer) => {
13583
+ runtimeProfile = answer.ok ? answer.runtimeProfile ?? void 0 : void 0;
13584
+ if (pinned !== null) render(pinned);
13585
+ else if (lastSnapshot !== null) render(lastSnapshot);
13586
+ }).catch(() => void 0);
13544
13587
  poll();
13545
13588
  return { close };
13546
13589
  }
package/cordis.patch.yml CHANGED
@@ -134,12 +134,11 @@
134
134
  # `file-upload` 注销 resolver 赛跑 —— 启用当场把 session-controller 打成 failed,
135
135
  # 之后每次新建会话都是 `gateway/service-unavailable`(连 toggle 都救不回来,只能重启)。
136
136
  #
137
- # 证据与 A/B:ADR-0037 的 2026-10-22 补记。自带 profile 的可写根仍有两处:启动器每次启动
138
- # 叠的 `--patch profile/sandbox.patch.yml`,以及 `profile/isolation.patch.yml` 里同值的那一行
139
- # (seed-profile.sh 追加进 profile,给不带启动器的 `dsh workbench`)。装进别人的 profile 时,
140
- # 请把那一行写进那个 profile 的 `cordis.patch.yml` 再**重启**(在线写同一行也会撞同一个竞态)。
141
- # 少了它只会让"无会话的插件命令"被沙箱挡,不会再动会话接口;宿主启动时会就此打一条 warn
142
- # (见 runtime-seed 的 coversWritableRoot)。
137
+ # 现在可写根由**产品代码自己声明**(`src/shared/api/host/workbench-shell.ts` 的
138
+ # `provideWorkbenchShell`:`mode: 'workspace-write'`、`workspaceRoot: dshHome()`),
139
+ # 既不在 bundle、也不在 profile、也不需要启动器的 `--patch`:三条安装路径
140
+ # (独立实例 / `dsh web` / Desktop 插件页)行为一致。
141
+ # 证据与 A/B:ADR-0037 的 2026-10-22 补记;宿主启动时会打一行 info 说明实际可写根。
143
142
 
144
143
  - insert:
145
144
  - id: workbench
@@ -3588,9 +3588,8 @@ var en = {
3588
3588
  "env.title": "Preparing the workbench",
3589
3589
  "env.checking": "Getting the runtime ready…",
3590
3590
  "env.installing": "Installing background tools — the first run takes a moment…",
3591
- "env.installingToolchain": "Installing the workbench’s own npm / pnpm — the first run downloads them, so it can take a while.",
3591
+ "env.installingToolchain": "Installing the workbench’s own pnpm — the first run downloads it, so it can take a while.",
3592
3592
  "env.failed": "One step away",
3593
- "env.elapsed": "Waiting {seconds}s",
3594
3593
  "env.stepFailed.install": "install failed",
3595
3594
  "env.stepFailed.path": "installed but not found",
3596
3595
  "env.stepFailed.shell": "cannot reach the command line",
@@ -3609,14 +3608,15 @@ var en = {
3609
3608
  "env.fix.whyShell": "The workbench cannot reach the command line on this machine, so it cannot fetch data — you cannot install this from here; someone needs to fix the background environment.",
3610
3609
  "env.fix.whyHost": "The workbench runs a few checks on the command line and collects what they return; this time the command went out and nothing came back. The tools themselves are most likely fine — reinstalling Node / npm / pnpm will not help.",
3611
3610
  "env.fix.step": "Run this in a terminal, then hit Retry:",
3611
+ "env.fix.stepRetry": "Hit Retry to try again; if it still fails you can enter the workbench anyway and restart it once the install succeeds.",
3612
3612
  "env.fix.stepPath": "Open a new terminal window (or make sure npm’s global bin directory is on PATH), then hit Retry.",
3613
3613
  "env.fix.stepShell": "Restart the workbench (or dsh); if it keeps happening, the workbench still cannot reach the command line.",
3614
3614
  "env.fix.stepHost": "Quit the workbench completely (including the tray icon), clear the dsh-* leftovers in the system temp folder, then open it again. If it keeps happening, update dsh.",
3615
3615
  "env.fix.permission": "Permission denied: rerun with sudo, or point npm’s global directory at your home.",
3616
3616
  "env.fix.network": "Network unreachable: check your network or proxy, then retry.",
3617
3617
  "env.fix.details": "Details (for troubleshooting)",
3618
- "env.log": "Detailed output",
3619
3618
  "env.retry": "Retry",
3619
+ "env.failed.continue": "Enter workbench anyway",
3620
3620
  "env.wake": "Open browser",
3621
3621
  "env.wake.opening": "Opening the browser…",
3622
3622
  "env.wake.browserOpen": "The browser is already running but the extension is not connected — check that it is enabled.",
@@ -4358,9 +4358,8 @@ var dictionaries = {
4358
4358
  "env.title": "准备工作台",
4359
4359
  "env.checking": "正在准备运行环境…",
4360
4360
  "env.installing": "正在安装后台工具,第一次会慢一点…",
4361
- "env.installingToolchain": "正在装工作台自己那份 npm / pnpm —— 第一次要下载,慢一点正常。",
4361
+ "env.installingToolchain": "正在装工作台自己那份 pnpm —— 第一次要下载,慢一点正常。",
4362
4362
  "env.failed": "还差一步就能用了",
4363
- "env.elapsed": "已等待 {seconds} 秒",
4364
4363
  "env.stepFailed.install": "装不上",
4365
4364
  "env.stepFailed.path": "装了但还找不到",
4366
4365
  "env.stepFailed.shell": "连不上命令行",
@@ -4379,14 +4378,15 @@ var dictionaries = {
4379
4378
  "env.fix.whyShell": "工作台拿不到这台机器的命令行,所以取不了数据 —— 这一步你装不了,需要有人处理后台环境。",
4380
4379
  "env.fix.whyHost": "工作台要靠命令行跑几条检查,再把结果收回来;这次命令发出去了,结果一直没回来。工具本身多半是好的 —— 重装 Node / npm / pnpm 不会有帮助。",
4381
4380
  "env.fix.step": "在终端里运行下面这条命令,装好之后点「重试」:",
4381
+ "env.fix.stepRetry": "点「重试」再装一次;还装不上也可以先进入工作台 —— 装好之后重启工作台就会用上。",
4382
4382
  "env.fix.stepPath": "新开一个终端窗口(或确认 npm 的全局 bin 目录在 PATH 里),然后点「重试」。",
4383
4383
  "env.fix.stepShell": "重启一次工作台(或 dsh);如果反复出现,说明工作台还是拿不到命令行。",
4384
4384
  "env.fix.stepHost": "完全退出工作台(托盘图标也要退),清掉系统临时目录里 dsh- 开头的残留,再重新打开;反复出现就升级 dsh。",
4385
4385
  "env.fix.permission": "权限不够:在命令前加 sudo 重跑,或先把 npm 的全局目录配到你的用户目录。",
4386
4386
  "env.fix.network": "网络不通:检查网络或代理设置后重试。",
4387
4387
  "env.fix.details": "详细输出(排障用)",
4388
- "env.log": "详细输出",
4389
4388
  "env.retry": "重试",
4389
+ "env.failed.continue": "先进入工作台",
4390
4390
  "env.wake": "打开浏览器",
4391
4391
  "env.wake.opening": "正在打开浏览器…",
4392
4392
  "env.wake.browserOpen": "浏览器已经开着,但扩展没连上 —— 检查扩展是否启用。",
@@ -13011,10 +13011,16 @@ function createClientUnitRegistry(options = {}) {
13011
13011
  }
13012
13012
  //#endregion
13013
13013
  //#region src/app/units/manifests.ts
13014
+ var DEFAULT_RETRY_DELAY_MS = 1e3;
13015
+ var DEFAULT_MAX_RETRIES = 30;
13014
13016
  function createUnitManifestLoader(options = {}) {
13015
13017
  let inFlight = false;
13016
13018
  let queued = false;
13017
13019
  let watching = false;
13020
+ let retries = 0;
13021
+ let timer = null;
13022
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
13023
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
13018
13024
  const subscribeFacts = options.subscribeFacts ?? subscribeFactRevision;
13019
13025
  const load = async (registry) => {
13020
13026
  if (inFlight) {
@@ -13022,11 +13028,23 @@ function createUnitManifestLoader(options = {}) {
13022
13028
  return;
13023
13029
  }
13024
13030
  inFlight = true;
13031
+ let settled = false;
13025
13032
  try {
13026
13033
  const response = await apiGet(UNIT_MANIFESTS_ROUTE).catch(() => null);
13027
- if (response?.ok === true && Array.isArray(response.manifests)) registry.setManifests(response.manifests, Array.isArray(response.installable) ? response.installable : void 0);
13034
+ if (response?.ok === true && Array.isArray(response.manifests)) {
13035
+ registry.setManifests(response.manifests, Array.isArray(response.installable) ? response.installable : void 0);
13036
+ settled = !registry.refusals().some((item) => item.kind === "no-manifest");
13037
+ }
13028
13038
  } finally {
13029
13039
  inFlight = false;
13040
+ if (settled) retries = 0;
13041
+ else if (retries < maxRetries && timer === null) {
13042
+ retries += 1;
13043
+ timer = setTimeout(() => {
13044
+ timer = null;
13045
+ load(registry);
13046
+ }, retryDelayMs);
13047
+ }
13030
13048
  if (queued) {
13031
13049
  queued = false;
13032
13050
  load(registry);
@@ -13036,7 +13054,10 @@ function createUnitManifestLoader(options = {}) {
13036
13054
  return (registry) => {
13037
13055
  if (!watching) {
13038
13056
  watching = true;
13039
- subscribeFacts(factOfRoute(UNIT_MANIFESTS_ROUTE), () => void load(registry));
13057
+ subscribeFacts(factOfRoute(UNIT_MANIFESTS_ROUTE), () => {
13058
+ retries = 0;
13059
+ load(registry);
13060
+ });
13040
13061
  }
13041
13062
  load(registry);
13042
13063
  };
@@ -13081,7 +13102,7 @@ function failedStepText(guidance, t) {
13081
13102
  if (guidance === "host") return t("env.stepFailed.host");
13082
13103
  return t("env.stepFailed.install");
13083
13104
  }
13084
- function guidanceTextOf(guidance, t) {
13105
+ function guidanceTextOf(guidance, t, hasCommand = true) {
13085
13106
  if (guidance === "path") return {
13086
13107
  why: t("env.fix.whyPath"),
13087
13108
  step: t("env.fix.stepPath")
@@ -13096,7 +13117,7 @@ function guidanceTextOf(guidance, t) {
13096
13117
  };
13097
13118
  return {
13098
13119
  why: t("env.fix.why"),
13099
- step: t("env.fix.step")
13120
+ step: hasCommand ? t("env.fix.step") : t("env.fix.stepRetry")
13100
13121
  };
13101
13122
  }
13102
13123
  function stepDetailText(step, snapshot, t) {
@@ -13121,6 +13142,19 @@ function bridgeAdvice(issue, t, fixes) {
13121
13142
  };
13122
13143
  return { text: t("env.bridge.unknown") };
13123
13144
  }
13145
+ function bridgeAdviceLines(issues, t, fixes) {
13146
+ const listed = issues.length === 0 ? ["unknown"] : issues;
13147
+ const seen = /* @__PURE__ */ new Set();
13148
+ const out = [];
13149
+ for (const issue of listed) {
13150
+ const advice = bridgeAdvice(issue, t, fixes);
13151
+ const key = `${advice.text}|${advice.link?.href ?? ""}`;
13152
+ if (seen.has(key)) continue;
13153
+ seen.add(key);
13154
+ out.push(advice);
13155
+ }
13156
+ return out;
13157
+ }
13124
13158
  //#endregion
13125
13159
  //#region src/app/layouts/env-gate/gate-deadline.ts
13126
13160
  function createGateDeadline(giveUpMs, expire) {
@@ -13281,8 +13315,10 @@ function createBootGate(ctx, options = {}) {
13281
13315
  const buttons = el("div", BootGate_module_default.buttons);
13282
13316
  const retry = el("button", BootGate_module_default.button, t("env.retry"));
13283
13317
  retry.setAttribute("type", "button");
13318
+ const failedSkip = el("button", BootGate_module_default.button, t("env.failed.continue"));
13319
+ failedSkip.setAttribute("type", "button");
13284
13320
  const fixCommand = commandRow(t);
13285
- buttons.append(retry);
13321
+ buttons.append(retry, failedSkip);
13286
13322
  actions.append(fixTitle, fixWhy, fixStep, fixCommand.row, fixAdvice, buttons);
13287
13323
  const bridge = el("div", BootGate_module_default.bridge);
13288
13324
  bridge.hidden = true;
@@ -13326,6 +13362,8 @@ function createBootGate(ctx, options = {}) {
13326
13362
  let cancelled = false;
13327
13363
  const forcePlan = createForcePlan();
13328
13364
  let pinned = null;
13365
+ let lastSnapshot = null;
13366
+ let runtimeProfile;
13329
13367
  function close() {
13330
13368
  if (closed) return;
13331
13369
  closed = true;
@@ -13382,6 +13420,7 @@ function createBootGate(ctx, options = {}) {
13382
13420
  return row;
13383
13421
  }
13384
13422
  function render(snapshot) {
13423
+ lastSnapshot = snapshot;
13385
13424
  wakeNote.hidden = true;
13386
13425
  if (bootHintShown && (snapshot.phase !== "idle" || snapshot.steps.length > 0)) {
13387
13426
  bootHintShown = false;
@@ -13422,10 +13461,10 @@ function createBootGate(ctx, options = {}) {
13422
13461
  bridgeRetry.removeAttribute("disabled");
13423
13462
  const commandText = snapshot.steps.find((step) => step.status === "failed" && step.command !== void 0)?.command;
13424
13463
  const guidance = snapshot.guidance ?? "install";
13425
- const advice = guidanceTextOf(guidance, t);
13464
+ const showCommand = guidance === "install" && commandText !== void 0;
13465
+ const advice = guidanceTextOf(guidance, t, showCommand);
13426
13466
  fixWhy.textContent = advice.why;
13427
13467
  fixStep.textContent = advice.step;
13428
- const showCommand = guidance === "install" && commandText !== void 0;
13429
13468
  fixCommand.set(showCommand ? commandText : void 0);
13430
13469
  fixAdvice.textContent = !showCommand ? "" : snapshot.failure === "permission" ? t("env.fix.permission") : snapshot.failure === "network" ? t("env.fix.network") : "";
13431
13470
  const bridgeStep = snapshot.steps.find((step) => step.id === "bridge");
@@ -13433,9 +13472,7 @@ function createBootGate(ctx, options = {}) {
13433
13472
  const bridgeBroken = bridgeIssues.length > 0 || bridgeStep !== void 0 && (bridgeStep.status === "warn" || bridgeStep.status === "failed");
13434
13473
  bridge.hidden = !bridgeBroken;
13435
13474
  if (bridgeBroken) {
13436
- const listed = bridgeIssues.length === 0 ? ["unknown"] : bridgeIssues;
13437
- bridgeLines.replaceChildren(...listed.map((issue) => {
13438
- const advice = bridgeAdvice(issue, t, runtimeProfileValue()?.bridge);
13475
+ bridgeLines.replaceChildren(...bridgeAdviceLines(bridgeIssues, t, runtimeProfile?.bridge ?? runtimeProfileValue()?.bridge).map((advice) => {
13439
13476
  const item = el("li", BootGate_module_default.bridgeLine, advice.text);
13440
13477
  if (advice.link !== void 0) {
13441
13478
  const link = document.createElement("a");
@@ -13517,6 +13554,7 @@ function createBootGate(ctx, options = {}) {
13517
13554
  wakeNote.hidden = false;
13518
13555
  }
13519
13556
  retry.addEventListener("click", retryNow);
13557
+ failedSkip.addEventListener("click", close);
13520
13558
  bridgeWake.addEventListener("click", () => void wakeBrowserNow());
13521
13559
  bridgeRetry.addEventListener("click", retryNow);
13522
13560
  bridgeSkip.addEventListener("click", close);
@@ -13534,6 +13572,11 @@ function createBootGate(ctx, options = {}) {
13534
13572
  } });
13535
13573
  if (strip !== null) overlay.append(strip);
13536
13574
  }
13575
+ readRuntimeCapabilities().then((answer) => {
13576
+ runtimeProfile = answer.ok ? answer.runtimeProfile ?? void 0 : void 0;
13577
+ if (pinned !== null) render(pinned);
13578
+ else if (lastSnapshot !== null) render(lastSnapshot);
13579
+ }).catch(() => void 0);
13537
13580
  poll();
13538
13581
  return { close };
13539
13582
  }