cicy-desktop 2.1.73 → 2.1.75
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 +6 -6
- package/src/backends/homepage-preload.js +6 -2
- package/src/backends/homepage-window.js +6 -1
- package/src/backends/local-teams.js +59 -1
- package/src/backends/sidecar-ipc.js +3 -0
- package/src/main.js +8 -0
- package/src/utils/app-icon.js +22 -0
- package/src/utils/window-monitor.js +15 -9
- package/src/utils/window-utils.js +2 -0
- package/workers/render/src/App.jsx +20 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cicy-desktop",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.75",
|
|
4
4
|
"description": "CiCy - AI-powered operating system browser",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -133,11 +133,11 @@
|
|
|
133
133
|
},
|
|
134
134
|
"//optionalDependencies": "Runtime Bundle v1 (主人指令): platform binaries delivered by `npm i -g cicy-desktop` itself — npm installs only the current-platform subpackage (os/cpu pinned in each), so first start seeds the runtime store with ZERO network, ZERO npx. Windows packages are named *-windows-* (npm spam filter 403s new names containing win32). cicy-msys2 added once published.",
|
|
135
135
|
"optionalDependencies": {
|
|
136
|
-
"cicy-code-darwin-x64": "2.2.
|
|
137
|
-
"cicy-code-darwin-arm64": "2.2.
|
|
138
|
-
"cicy-code-linux-x64": "2.2.
|
|
139
|
-
"cicy-code-linux-arm64": "2.2.
|
|
140
|
-
"cicy-code-windows-x64": "2.2.
|
|
136
|
+
"cicy-code-darwin-x64": "2.2.6",
|
|
137
|
+
"cicy-code-darwin-arm64": "2.2.6",
|
|
138
|
+
"cicy-code-linux-x64": "2.2.6",
|
|
139
|
+
"cicy-code-linux-arm64": "2.2.6",
|
|
140
|
+
"cicy-code-windows-x64": "2.2.6",
|
|
141
141
|
"cicy-mihomo-darwin-x64": "1.10.4",
|
|
142
142
|
"cicy-mihomo-darwin-arm64": "1.10.4",
|
|
143
143
|
"cicy-mihomo-linux-x64": "1.10.4",
|
|
@@ -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
|
}
|
|
@@ -32,6 +32,8 @@ async function openHomepage() {
|
|
|
32
32
|
minWidth: 360,
|
|
33
33
|
minHeight: 480,
|
|
34
34
|
title: "CiCy Desktop",
|
|
35
|
+
icon: require("../utils/app-icon").appIconPath(), // npx/unpackaged → set the
|
|
36
|
+
// window+taskbar icon ourselves (no .exe to embed it on Windows).
|
|
35
37
|
backgroundColor: "#0d1117",
|
|
36
38
|
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",
|
|
37
39
|
// Auto-hide the native menu bar on win/linux. The bar reappears when
|
|
@@ -72,8 +74,11 @@ async function openHomepage() {
|
|
|
72
74
|
|
|
73
75
|
// Pipe renderer console + load failures to main-process stdout so we can
|
|
74
76
|
// diagnose blank-page bugs from logs without opening DevTools.
|
|
77
|
+
const __verbose = !!(process.env.CICY_DEBUG || process.env.CICY_VERBOSE);
|
|
75
78
|
homepage.webContents.on("console-message", (_e, level, msg, line, source) => {
|
|
76
|
-
|
|
79
|
+
// Warnings/errors (level>=2) always surface — they're what diagnose a blank
|
|
80
|
+
// page. The chatty info stream only with CICY_DEBUG.
|
|
81
|
+
if (level >= 2 || (__verbose && level >= 1)) console.log(`[homepage:console L${line}] ${msg} (${source})`);
|
|
77
82
|
});
|
|
78
83
|
// Log load failures (corrupt/missing install) — no remote fallback by
|
|
79
84
|
// 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 };
|
package/src/main.js
CHANGED
|
@@ -117,6 +117,14 @@ if (!__singleLock) {
|
|
|
117
117
|
electronApp.exit(0);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// Windows groups taskbar buttons + picks the taskbar icon by AppUserModelId.
|
|
121
|
+
// Unpackaged (npx / npm i -g) there's no installer to register one, so without
|
|
122
|
+
// this the taskbar shows the stock Electron icon even when each BrowserWindow
|
|
123
|
+
// sets its own. Must be set before any window is created. (No-op off Windows.)
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
try { electronApp.setAppUserModelId("com.cicy.desktop"); } catch {}
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
// Register cicy-desktop:// as the desktop's URL protocol. We MOVED off the bare
|
|
121
129
|
// `cicy://` scheme because it collides: the CiCy mobile/Expo app (com.cicy-ai
|
|
122
130
|
// .mobile) and a generic com.github.Electron both claim `cicy:`, so a browser
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Resolve the bundled app icon for BrowserWindow({ icon }).
|
|
2
|
+
//
|
|
3
|
+
// When cicy-desktop runs UNPACKAGED (npx / npm i -g — the mac/win/linux default,
|
|
4
|
+
// 主人令), there is no electron-builder .exe to embed the icon, so every window
|
|
5
|
+
// falls back to the stock Electron icon unless we point BrowserWindow at our own
|
|
6
|
+
// icon file explicitly. The icons ship in build/ (published — no files[]/.npmignore
|
|
7
|
+
// excludes it). Windows wants a .ico; linux a .png; macOS takes the dock icon
|
|
8
|
+
// from elsewhere but a .png here is harmless.
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
11
|
+
const ROOT = path.join(__dirname, "..", ".."); // src/utils → package root
|
|
12
|
+
const ICON = {
|
|
13
|
+
win32: path.join(ROOT, "build", "icon.ico"),
|
|
14
|
+
linux: path.join(ROOT, "build", "icon.png"),
|
|
15
|
+
darwin: path.join(ROOT, "build", "icon.png"),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function appIconPath() {
|
|
19
|
+
return ICON[process.platform] || ICON.linux;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { appIconPath };
|
|
@@ -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
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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,
|
|
@@ -284,6 +284,8 @@ function createWindow(options = {}, accountIdx = 0, forceNew = false) {
|
|
|
284
284
|
height: winHeight,
|
|
285
285
|
x: posX,
|
|
286
286
|
y: posY,
|
|
287
|
+
icon: require("./app-icon").appIconPath(), // npx/unpackaged → set our own icon
|
|
288
|
+
// (Windows has no built .exe to embed it; default electron icon otherwise).
|
|
287
289
|
// Native menu bar collapses by default on win/linux; press Alt to
|
|
288
290
|
// peek. Same UX as the homepage window — keeps the chrome out of
|
|
289
291
|
// the way for backend pages.
|
|
@@ -568,7 +568,7 @@ export default function App() {
|
|
|
568
568
|
))}
|
|
569
569
|
</div>
|
|
570
570
|
|
|
571
|
-
{
|
|
571
|
+
{/* Docker 安装卡已下线 (主人令): Windows 走原生 cicy-code.exe --helper,不再用 Docker。 */}
|
|
572
572
|
{showLocal && localList.length > 0 && <MitmConsentCard team={localList[0]} />}
|
|
573
573
|
|
|
574
574
|
{profileError && (
|
|
@@ -975,9 +975,17 @@ function LocalTeamCard({ team, onOpen, onRename, onRefresh }) {
|
|
|
975
975
|
const v = JSON.parse(r.body)?.version || null;
|
|
976
976
|
setLatest(v);
|
|
977
977
|
if (manual && v) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
978
|
+
if (!team.version) {
|
|
979
|
+
// Current version UNKNOWN (daemon stopped, or health returned no
|
|
980
|
+
// version). Do NOT claim "已是最新" and NEVER show the latest as if
|
|
981
|
+
// it were the current version (the old `team.version || v` bug made
|
|
982
|
+
// it say "已是最新 v<latest>" while the running daemon was older).
|
|
983
|
+
setUpToDateMsg(`${tr("sidecar.latestVersionIs", "最新版本")} v${v}·${tr("sidecar.startToCompare", "启动后对比当前版本")}`);
|
|
984
|
+
setTimeout(() => setUpToDateMsg(""), 3500);
|
|
985
|
+
} else if (cmpVer(v, team.version) > 0) {
|
|
986
|
+
// Behind — the 更新 badge/button drives the upgrade; no toast here.
|
|
987
|
+
} else {
|
|
988
|
+
setUpToDateMsg(`${tr("sidecar.upToDate", "已是最新")} v${team.version}`);
|
|
981
989
|
setTimeout(() => setUpToDateMsg(""), 2500);
|
|
982
990
|
}
|
|
983
991
|
}
|
|
@@ -1061,10 +1069,13 @@ function LocalTeamCard({ team, onOpen, onRename, onRefresh }) {
|
|
|
1061
1069
|
};
|
|
1062
1070
|
const BUSY_LABEL = { start: "启动中…", restart: "重启中…", update: "更新中…", stop: "停止中…" };
|
|
1063
1071
|
|
|
1064
|
-
// 打开
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1067
|
-
// and
|
|
1072
|
+
// 打开 flow (主人 spec): start the LOCAL daemon if it's down (with a 启动中…
|
|
1073
|
+
// toast), then open. The window itself is opened by openTeam() in main, which
|
|
1074
|
+
// (1) reuses an already-open window for this team (list_windows check first),
|
|
1075
|
+
// and (2) for a local team, TCP-探活 until :8008 actually answers before
|
|
1076
|
+
// creating the window — so we never pop a blank page that needs a manual
|
|
1077
|
+
// reload. (/api/health is NOT used — it's unreliable mid-boot; the gate is a
|
|
1078
|
+
// raw TCP probe.) Remote/custom teams just open and show their own UI.
|
|
1068
1079
|
const handleOpen = async () => {
|
|
1069
1080
|
if (busy) return;
|
|
1070
1081
|
if (!running && local && window.cicy?.sidecar?.start) {
|
|
@@ -1078,7 +1089,7 @@ function LocalTeamCard({ team, onOpen, onRename, onRefresh }) {
|
|
|
1078
1089
|
}
|
|
1079
1090
|
toast.dismiss(opToastId); // came up — no lingering toast, the window opens
|
|
1080
1091
|
}
|
|
1081
|
-
onOpen(); //
|
|
1092
|
+
onOpen(); // openTeam() gates on list_windows + TCP liveness before showing
|
|
1082
1093
|
};
|
|
1083
1094
|
const openLabel = running
|
|
1084
1095
|
? tr("localTeams.open", "打开")
|