cicy-desktop 2.1.307 → 2.1.309

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.
@@ -132,6 +132,21 @@ jobs:
132
132
  if ($kfile) { & $oss cp "$($kfile.FullName)" "$kernelKey" -f --acl public-read } else { Write-Warning "kernel not found in msi extract" }
133
133
  } else { Write-Host "WSL2 kernel already on OSS, skip" }
134
134
 
135
+ # 0b) 新版 WSL 的 MSI(一次性,已存在就跳过):老 Win10 自带旧版 WSL 反复死锁,桌面端改为装新版。
136
+ $wslKey = "$base/wsl/wsl.x64.msi"
137
+ & $oss stat "$wslKey" 2>$null | Out-Null
138
+ if ($LASTEXITCODE -ne 0) {
139
+ $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/microsoft/WSL/releases/latest" -Headers @{ "User-Agent" = "cicy-desktop-ci" }
140
+ $asset = $rel.assets | Where-Object { $_.name -match 'x64\.msi$' } | Select-Object -First 1
141
+ if ($asset) {
142
+ Write-Host "Publishing modern WSL $($rel.tag_name) -> $wslKey"
143
+ Invoke-WebRequest -Uri $asset.browser_download_url -OutFile wsl.x64.msi
144
+ & $oss cp "wsl.x64.msi" "$wslKey" -f --acl public-read
145
+ Set-Content -Path wsl-version.txt -Value $rel.tag_name
146
+ & $oss cp "wsl-version.txt" "$base/wsl/version.txt" -f --acl public-read
147
+ } else { Write-Warning "no x64 msi asset in microsoft/WSL latest" }
148
+ } else { Write-Host "modern WSL msi already on OSS, skip" }
149
+
135
150
  # 1) 永久版本化副本(unique key — 不会被后续发版覆盖)
136
151
  $verKey = "$base/cicy-desktop-$version.exe"
137
152
  Write-Host "Uploading versioned -> $verKey"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.307",
3
+ "version": "2.1.309",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -159,6 +159,7 @@ function register({ sidecarLogPath } = {}) {
159
159
  } else if (result) {
160
160
  _lastBootstrapError = { reason: result.reason || result.error || "bootstrap_failed", message: result.message || "", ts: Date.now() };
161
161
  } else return;
162
+ _softRetryAt = Date.now() + 2 * 60 * 1000;
162
163
  if (_lastBootstrapError.reason === "wsl_reboot_required") scheduleReboot(90);
