cicy-desktop 2.1.369 → 2.1.370

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.369",
3
+ "version": "2.1.370",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -380,7 +380,9 @@ function curlDownload(url, dest, { emit, phase = "image", label = "下载镜像"
380
380
  // --retry-connrefused (curl 7.52+) covers a not-yet-ready net. On curl 7.71+
381
381
  // also add --retry-all-errors so a transient DNS blip (exit 6, common right
382
382
  // after app start) retries on the fast curl path instead of the slow node one.
383
- const args = ["-sL", "-A", DL_UA, "-C", "-", "--retry", "8", "--retry-delay", "3", "--retry-connrefused"];
383
+ // --connect-timeout + --speed-limit/--speed-time:被墙的源几秒就放弃,传着传着卡死(<10KB/s 持续 2 分钟)
384
+ // 也会退出 —— 否则 curl 永远挂着,bootstrap 和守护循环一起卡住(国内链路上真会发生)。
385
+ const args = ["-sL", "-A", DL_UA, "-C", "-", "--connect-timeout", "20", "--speed-limit", "10240", "--speed-time", "120", "--retry", "8", "--retry-delay", "3", "--retry-connrefused"];
384
386
  if (curlSupportsRetryAllErrors(bin)) args.push("--retry-all-errors");
385
387
  args.push("-o", dest, url);
386
388
  let child;
@@ -88,13 +88,25 @@ function sshConnect() {
88
88
  }
89
89
  // 等价于 wslRun:成功 resolve {stdout, stderr};非零退出 reject(err.stdout/err.stderr/err.code)。
90
90
  // opts.stdin:可读流,灌进命令的 stdin(docker load 用)。opts.onLine:逐行回调(流式进度)。
91
+ // ⚠️ 超时必须从调用开始就算(含建连、开通道):虚拟机慢启动时 sshd 可能接了 TCP 却迟迟不开 exec 通道,
92
+ // 以前计时器只在 exec 回调里才挂上 → 回调永不来 → 这个 promise 永不结束 → status()/守护循环整条卡死
93
+ // (xs-2002 2026-10-22 起卡了一夜,docker-status 一直停在旧时间)。
91
94
  async function vmRun(cmd, { timeout = 60000, stdin, onLine } = {}) {
92
- const c = await sshConnect();
95
+ const t0 = Date.now();
96
+ const c = await Promise.race([sshConnect(), new Promise((_, rej) => setTimeout(() => rej(new Error("ssh connect timeout")), Math.min(timeout, 30000)))]);
93
97
  return new Promise((resolve, reject) => {
94
- c.exec(`sudo -n sh -c ${shQuote(cmd)}`, (err, stream) => {
95
- if (err) { _conn = null; try { c.end(); } catch {} return reject(err); }
96
- let out = "", errOut = "", buf = "";
97
- const timer = setTimeout(() => { try { stream.close(); } catch {} reject(Object.assign(new Error(`vmRun timeout after ${timeout}ms: ${cmd.slice(0, 120)}`), { stdout: out, stderr: errOut })); }, timeout);
98
+ let stream = null, done = false;
99
+ let out = "", errOut = "", buf = "";
100
+ const timer = setTimeout(() => {
101
+ if (done) return; done = true;
102
+ try { stream && stream.close(); } catch {}
103
+ if (!stream) { _conn = null; try { c.end(); } catch {} } // 通道都没开出来:这条连接已不可信,丢掉重连
104
+ reject(Object.assign(new Error(`vmRun timeout after ${timeout}ms: ${cmd.slice(0, 120)}`), { stdout: out, stderr: errOut }));
105
+ }, Math.max(1000, timeout - (Date.now() - t0)));
106
+ c.exec(`sudo -n sh -c ${shQuote(cmd)}`, (err, st) => {
107
+ if (done) { try { st && st.close(); } catch {} return; }
108
+ if (err) { done = true; clearTimeout(timer); _conn = null; try { c.end(); } catch {} return reject(err); }
109
+ stream = st;
98
110
  const line = (chunk) => {
99
111
  if (!onLine) return;
100
112
  buf += chunk;
@@ -104,6 +116,7 @@ async function vmRun(cmd, { timeout = 60000, stdin, onLine } = {}) {
104
116
  stream.stderr.on("data", (d) => { const s = d.toString("utf8"); errOut += s; line(s); });
105
117
  stream.on("close", (code) => {
106
118
  clearTimeout(timer);
119
+ if (done) return; done = true;
107
120
  if (code === 0) resolve({ stdout: out, stderr: errOut });
108
121
  else reject(Object.assign(new Error(`exit ${code}: ${(errOut || out).trim().slice(-300)}`), { code, stdout: out, stderr: errOut }));
109
122
  });
@@ -307,8 +307,10 @@ function httpOk(port, p) {
307
307
  function accelOf() {
308
308
  const t = tailOf(F.qemuLog, 32 * 1024);
309
309
  if (!t && !pidAlive(readPid(F.qemuPid))) return "";
310
- // qemu 依次试 -accel:whpx 起不来会打一行 WHPX 报错再退到 tcg
311
- return /whpx/i.test(t) && /(fail|error|no accelerator|not supported|unavailable)/i.test(t) ? "tcg" : "whpx";
310
+ // qemu 依次试 -accel:whpx 起不来会打「failed to initialize whpx: …」再「falling back to tcg」。
311
+ // 只认这两句 —— WHPX 正常时也会打无害的 warning(如「WHPX: Failed to get performance monitoring
312
+ // features」),以前按「whpx + fail」判,把 9 台跑在 WHPX 上的机器误报成软件模拟(2026-10-23)。
313
+ return /falling back to tcg|failed to initialize whpx/i.test(t) ? "tcg" : "whpx";
312
314
  }
313
315
  function whpxFeature() { return WIN && fs.existsSync(path.join(process.env.SystemRoot || "C:\\Windows", "System32", "WinHvPlatform.dll")); }
314
316
  // 串口日志最后一行可读的话 = 开机进度(给卡片「启动中」下面那一行小字)