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 +6 -6
- package/src/chrome/chrome-cdp-client.js +25 -1
- package/src/main.js +8 -3
- package/src/tools/chrome-tools.js +100 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cicy-desktop",
|
|
3
|
-
"version": "2.1.
|
|
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.
|
|
137
|
-
"cicy-code-darwin-arm64": "2.3.
|
|
138
|
-
"cicy-code-linux-x64": "2.3.
|
|
139
|
-
"cicy-code-linux-arm64": "2.3.
|
|
140
|
-
"cicy-code-windows-x64": "2.3.
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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",
|