163
164
  if (HARD_BOOTSTRAP_REASONS.has(_lastBootstrapError.reason)) {
164
165
  _autoBootstrapPaused = _lastBootstrapError.reason;
@@ -171,6 +172,7 @@ function register({ sidecarLogPath } = {}) {
171
172
  let _rebootScheduled = false;
172
173
  let _relayMisses = 0, _relayResetDone = false; // WSL localhost 转发自愈(见 daemon 循环)
173
174
  let _unknownStreak = 0, _wslRepairDone = false; // WSL 卡死自愈(见 daemon 循环)
175
+ let _softRetryAt = 0; // 非硬失败(如 docker_install_failed)也退避 2 分钟,别每 35 秒重跑一遍
174
176
  // 防重启循环:WSL 始终起不来时不能每次开机都重启。计数存 db/wsl-reboots.json,最多自动重启 2 次
175
177
  // (成功后 :8008 起来时清零)。
176
178
  const REBOOT_COUNT_FILE = path.join(os.homedir(), "cicy-ai", "db", "wsl-reboots.json");
@@ -265,6 +267,8 @@ function register({ sidecarLogPath } = {}) {
265
267
  } else if (s.running) { _relayMisses = 0; }
266
268
  if (!s.running && !s.unknown && _autoBootstrapPaused && Date.now() < _autoBootstrapRetryAt) {
267
269
  // 硬失败后退避(见 recordBootstrapResult);卡片显示 lastError,到点自动再试,用户点「重试」也行。
270
+ } else if (!s.running && !s.unknown && Date.now() < _softRetryAt) {
271
+ // 上一轮刚失败,2 分钟内不重跑
268
272
  } else if (!s.running && !s.unknown) {
269
273
  if (_autoBootstrapPaused) log.info(`[docker-daemon] backoff elapsed after ${_autoBootstrapPaused} → auto-retrying bootstrap`);
270
274
  log.info(`[docker-daemon] :8008 down (${s.installed ? "installed" : "not installed"}) → auto-bootstrapping WSL cicy-code`);
@@ -612,6 +612,7 @@ function wslExe() {
612
612
  if (process.platform !== "win32") return "wsl";
613
613
  const sysroot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
614
614
  const cands = [
615
+ path.join(process.env.ProgramFiles || "C:\\Program Files", "WSL", "wsl.exe"), // 新版 WSL(MSI),优先
615
616
  path.join(sysroot, "System32", "wsl.exe"),
616
617
  path.join(process.env.LOCALAPPDATA || "", "Microsoft", "WindowsApps", "wsl.exe"),
617
618
  ];
@@ -670,6 +671,18 @@ function featureEnabled(feature) {
670
671
  });
671
672
  }
672
673
 
674
+ // 新版 WSL(微软独立发布的 MSI,Store 版同源):不走旧的 LxssManager、内核内置、`--status` 等命令
675
+ // 齐全、localhost 转发稳定。老 Win10 自带的旧版 WSL 反复死锁/半死/缺内核,全部由它一次解决。
676
+ // MSI 由发布工作流从 GitHub 镜像到 OSS(国内可下)。
677
+ const WSL_MODERN_MSI_URL = process.env.CICY_WSL_MSI_URL || (require("./mirrors").OSS_RELEASES_BASE + "/wsl/wsl.x64.msi");
678
+ function modernWslInstalled() {
679
+ if (process.platform !== "win32") return true;
680
+ try { return fs.existsSync(path.join(process.env.ProgramFiles || "C:\\Program Files", "WSL", "wsl.exe")); } catch { return false; }
681
+ }
682
+ // 这台机器的旧版 WSL 出过死锁/被自动重启过(db/wsl-reboots.json 有计数)→ 下次提权时顺手换新版。
683
+ function legacyWslTroubleSeen() {
684
+ try { const j = JSON.parse(fs.readFileSync(path.join(os.homedir(), "cicy-ai", "db", "wsl-reboots.json"), "utf8")); return (Number(j.count) || 0) > 0; } catch { return false; }
685
+ }
673
686
  const WSL_KERNEL_MSI_URL = process.env.CICY_WSL_KERNEL_URL || "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi";
674
687
  // 裸内核文件(由发布工作流从 MSI 解出后放到 OSS):精简版 Windows 没有 msiexec 时直接放到位。
675
688
  const WSL_KERNEL_RAW_URL = process.env.CICY_WSL_KERNEL_RAW_URL || (require("./mirrors").OSS_RELEASES_BASE + "/wsl-kernel/kernel");
@@ -750,6 +763,12 @@ function elevatedWslSetup({ emit, need = {} } = {}) {
750
763
  ' & $SFC /scannow | Out-Null; Say ("sfc exit=" + $LASTEXITCODE)',
751
764
  ' foreach ($f in $bad) { $c = En $f }',
752
765
  '}',
766
+ `$MODERN = Join-Path $env:ProgramFiles "WSL\\wsl.exe"`,
767
+ `if (-not (Test-Path $MODERN) -and (Test-Path $MSIEXEC)) {`,
768
+ ` $wm = Join-Path $env:USERPROFILE "Downloads\\wsl.x64.msi"`,
769
+ ` Say "installing modern WSL (replaces the legacy inbox WSL: no LxssManager deadlocks, kernel built in) — download ~250MB"`,
770
+ ` try { if (-not (Test-Path $wm) -or (Get-Item $wm).Length -lt 100000000) { Invoke-WebRequest -UseBasicParsing -Uri "${WSL_MODERN_MSI_URL}" -OutFile $wm }; $p = Start-Process $MSIEXEC -ArgumentList "/i","\`"$wm\`"","/qn","/norestart" -Wait -PassThru; Say ("modern wsl msi exit=" + $p.ExitCode) } catch { Say ("modern wsl install failed: " + $_.Exception.Message) }`,
771
+ `}`,
753
772
  `$KDIR = Join-Path $S32 "lxss\\tools"; $KFILE = Join-Path $KDIR "kernel"`,
754
773
  `if (-not (Test-Path $KFILE)) {`,
755
774
  ` if ((Test-Path $MSIEXEC) -and (Test-Path "${msi.replace(/"/g, '""')}")) { $p = Start-Process $MSIEXEC -ArgumentList "/i","\`"${msi.replace(/"/g, '""')}\`"","/qn","/norestart" -Wait -PassThru; Say ("kernel msi exit=" + $p.ExitCode) }`,
@@ -849,10 +868,20 @@ async function ensureWsl({ emit } = {}) {
849
868
  // 功能都已启用、wsl.exe 也在,但 WSL 本身还不能用(`wsl -l -v` 只打印帮助)= 启用后还没
850
869
  // 重启。别去 --import(必失败),直接进自动重启流程。
851
870
  if (!(await wslFunctional())) {
871
+ if (!modernWslInstalled() && legacyWslTroubleSeen()) {
872
+ // 旧版 WSL 已经死锁/重启过 → 别再等它,换新版 WSL(一次 UAC),装完重启。
873
+ emit && emit({ phase: "install-docker", status: "running", message: "旧版 WSL 反复无响应,改为安装微软新版 WSL(会弹一次 UAC,请点「是」,下载约 250MB)…" });
874
+ await elevatedWslSetup({ emit, need: {} });
875
+ }
852
876
  log.warn("[bootstrap] ensure-wsl: features enabled but WSL not functional yet (pending reboot)");
853
877
  emit && emit({ phase: "install-docker", status: "running", message: "WSL 功能已启用但尚未生效,需要重启 Windows…" });
854
878
  return { ok: false, needsReboot: true };
855
879
  }
880
+ if (!modernWslInstalled() && legacyWslTroubleSeen()) {
881
+ emit && emit({ phase: "install-docker", status: "running", message: "这台机器的旧版 WSL 出过死锁,升级为微软新版 WSL(会弹一次 UAC,下载约 250MB)…" });
882
+ await elevatedWslSetup({ emit, need: {} });
883
+ if (modernWslInstalled()) { emit && emit({ phase: "install-docker", status: "running", message: "新版 WSL 已安装,需要重启 Windows 生效…" }); return { ok: false, needsReboot: true }; }
884
+ }
856
885
  // WSL2 内核(lxss\tools\kernel)缺失时 --import 也必失败。先把 MSI 下到 Downloads,再用
857
886
  // 同一个提权脚本装(功能已启用的话脚本只做装内核这一件事)。
858
887
  if (!wslKernelPresent()) {
@@ -988,7 +1017,7 @@ module.exports = {
988
1017
  start, stop, stopContainer, restart, checkStatus, loadImage, loadImageFromTarball,
989
1018
  downloadImageTarball, imagePresent, dockerOk, installDocker,
990
1019
  bootstrap, probeHealth, readContainerToken, dockerDesktopExe, desktopDir, downloadsDir, imageTarballPath,
991
- launchElevated, elevatedWslSetup, spawnProbe, wslFunctional, wslKernelPresent, wslExe, wslMissing, ensureWsl, virtualizationStatus,
1020
+ launchElevated, elevatedWslSetup, modernWslInstalled, spawnProbe, wslFunctional, wslKernelPresent, wslExe, wslMissing, ensureWsl, virtualizationStatus,
992
1021
  // platform-agnostic download/retry primitives, reused by native.js
993
1022
  ensureDownloaded, curlDownload, withRetry, waitUntil, run, headSize,
994
1023
  // image freshness (修「重建仍用旧镜像」—— 校验 OSS ETag 变了才重下重载)
@@ -95,7 +95,9 @@ async function ensureWslKernel({ emit } = {}) {
95
95
  // …连接尝试失败」。实测 `wsl --shutdown` 后恢复。据此标记,bootstrap 遇到就重置一次再重试。
96
96
  function isWslVmUnreachable(err) {
97
97
  if (!err) return false;
98
- const code = err.code === 4294967295 || err.code === -1 || err.code === 0xffffffff;
98
+ // bash 的退出码只会是 0-255;>255 / -1 / 4294967295 只可能来自 wsl.exe 自己(连不上 VM)。
99
+ const code = err.code === 4294967295 || err.code === -1 || err.code === 0xffffffff || (typeof err.code === "number" && err.code > 255);
100
+ if (code) return true;
99
101
  let text = "";
100
102
  try { const raw = err.stdout || ""; text = Buffer.isBuffer(raw) ? raw.toString("utf16le") : String(raw); } catch {}
101
103
  try { text += Buffer.from(String(err.stdout || ""), "binary").toString("utf16le"); } catch {}
@@ -139,7 +141,13 @@ function wslRunStream(cmd, { emit, phase = "install-docker", timeout = 900000, d
139
141
  child.stderr.on("data", pump);
140
142
  const timer = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} reject(Object.assign(new Error("timeout"), { stdout: tail })); }, timeout);
141
143
  child.on("error", (e) => { clearTimeout(timer); reject(e); });
142
- child.on("close", (code) => { clearTimeout(timer); code === 0 ? resolve({ stdout: tail }) : reject(Object.assign(new Error(`exit ${code}`), { stdout: tail })); });
144
+ child.on("close", (code) => {
145
+ clearTimeout(timer);
146
+ if (code === 0) return resolve({ stdout: tail });
147
+ const err = Object.assign(new Error(`exit ${code}`), { stdout: tail, code });
148
+ err.wslVmUnreachable = isWslVmUnreachable(err);
149
+ reject(err);
150
+ });
143
151
  });
144
152
  }
145
153
 
@@ -139,3 +139,20 @@ test("the container update script is pushed with LF endings even from a CRLF che
139
139
  assert.match(read("src/sidecar/wsl-docker.js"), /cicy-code-update\.sh"\), "utf8"\)\.replace\(\/\\r\\n\?\/g, "\\n"\)/);
140
140
  assert.match(read(".gitattributes"), /\*\.sh text eol=lf/);
141
141
  });
142
+
143
+ test("streamed wsl runs flag an unreachable VM too, and soft failures back off 2 min", () => {
144
+ const w = read("src/sidecar/wsl-docker.js");
145
+ assert.match(w, /const err = Object\.assign\(new Error\(`exit \$\{code\}`\), \{ stdout: tail, code \}\);\n\s+err\.wslVmUnreachable = isWslVmUnreachable\(err\);/);
146
+ assert.match(w, /err\.code > 255\)/);
147
+ assert.match(read("src/backends/sidecar-ipc.js"), /_softRetryAt = Date\.now\(\) \+ 2 \* 60 \* 1000;/);
148
+ });
149
+
150
+ test("legacy WSL trouble → install modern WSL (MSI mirrored to OSS) in the single elevation; wslExe prefers it", () => {
151
+ const d = read("src/sidecar/docker.js");
152
+ assert.match(d, /function modernWslInstalled\(\)/);
153
+ assert.match(d, /function legacyWslTroubleSeen\(\)/);
154
+ assert.match(d, /"WSL", "wsl\.exe"\), \/\/ 新版 WSL/);
155
+ assert.match(d, /installing modern WSL/);
156
+ assert.match(d, /if \(!modernWslInstalled\(\) && legacyWslTroubleSeen\(\)\) \{/);
157
+ assert.match(read(".github/workflows/windows-exe-release.yml"), /wsl\/wsl\.x64\.msi/);
158
+ });