cicy-desktop 2.1.89 → 2.1.90

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.89",
3
+ "version": "2.1.90",
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.3.9",
137
- "cicy-code-darwin-arm64": "2.3.9",
138
- "cicy-code-linux-x64": "2.3.9",
139
- "cicy-code-linux-arm64": "2.3.9",
140
- "cicy-code-windows-x64": "2.3.9",
136
+ "cicy-code-darwin-x64": "2.3.11",
137
+ "cicy-code-darwin-arm64": "2.3.11",
138
+ "cicy-code-linux-x64": "2.3.11",
139
+ "cicy-code-linux-arm64": "2.3.11",
140
+ "cicy-code-windows-x64": "2.3.11",
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",
@@ -17,7 +17,31 @@ async function getTargets(debuggerPort, host = "127.0.0.1") {
17
17
  }
18
18
 
19
19
  async function createTarget(debuggerPort, targetUrl = "about:blank", host = "127.0.0.1") {
20
- const url = `http://${host}:${debuggerPort}/json/new?${encodeURIComponent(String(targetUrl || "about:blank"))}`;
20
+ const wantUrl = String(targetUrl || "about:blank");
21
+ // Prefer the browser-level CDP endpoint (Target.createTarget over the browser
22
+ // websocket). Newer Chrome (149+) rejects/closes the HTTP /json/new endpoint,
23
+ // and — crucially — the browser websocket can open a tab even when there are
24
+ // 0 page targets: the windowless Chrome that lingers on macOS after its last
25
+ // window is closed. Without this, /json/new fails, ensurePageTargets can't
26
+ // create a tab, and the profile is stuck at "No inspectable targets".
27
+ try {
28
+ const version = await getVersion(debuggerPort, host);
29
+ const browserWs = version && version.webSocketDebuggerUrl;
30
+ if (browserWs) {
31
+ const res = await callCdp({
32
+ debuggerPort,
33
+ host,
34
+ target: browserWs,
35
+ method: "Target.createTarget",
36
+ params: { url: wantUrl },
37
+ });
38
+ const id = res && res.targetId;
39
+ if (id) return { id, targetId: id };
40
+ }
41
+ } catch (_) {
42
+ // Fall through to the legacy HTTP endpoint (older Chrome / unexpected shape).
43
+ }
44
+ const url = `http://${host}:${debuggerPort}/json/new?${encodeURIComponent(wantUrl)}`;
21
45
  const response = await fetch(url, { method: "PUT" });
22
46
  if (!response.ok) {
23
47
  throw new Error(`Request failed ${response.status} ${response.statusText} for ${url}`);
package/src/main.js CHANGED
@@ -267,6 +267,11 @@ const {
267
267
  const automationEnabled =
268
268
  process.env.CICY_DESKTOP_HTTP === "1" || enableMcp || !!process.env.CICY_MASTER_URL;
269
269
 
270
+ // CDP 调试口(9221)默认开(主人令),只绑 127.0.0.1 回环、不暴露到网络。与上面的
271
+ // HTTP/MCP server 解耦:HTTP server 仍按 automationEnabled 控制(避免对外 0.0.0.0
272
+ // 暴露 / 防火墙弹窗),CDP 单独默认开。要关 CDP:显式设 CICY_DESKTOP_CDP=0。
273
+ const cdpEnabled = process.env.CICY_DESKTOP_CDP !== "0";
274
+
270
275
  config.port = PORT;
271
276
  if (chromeBinary) {
272
277
  config.chromeBinary = chromeBinary;
@@ -458,12 +463,12 @@ const server = http.createServer(app);
458
463
 
459
464
  // 必须在 whenReady 之前设置调试端口。CDP 无鉴权,仅在自动化启用时才开,并显式
460
465
  // 绑回环地址(默认已是 127.0.0.1,显式设置防止意外暴露到 0.0.0.0)。
461
- if (automationEnabled) {
466
+ if (cdpEnabled) {
462
467
  electronApp.commandLine.appendSwitch("remote-debugging-port", "9221");
463
468
  electronApp.commandLine.appendSwitch("remote-debugging-address", "127.0.0.1");
464
- log.info("[MCP] Remote debugging enabled on 127.0.0.1:9221 (automation enabled)");
469
+ log.info("[MCP] Remote debugging enabled on 127.0.0.1:9221 (default on; set CICY_DESKTOP_CDP=0 to disable)");
465
470
  } else {
466
- log.info("[MCP] Remote debugging port NOT opened (automation disabled — set --mcp / CICY_DESKTOP_HTTP=1 / CICY_MASTER_URL to enable)");
471
+ log.info("[MCP] Remote debugging port NOT opened (CICY_DESKTOP_CDP=0)");
467
472
  }
468
473
 
469
474
  // Register the cicy:// scheme (tab-browser start page) — must run before ready.
@@ -62,6 +62,25 @@ function normalizePrivateProxy(proxyValue) {
62
62
  return p.enabled && p.url ? p.url : null;
63
63
  }
64
64
 
65
+ // All Chrome profiles share the single chrome-profile-1 mihomo listener (20001):
66
+ // cicy-mihomo's default config only emits that one listener (profile-2/3 are
67
+ // commented out), so a profile pointing at a dead per-profile port (20002…) or
68
+ // at no proxy at all can't load pages. Self-heal at launch — empty proxy or a
69
+ // local chrome-profile-range port (20000–20099) collapses to 20001; a genuine
70
+ // external proxy is left untouched. Non-destructive: only the launch arg is
71
+ // rewritten, chrome.json is not.
72
+ const SHARED_CHROME_PROXY = "socks5://127.0.0.1:20001";
73
+ function canonicalChromeProxy(proxyUrl) {
74
+ if (!proxyUrl) return SHARED_CHROME_PROXY;
75
+ try {
76
+ const u = new URL(proxyUrl);
77
+ const isLocal = u.hostname === "127.0.0.1" || u.hostname === "localhost";
78
+ const port = Number(u.port);
79
+ if (isLocal && port >= 20000 && port <= 20099) return SHARED_CHROME_PROXY;
80
+ } catch {}
81
+ return proxyUrl;
82
+ }
83
+
65
84
  function readPrivateChromeConfig() {
66
85
  if (!fs.existsSync(PRIVATE_CHROME_JSON)) return {};
67
86
  return JSON.parse(fs.readFileSync(PRIVATE_CHROME_JSON, "utf-8"));
@@ -317,7 +336,7 @@ async function launchOrActivateProfile({
317
336
  throw new Error("Chrome debugger port 9221 is reserved by Electron. Please use another port.");
318
337
  }
319
338
 
320
- const effectiveProxy = normalized.proxyUrl;
339
+ const effectiveProxy = canonicalChromeProxy(normalized.proxyUrl);
321
340
  const effectiveUserDataDirRoot =
322
341
  normalized.expanded.rpaDir ||
323
342
  getDefaultUserDataDirRoot(accountIdx, config.chromeUserDataRoot || DEFAULT_USER_DATA_BASE_ROOT);
@@ -327,38 +346,72 @@ async function launchOrActivateProfile({
327
346
  // Script parity: if /json/version reachable => activate first page target and return reused
328
347
  const liveStatus = await probeChromeDebugger(effectivePort);
329
348
  if (liveStatus.isRunning) {
330
- const { targets, activatedTargetId } = await ensurePageTargets({
331
- debuggerPort: effectivePort,
332
- url,
333
- activateIfRunning,
334
- });
335
- if (activateIfRunning) bringChromeAppToForeground();
336
-
337
- const nextRuntime = registry.upsert(accountIdx, {
338
- status: "running",
339
- debuggerPort: effectivePort,
340
- proxy: effectiveProxy || null,
341
- userDataDirRoot: displayUserDataDir,
342
- profileDirectory: getProfileDirectory(accountIdx),
343
- url: url || null,
344
- webSocketDebuggerUrl: liveStatus.webSocketDebuggerUrl || null,
345
- error: null,
346
- });
349
+ // Detect a windowless Chrome debugger up but 0 page targets. It lingers on
350
+ // macOS after its last window is closed; reusing it is unreliable because a
351
+ // CDP-created tab has no browser window and Chrome discards it, leaving the
352
+ // profile stuck at "No inspectable targets". Only trust an explicit empty
353
+ // list (a transient /json/list failure must NOT be read as windowless, or we
354
+ // could kill a Chrome that actually has the user's tabs open).
355
+ let windowless = false;
356
+ try {
357
+ const live = await getTargets(effectivePort);
358
+ windowless = Array.isArray(live) && live.filter((t) => t.type === "page").length === 0;
359
+ } catch (_) {
360
+ windowless = false;
361
+ }
347
362
 
348
- return {
349
- reused: true,
350
- activatedTargetId,
351
- profileKey,
352
- profileSource,
353
- accountIdx,
354
- gmail: normalized.gmail,
355
- port: effectivePort,
356
- proxy: effectiveProxy || null,
357
- userDataDirRoot: displayUserDataDir,
358
- runtime: nextRuntime,
359
- liveStatus,
360
- targetsPreview: buildTargetsPreview(targets),
361
- };
363
+ if (!windowless) {
364
+ const { targets, activatedTargetId } = await ensurePageTargets({
365
+ debuggerPort: effectivePort,
366
+ url,
367
+ activateIfRunning,
368
+ });
369
+ if (activateIfRunning) bringChromeAppToForeground();
370
+
371
+ const nextRuntime = registry.upsert(accountIdx, {
372
+ status: "running",
373
+ debuggerPort: effectivePort,
374
+ proxy: effectiveProxy || null,
375
+ userDataDirRoot: displayUserDataDir,
376
+ profileDirectory: getProfileDirectory(accountIdx),
377
+ url: url || null,
378
+ webSocketDebuggerUrl: liveStatus.webSocketDebuggerUrl || null,
379
+ error: null,
380
+ });
381
+
382
+ return {
383
+ reused: true,
384
+ activatedTargetId,
385
+ profileKey,
386
+ profileSource,
387
+ accountIdx,
388
+ gmail: normalized.gmail,
389
+ port: effectivePort,
390
+ proxy: effectiveProxy || null,
391
+ userDataDirRoot: displayUserDataDir,
392
+ runtime: nextRuntime,
393
+ liveStatus,
394
+ targetsPreview: buildTargetsPreview(targets),
395
+ };
396
+ }
397
+
398
+ // Windowless: tear the lingering process down (release its SingletonLock on
399
+ // the user-data-dir) before falling through to the fresh-launch path, which
400
+ // spawns Chrome with a real, persistent window.
401
+ const lingering = registry.get(accountIdx);
402
+ if (lingering?.pid) {
403
+ closeChromeProcess(lingering.pid);
404
+ } else if (liveStatus.webSocketDebuggerUrl) {
405
+ try {
406
+ await callCdp({
407
+ debuggerPort: effectivePort,
408
+ target: liveStatus.webSocketDebuggerUrl,
409
+ method: "Browser.close",
410
+ params: {},
411
+ });
412
+ } catch (_) {}
413
+ }
414
+ await new Promise((resolve) => setTimeout(resolve, 1000));
362
415
  }
363
416
 
364
417
  ensureRpaProfileInitialized({
@@ -910,11 +963,25 @@ function registerChromeTools(registerTool) {
910
963
  }
911
964
 
912
965
  try {
966
+ // Browser-level CDP methods (Target.*, Browser.*) must attach to the
967
+ // browser endpoint, not a page target. Without an explicit target, CRI
968
+ // defaults to the first page target and fails with "No inspectable
969
+ // targets" when the profile has 0 page targets — a windowless Chrome
970
+ // lingering after its last window was closed, or Chrome 149 where the
971
+ // HTTP /json/new tab-create endpoint is disabled. Resolve the browser
972
+ // websocket so "add tab" (Target.createTarget) works from any state.
973
+ let effectiveTarget = target;
974
+ if (!effectiveTarget && /^(Target|Browser)\./.test(method)) {
975
+ try {
976
+ const v = await getVersion(port);
977
+ if (v && v.webSocketDebuggerUrl) effectiveTarget = v.webSocketDebuggerUrl;
978
+ } catch (_) {}
979
+ }
913
980
  const result = await callCdp({
914
981
  debuggerPort: port,
915
982
  method,
916
983
  params: params || {},
917
- target,
984
+ target: effectiveTarget,
918
985
  });
919
986
  registry.upsert(accountIdx, {
920
987
  status: "running",