cicy-desktop 2.1.302 → 2.1.304

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.302",
3
+ "version": "2.1.304",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -144,10 +144,10 @@
144
144
  "//optionalDependencies": "(2026-06 回调): mac/linux 改回 native cicy-code(:8008,colima 在 16G mac 上压垮内存)→ 重新内置 cicy-code-<plat> / cicy-mihomo-<plat>,localbin.fromBundle 零网络 seed(npm 仅作更新通道,带 npmmirror→npmjs 回退)。这些由 scripts/sync-runtime-deps.cjs 在 tag-push 时同步到最新版。npm 的 os/cpu 字段保证每个平台只装自己那份。Windows 仍走 docker(WSL :8009),它那份 cicy-code-windows 用不到但 bundle 着无害。",
145
145
  "optionalDependencies": {
146
146
  "electron": "41.0.3",
147
- "cicy-code-darwin-x64": "2.3.570",
148
- "cicy-code-darwin-arm64": "2.3.570",
149
- "cicy-code-linux-x64": "2.3.570",
150
- "cicy-code-linux-arm64": "2.3.570",
147
+ "cicy-code-darwin-x64": "2.3.575",
148
+ "cicy-code-darwin-arm64": "2.3.575",
149
+ "cicy-code-linux-x64": "2.3.575",
150
+ "cicy-code-linux-arm64": "2.3.575",
151
151
  "cicy-code-windows-x64": "2.3.193",
152
152
  "cicy-mihomo-darwin-x64": "1.10.4",
153
153
  "cicy-mihomo-darwin-arm64": "1.10.4",
@@ -170,6 +170,7 @@ function register({ sidecarLogPath } = {}) {
170
170
  // 系统倒计时提示),UI 可「取消」或「立即重启」。每次开机只安排一次。
171
171
  let _rebootScheduled = false;
172
172
  let _relayMisses = 0, _relayResetDone = false; // WSL localhost 转发自愈(见 daemon 循环)
173
+ let _unknownStreak = 0, _wslRepairDone = false; // WSL 卡死自愈(见 daemon 循环)
173
174
  // 防重启循环:WSL 始终起不来时不能每次开机都重启。计数存 db/wsl-reboots.json,最多自动重启 2 次
174
175
  // (成功后 :8008 起来时清零)。
175
176
  const REBOOT_COUNT_FILE = path.join(os.homedir(), "cicy-ai", "db", "wsl-reboots.json");
@@ -228,6 +229,21 @@ function register({ sidecarLogPath } = {}) {
228
229
  // bootstrap 幂等且可续跑;首次启用 WSL 若要求重启 Windows,下次登录 Desktop
229
230
  // 会再次进入这里并从已完成的步骤继续。unknown 表示 WSL 正卡住/未响应,
230
231
  // 此时不并发安装,留给下一轮检测或显式「修复 WSL」。
232
+ // WSL 卡死(所有 wsl 命令 hang → status unknown)也不能等人点「修复 WSL」:连续 3 轮先自动
233
+ // 修(杀僵进程 + 重启 LxssManager,必要时提权一次);6 轮仍卡死 → 自动重启 Windows(受
234
+ // 2 次上限保护;重启后 bootstrap 自动重置坏 distro)。
235
+ if (s.unknown && !s.running) {
236
+ _unknownStreak += 1;
237
+ if (_unknownStreak === 3 && !_wslRepairDone && appDocker.repairWsl) {
238
+ _wslRepairDone = true;
239
+ auditDestructiveIpc(log, "docker:self-heal-wsl-wedged", null, { streak: _unknownStreak });
240
+ log.warn("[docker-daemon] WSL unresponsive for 3 checks → auto repair (kill wsl.exe, restart LxssManager)");
241
+ try { const r = await appDocker.repairWsl({ emit: () => {} }); if (r && r.needsReboot) { _lastBootstrapError = { reason: "wsl_wedged", message: "WSL 卡死且服务重启无效,将自动重启 Windows 修复", ts: Date.now() }; scheduleReboot(90); } } catch (e) { log.warn(`[docker-daemon] auto repairWsl failed: ${e.message}`); }
242
+ await refreshDockerStatus(); return;
243
+ }
244
+ if (_unknownStreak >= 6 && !_rebootScheduled) { _lastBootstrapError = { reason: "wsl_wedged", message: "WSL 持续无响应,将自动重启 Windows 修复", ts: Date.now() }; scheduleReboot(90); }
245
+ return;
246
+ } else if (!s.unknown) { _unknownStreak = 0; }
231
247
  // 容器在里面健康、Windows 侧却连不通 = WSL localhost 转发坏了(实测 wsl --shutdown 后恢复)。
232
248
  // 连续 2 轮(≈2 分钟)如此 → 每次开机最多自动重置一次 WSL。数据在 volume 里,容器由
233
249
  // unless-stopped 策略自动拉起;不算「销毁」——对用户来说它本来就已经不可用了。
@@ -640,9 +640,12 @@ async function wslMissing() {
640
640
  // wsl.exe 不存在(ENOENT):功能刚启用但还没重启 Windows 时就是这样 —— 按「缺失」处理,
641
641
  // 让 ensureWsl 走 needsReboot,而不是当成已就绪去 --import 然后 spawn ENOENT。
642
642
  if (err && (err.code === "ENOENT" || /ENOENT/.test(err.message || ""))) return resolve(true);
643
- // 旧版内置 wsl.exe 存根:任何子命令都只打印用法帮助("wsl.exe [Argument]" / "--install")。
644
- // 功能刚启用还没重启时就是这个状态 按缺失处理(ensureWsl needsReboot)。
645
- if (/未安装|not installed|--install|\[Argument\]|\[参数\]/i.test(s)) return resolve(true);
643
+ // 用法帮助有两种来源:(a) 功能刚启用还没重启的存根 wsl.exe —— 任何子命令都只打印帮助;
644
+ // (b) 老版 Win10(19042 等)的内置 wsl.exe 根本不认识 `--status`,也打印帮助,但 WSL 本身
645
+ // 完全可用。只看帮助文本区分不了 再用 `wsl -l -v` 功能探测:能列出/说"没有发行版"就是可用。
646
+ if (/未安装|not installed|--install|\[Argument\]|\[参数\]/i.test(s)) {
647
+ return wslFunctional().then((ok) => resolve(!ok));
648
+ }
646
649
  if (err && (err.killed || err.signal || err.code === "ETIMEDOUT")) return resolve(null); // timed out → unknown
647
650
  resolve(false); // wsl present (errored for another reason → assume OK)
648
651
  });
@@ -217,9 +217,21 @@ async function repairWsl({ emit } = {}) {
217
217
  if (process.platform !== "win32") return { ok: false, error: "win-only" };
218
218
  const say = (m) => { try { emit && emit({ phase: "install-docker", status: "running", message: m }); } catch {} };
219
219
  say("尝试重启 WSL 服务(LxssManager)…");
220
+ // 先把僵住的 wsl.exe 杀掉(它们挂在 LxssManager 上,不杀 Restart-Service 也 hang)。
221
+ await new Promise((r) => execFile("taskkill", ["/IM", "wsl.exe", "/F"], { timeout: 15000, windowsHide: true }, () => r()));
220
222
  await new Promise((r) => execFile("powershell", ["-NoProfile", "-Command", "Restart-Service LxssManager -Force -ErrorAction SilentlyContinue"], { timeout: 30000, windowsHide: true }, () => r()));
221
223
  await new Promise((r) => setTimeout(r, 2500));
222
- if (await lxssWedged()) {
224
+ // 「服务状态正常但所有 wsl 命令都 hang」也是卡死(实测):用 `wsl -l -q` 的超时(null)判定,
225
+ // 不只看 LxssManager 的 STOP_PENDING。非提权重启服务通常没权限 → 再提权试一次(一次 UAC)。
226
+ const stillHung = async () => (await lxssWedged()) || (await distroInstalled()) === null;
227
+ if (await stillHung()) {
228
+ say("普通权限重启 WSL 服务无效,提权再试(会弹一次 UAC)…");
229
+ try {
230
+ await docker.launchElevated("powershell.exe", ["-NoProfile", "-Command", "Get-Process wsl,wslhost -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue; Restart-Service LxssManager -Force"], { emit });
231
+ } catch {}
232
+ await new Promise((r) => setTimeout(r, 8000));
233
+ }
234
+ if (await stillHung()) {
223
235
  markWslReset();
224
236
  say("WSL 服务仍卡死,需要重启电脑;重启后会自动修复。");
225
237
  return { ok: false, needsReboot: true };
@@ -107,3 +107,18 @@ test("broken WSL localhost relay (healthy inside, unreachable from Windows) is r
107
107
  assert.match(ipc, /_relayMisses >= 2 && !_relayResetDone/);
108
108
  assert.match(ipc, /docker:self-heal-localhost-relay/);
109
109
  });
110
+
111
+ test("wsl --status printing usage (old inbox wsl.exe) is not 'missing' when wsl -l works", () => {
112
+ const d = read("src/sidecar/docker.js");
113
+ const i = d.indexOf("async function wslMissing()");
114
+ assert.match(d.slice(i, i + 2000), /return wslFunctional\(\)\.then\(\(ok\) => resolve\(!ok\)\);/);
115
+ });
116
+
117
+ test("a wedged WSL (status unknown) is auto-repaired, then auto-rebooted if still hung", () => {
118
+ const ipc = read("src/backends/sidecar-ipc.js");
119
+ assert.match(ipc, /_unknownStreak === 3 && !_wslRepairDone/);
120
+ assert.match(ipc, /docker:self-heal-wsl-wedged/);
121
+ assert.match(ipc, /_unknownStreak >= 6 && !_rebootScheduled/);
122
+ const w = read("src/sidecar/wsl-docker.js");
123
+ assert.match(w, /const stillHung = async \(\) => \(await lxssWedged\(\)\) \|\| \(await distroInstalled\(\)\) === null;/);
124
+ });