cicy-desktop 2.1.73 → 2.1.74

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.73",
3
+ "version": "2.1.74",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -24,13 +24,17 @@ const { contextBridge, ipcRenderer, shell } = require("electron");
24
24
  // args, and the reply (or error) to the console. Skip noisy channels that
25
25
  // fire on every render tick to avoid drowning the console.
26
26
  const __noisy = new Set(["backends:list", "backends:health-all"]);
27
+ // Per-IPC call/reply tracing floods the console on every render tick and is
28
+ // pure debug noise for someone just running `npx cicy-desktop`. Off by default;
29
+ // set CICY_DEBUG=1 to restore it. Errors are always logged.
30
+ const __verbose = !!(process.env.CICY_DEBUG || process.env.CICY_VERBOSE);
27
31
  let __ipcSeq = 0;
28
32
  function logInvoke(channel, ...args) {
29
33
  const id = ++__ipcSeq;
30
34
  const noisy = __noisy.has(channel);
31
- if (!noisy) console.log(`[ipc#${id}] call`, channel, ...args);
35
+ if (__verbose && !noisy) console.log(`[ipc#${id}] call`, channel, ...args);
32
36
  return ipcRenderer.invoke(channel, ...args).then(
33
- (res) => { if (!noisy) console.log(`[ipc#${id}] reply`, channel, res); return res; },
37
+ (res) => { if (__verbose && !noisy) console.log(`[ipc#${id}] reply`, channel, res); return res; },
34
38
  (err) => { console.error(`[ipc#${id}] error`, channel, err); throw err; },
35
39
  );
36
40
  }
