cicy-desktop 2.1.338 → 2.1.340

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.338",
3
+ "version": "2.1.340",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -229,6 +229,41 @@ async function download(url, dest, onProgress) {
229
229
  throw lastErr || new Error("download failed");
230
230
  }
231
231
 
232
+ // ── 自动安装的循环闸 ─────────────────────────────────────────────────────────
233
+ // Auto-install kills the app by design (an installer cannot replace a running
234
+ // exe). So if an install does NOT land, the app comes back on the old version,
235
+ // the next check() tries the same thing, and the machine is stuck in a restart
236
+ // loop that closes the user's window every time. That is not hypothetical — it
237
+ // took the whole fleet down. The attempt is therefore remembered ACROSS
238
+ // restarts, and a version that has already failed twice is not auto-installed
239
+ // again until the cooldown passes; the update is still offered, just not forced.
240
+ const AUTO_TRY_MAX = 2;
241
+ const AUTO_TRY_COOLDOWN_MS = 6 * 60 * 60 * 1000;
242
+
243
+ function readAutoTry() {
244
+ try { return readGlobalConfig(GLOBAL_JSON)?.desktopAutoInstall || {}; } catch { return {}; }
245
+ }
246
+ function noteAutoTry(version) {
247
+ const cur = readAutoTry();
248
+ const count = cur.version === version ? (cur.count || 0) + 1 : 1;
249
+ try {
250
+ updateGlobalConfig(GLOBAL_JSON, (c) => ({
251
+ ...(c || {}),
252
+ desktopAutoInstall: { version, count, at: Date.now() },
253
+ }));
254
+ } catch {}
255
+ return count;
256
+ }
257
+ function clearAutoTry() {
258
+ try { updateGlobalConfig(GLOBAL_JSON, (c) => { const n = { ...(c || {}) }; delete n.desktopAutoInstall; return n; }); } catch {}
259
+ }
260
+ function autoInstallAllowed(version) {
261
+ const t = readAutoTry();
262
+ if (t.version !== version) return true;
263
+ if ((t.count || 0) < AUTO_TRY_MAX) return true;
264
+ return Date.now() - (t.at || 0) > AUTO_TRY_COOLDOWN_MS;
265
+ }
266
+
232
267
  // ── 状态机 + 广播 ─────────────────────────────────────────────────────────────
233
268
  let _win = null;
234
269
  let _state = { status: "idle", version: null, current: null, progress: null, filePath: null, error: null, autoUpdate: false, auto: false };
@@ -260,6 +295,9 @@ function init(mainWin) {
260
295
  _win = mainWin;
261
296
  _state.current = app.getVersion();
262
297
  _state.autoUpdate = getAutoUpdate();
298
+ // Running the version we last tried to install = that attempt worked; forget
299
+ // it so a future update to the same number is never wrongly blocked.
300
+ try { if (readAutoTry().version === _state.current) clearAutoTry(); } catch {}
263
301
  setTimeout(() => check().catch(() => {}), 15_000); // 启动后探一次
264
302
  setInterval(() => check().catch(() => {}), 30 * 60 * 1000); // 每 30 分钟
265
303
  }
@@ -279,10 +317,15 @@ async function check() {
279
317
  log.info(`[app-updater] ${latest} 安装包就绪:${ready}`);
280
318
  broadcast({ status: "available", version: latest, current, progress: null, filePath: null, autoUpdate: getAutoUpdate(), auto: false });
281
319
  if (getAutoUpdate()) {
282
- log.info(`[app-updater] auto-update on → downloading ${latest} and installing without asking`);
283
- broadcast({ auto: true });
284
- await downloadUpdate();
285
- if (_state.status === "ready") installNow();
320
+ if (!autoInstallAllowed(latest)) {
321
+ log.warn(`[app-updater] ${latest} 已自动安装失败 ${AUTO_TRY_MAX} 次,冷却中 — 只提示不再自动装(避免反复关闭 app)`);
322
+ } else {
323
+ const n = noteAutoTry(latest);
324
+ log.info(`[app-updater] auto-update on → downloading ${latest} and installing without asking (attempt ${n}/${AUTO_TRY_MAX})`);
325
+ broadcast({ auto: true });
326
+ await downloadUpdate();
327
+ if (_state.status === "ready") installNow();
328
+ }
286
329
  }
287
330
  } else { log.info(`[app-updater] ${latest} 版本号已更新但所有来源都拿不到安装包(HEAD 非 200):${urls.join(" , ")} — 暂不提示更新`); broadcast({ status: "up-to-date", version: current, current }); }
288
331
  } else {
@@ -364,8 +407,12 @@ function installWindows(installer) {
364
407
  // VBS escapes a double quote by doubling it. cmd waits for the installer (no
365
408
  // `start` on that leg), then the pause lets Windows release the replaced
366
409
  // files before the new exe runs hidden.
410
+ // Wait for THIS process to exit before the installer starts: it cannot replace
411
+ // a running exe, and previously the chain and app.quit() raced — the installer
412
+ // could begin while the app was still up, fail, and leave the version
413
+ // unchanged, which is what fed the restart loop.
367
414
  const cmd =
368
- `cmd /c ""${installer}" /S & timeout /t 20 /nobreak >nul & start "" "${exe}" --hidden"`;
415
+ `cmd /c "timeout /t 8 /nobreak >nul & "${installer}" /S & timeout /t 20 /nobreak >nul & start "" "${exe}" --hidden"`;
369
416
  const line = `sh.Run "${cmd.replace(/"/g, '""')}", 0, False`;
370
417
  fs.writeFileSync(vbs, ['Set sh = CreateObject("WScript.Shell")', line, ""].join("\r\n"));
371
418
  const ch = spawn("wscript.exe", ["//B", "//Nologo", vbs], {
@@ -375,8 +422,8 @@ function installWindows(installer) {
375
422
  });
376
423
  ch.unref();
377
424
  log.info(`[app-updater] hidden install + relaunch chain started (pid ${ch.pid})`);
378
- // Quit so the installer can replace the files it is about to overwrite.
379
- setTimeout(() => { try { app.quit(); } catch {} }, 1500);
425
+ // Quit promptly the chain waits 8s for us before touching anything.
426
+ setTimeout(() => { try { app.quit(); } catch {} }, 500);
380
427
  }
381
428
 
382
429
  function installNow() {