@@ -72,8 +72,11 @@ async function openHomepage() {
72
72
 
73
73
  // Pipe renderer console + load failures to main-process stdout so we can
74
74
  // diagnose blank-page bugs from logs without opening DevTools.
75
+ const __verbose = !!(process.env.CICY_DEBUG || process.env.CICY_VERBOSE);
75
76
  homepage.webContents.on("console-message", (_e, level, msg, line, source) => {
76
- if (level >= 1) console.log(`[homepage:console L${line}] ${msg} (${source})`);
77
+ // Warnings/errors (level>=2) always surface — they're what diagnose a blank
78
+ // page. The chatty info stream only with CICY_DEBUG.
79
+ if (level >= 2 || (__verbose && level >= 1)) console.log(`[homepage:console L${line}] ${msg} (${source})`);
77
80
  });
78
81
  // Log load failures (corrupt/missing install) — no remote fallback by
79
82
  // design: the homepage is the bundled SPA, full stop.
@@ -258,6 +258,20 @@ async function openTeam(id) {
258
258
  return { ok: true, windowId: existing.id, reused: true };
259
259
  }
260
260
 
261
+ // No window yet. Before opening one against a LOCAL sidecar, make sure the
262
+ // daemon is REALLY up (TCP 探活) — opening a window at a not-yet-ready :8008
263
+ // loads a blank page the user has to manually reload (主人 bug). Start it if
264
+ // it isn't running, then poll until it answers; bail (no blank window) if it
265
+ // never comes up. Remote/custom (non-localhost) teams skip this — their page
266
+ // shows its own connecting/login UI.
267
+ if (isLocalOrigin(url)) {
268
+ const ready = await ensureLocalSidecarAlive(url);
269
+ if (!ready) {
270
+ log.warn(`[local-teams] open ${id} → local sidecar not ready, not opening blank window`);
271
+ return { ok: false, error: "sidecar_not_ready" };
272
+ }
273
+ }
274
+
261
275
  const { createWindow } = require("../utils/window-utils");
262
276
  const win = createWindow(
263
277
  { url, title: `Local · ${node.name || id}` },
@@ -268,6 +282,31 @@ async function openTeam(id) {
268
282
  return { ok: true, windowId: win.id, reused: false };
269
283
  }
270
284
 
285
+ // Is this URL served by something on the local machine (the cicy-code sidecar)?
286
+ function isLocalOrigin(url) {
287
+ try { const h = new URL(url).hostname; return h === "127.0.0.1" || h === "localhost" || h === "::1"; }
288
+ catch { return false; }
289
+ }
290
+
291
+ // Ensure the local cicy-code daemon serving `url` actually answers before we
292
+ // open a window at it. Already up → true immediately. Down → start it, then
293
+ // poll a TCP probe (NOT /api/health, which is unreliable mid-boot) until it
294
+ // binds or we time out.
295
+ async function ensureLocalSidecarAlive(url, { timeoutMs = 15000 } = {}) {
296
+ let port;
297
+ try { port = Number(new URL(url).port) || 8008; } catch { return true; } // unparseable → don't block
298
+ let sidecar;
299
+ try { sidecar = require("../sidecar/cicy-code"); } catch { return true; }
300
+ if (await sidecar.probeExisting(port)) return true; // already answering
301
+ try { await sidecar.start({ port }); } catch {} // not up → bring it up
302
+ const t0 = Date.now();
303
+ while (Date.now() - t0 < timeoutMs) {
304
+ if (await sidecar.probeExisting(port)) return true;
305
+ await new Promise((r) => setTimeout(r, 300));
306
+ }
307
+ return false;
308
+ }
309
+
271
310
  function stripVolatile(u) {
272
311
  try {
273
312
  const p = new URL(u);
@@ -303,6 +342,25 @@ function reloadTeam(id) {
303
342
  }
304
343
  }
305
344
 
345
+ // Close any open window served by the local sidecar (origin
346
+ // http://127.0.0.1:<port> or http://localhost:<port>). Called when the sidecar
347
+ // is STOPPED so a now-dead :8008 team window doesn't linger showing a broken
348
+ // page (主人 bug report: stop 后应关掉 localhost 的 window).
349
+ function closeLocalWindows(port = 8008) {
350
+ const origins = new Set([`http://127.0.0.1:${port}`, `http://localhost:${port}`]);
351
+ let closed = 0;
352
+ for (const w of BrowserWindow.getAllWindows()) {
353
+ if (!w || w.isDestroyed()) continue;
354
+ let origin = "";
355
+ try { origin = new URL(w.webContents.getURL()).origin; } catch { continue; }
356
+ if (origins.has(origin)) {
357
+ try { w.close(); closed++; } catch {}
358
+ }
359
+ }
360
+ if (closed) log.info(`[local-teams] closed ${closed} local window(s) on :${port} after stop`);
361
+ return closed;
362
+ }
363
+
306
364
  // ── mutations ──────────────────────────────────────────────────────────
307
365
  //
308
366
  // add/remove/upgrade let an external caller (currently the cloud Team
@@ -737,4 +795,4 @@ async function upgradeTeam(id) {
737
795
  return result;
738
796
  }
739
797
 
740
- module.exports = { list, openTeam, reloadTeam, addTeam, removeTeam, updateTeam, upgradeTeam };
798
+ module.exports = { list, openTeam, reloadTeam, closeLocalWindows, addTeam, removeTeam, updateTeam, upgradeTeam };
@@ -92,6 +92,9 @@ function register({ sidecarLogPath } = {}) {
92
92
  ipcMain.handle("sidecar:stop", async () => {
93
93
  try {
94
94
  await sidecar.stop();
95
+ // The daemon is gone — close any open team window pointing at it so the
96
+ // user isn't left staring at a dead :8008 page (主人 bug report).
97
+ try { require("./local-teams").closeLocalWindows(PORT); } catch {}
95
98
  return { ok: true };
96
99
  } catch (e) {
97
100
  return { ok: false, error: e.message };
@@ -331,15 +331,21 @@ function initWindowMonitoring(win) {
331
331
  const logs = windowLogs.get(winId);
332
332
 
333
333
  const counters = windowIndexCounters.get(winId);
334
- log.info(
335
- `[${JSON.stringify({
336
- timestamp: Date.now(),
337
- level: ["verbose", "info", "warning", "error"][level] || "log",
338
- message,
339
- line,
340
- source: sourceId,
341
- })}`
342
- );
334
+ // Mirror EVERY renderer console line into the main log only with CICY_DEBUG;
335
+ // by default forward warnings/errors (level>=2) so normal startup stays quiet
336
+ // but real problems still surface. The in-memory log store below is unaffected
337
+ // (the log-viewer API still sees everything).
338
+ if (level >= 2 || process.env.CICY_DEBUG || process.env.CICY_VERBOSE) {
339
+ log.info(
340
+ `[${JSON.stringify({
341
+ timestamp: Date.now(),
342
+ level: ["verbose", "info", "warning", "error"][level] || "log",
343
+ message,
344
+ line,
345
+ source: sourceId,
346
+ })}`
347
+ );
348
+ }
343
349
  if (logs && counters) {
344
350
  logs.push({
345
351
  index: ++counters.log,
@@ -568,7 +568,7 @@ export default function App() {
568
568
  ))}
569
569
  </div>
570
570
 
571
- {showLocal && <DockerSetup onReady={fetchLocalTeams} />}
571
+ {/* Docker 安装卡已下线 (主人令): Windows 走原生 cicy-code.exe --helper,不再用 Docker。 */}
572
572
  {showLocal && localList.length > 0 && <MitmConsentCard team={localList[0]} />}
573
573
 
574
574
  {profileError && (
@@ -1061,10 +1061,13 @@ function LocalTeamCard({ team, onOpen, onRename, onRefresh }) {
1061
1061
  };
1062
1062
  const BUSY_LABEL = { start: "启动中…", restart: "重启中…", update: "更新中…", stop: "停止中…" };
1063
1063
 
1064
- // 打开 is NEVER gated on /api/health openTeam() in main doesn't check it,
1065
- // it just opens the window. health is an indicator, not a gate. When the
1066
- // LOCAL daemon is down we start it first; remote/other-port teams just open
1067
- // and let the loaded page show its own connecting/login/error UI.
1064
+ // 打开 flow (主人 spec): start the LOCAL daemon if it's down (with a 启动中…
1065
+ // toast), then open. The window itself is opened by openTeam() in main, which
1066
+ // (1) reuses an already-open window for this team (list_windows check first),
1067
+ // and (2) for a local team, TCP-探活 until :8008 actually answers before
1068
+ // creating the window — so we never pop a blank page that needs a manual
1069
+ // reload. (/api/health is NOT used — it's unreliable mid-boot; the gate is a
1070
+ // raw TCP probe.) Remote/custom teams just open and show their own UI.
1068
1071
  const handleOpen = async () => {
1069
1072
  if (busy) return;
1070
1073
  if (!running && local && window.cicy?.sidecar?.start) {
@@ -1078,7 +1081,7 @@ function LocalTeamCard({ team, onOpen, onRename, onRefresh }) {
1078
1081
  }
1079
1082
  toast.dismiss(opToastId); // came up — no lingering toast, the window opens
1080
1083
  }
1081
- onOpen(); // open regardless of health the window/page handles the rest
1084
+ onOpen(); // openTeam() gates on list_windows + TCP liveness before showing
1082
1085
  };
1083
1086
  const openLabel = running
1084
1087
  ? tr("localTeams.open", "打开")