cicy-desktop 2.1.78 → 2.1.79

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.
Files changed (54) hide show
  1. package/bin/cicy-desktop +7 -7
  2. package/package.json +6 -6
  3. package/src/backends/homepage-preload.js +22 -0
  4. package/src/backends/homepage-react/assets/index-CKpaMBKz.css +1 -0
  5. package/src/backends/homepage-react/assets/index-CSsNZgC5.js +365 -0
  6. package/src/backends/homepage-react/index.html +2 -2
  7. package/src/backends/homepage-window.js +52 -7
  8. package/src/backends/ipc.js +57 -0
  9. package/src/backends/local-teams.js +73 -26
  10. package/src/backends/sidecar-ipc.js +11 -0
  11. package/src/backends/webview-preload.js +5 -3
  12. package/src/backends/window-manager.js +13 -3
  13. package/src/chrome/chrome-launcher.js +5 -4
  14. package/src/chrome/debugger-port-resolver.js +1 -1
  15. package/src/cloud/cloud-client.js +237 -41
  16. package/src/cluster/types.js +0 -5
  17. package/src/extension/inject.js +1 -1
  18. package/src/main.js +282 -88
  19. package/src/master/chrome-config.js +2 -2
  20. package/src/preload-rpc.js +1 -1
  21. package/src/profiles/profile-store.js +321 -0
  22. package/src/profiles/trusted-origins-store.js +95 -0
  23. package/src/server/worker-observability-routes.js +0 -2
  24. package/src/sidecar/cicy-code.js +84 -23
  25. package/src/sidecar/localbin.js +20 -3
  26. package/src/sidecar/native.js +3 -3
  27. package/src/sidecar/version.js +45 -0
  28. package/src/tabbrowser/newtab-protocol.js +54 -0
  29. package/src/tabbrowser/tab-browser.html +151 -0
  30. package/src/tabbrowser/tab-shell-preload.js +28 -0
  31. package/src/tabbrowser/tab-shell.html +227 -0
  32. package/src/tools/account-tools.js +191 -25
  33. package/src/tools/chrome-tools.js +173 -37
  34. package/src/tools/device-tools.js +25 -0
  35. package/src/tools/index.js +2 -0
  36. package/src/tools/tab-browser-tools.js +453 -0
  37. package/src/tools/window-tools.js +64 -7
  38. package/src/utils/brand-host-electron.js +25 -0
  39. package/src/utils/context-menu-options.js +80 -0
  40. package/src/utils/cookie-logins.js +58 -0
  41. package/src/utils/ip-probe.js +50 -0
  42. package/src/utils/rpc-audit.js +53 -0
  43. package/src/utils/rpc-guard.js +189 -0
  44. package/src/utils/window-monitor.js +5 -15
  45. package/src/utils/window-registry.js +210 -0
  46. package/src/utils/window-thumbnails.js +126 -0
  47. package/src/utils/window-utils.js +146 -109
  48. package/workers/render/package-lock.json +6 -6
  49. package/workers/render/src/App.css +36 -2
  50. package/workers/render/src/App.jsx +587 -103
  51. package/src/backends/artifact-ipc.js +0 -142
  52. package/src/backends/homepage-react/assets/index-DE9m6JTn.css +0 -1
  53. package/src/backends/homepage-react/assets/index-DLYMzgf5.js +0 -365
  54. package/src/cluster/artifact-registry.js +0 -61
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
7
7
  <link rel="icon" type="image/png" sizes="256x256" href="./favicon-256.png" />
8
8
  <title>CiCy Desktop</title>
9
- <script type="module" crossorigin src="./assets/index-DLYMzgf5.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-DE9m6JTn.css">
9
+ <script type="module" crossorigin src="./assets/index-CSsNZgC5.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-CKpaMBKz.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
@@ -11,15 +11,40 @@ const path = require("path");
11
11
  const { BrowserWindow } = require("electron");
12
12
  const log = require("electron-log");
13
13
 
14
+ // Fixed homepage window size (主人令: 写死 930*640, 不能 resize).
15
+ const FIXED_WIDTH = 930;
16
+ const FIXED_HEIGHT = 640;
17
+
14
18
  const LOCAL_INDEX = path.join(__dirname, "homepage-react", "index.html");
15
19
 
16
20
  function pickHomepageURL() {
17
21
  return `file://${LOCAL_INDEX}`;
18
22
  }
19
23
 
20
- let homepage = null;
24
+ let homepage = null; // standalone fallback window (only if the tab engine fails)
25
+ let homeTabWc = null; // the homepage's resident-tab webContents (primary path)
21
26
 
27
+ // Primary entry: the homepage is the RESIDENT first tab of profile 0's tab
28
+ // browser window (主人令: homepage = profile 0 的起始页). Clicking a team there
29
+ // opens the team as another tab in the same window. Falls back to a standalone
30
+ // window only if the tab engine throws, so the homepage is never unreachable.
22
31
  async function openHomepage() {
32
+ try {
33
+ const tabBrowser = require("../tools/tab-browser-tools");
34
+ const { win, wc } = tabBrowser.openHomeWindow(0, pickHomepageURL());
35
+ if (wc) {
36
+ homeTabWc = wc;
37
+ try { wc.once("destroyed", () => { if (homeTabWc === wc) homeTabWc = null; }); } catch {}
38
+ }
39
+ log.info(`[homepage] opened as profile-0 resident tab (wc=${wc && wc.id})`);
40
+ return win;
41
+ } catch (e) {
42
+ log.warn(`[homepage] tab route failed (${e.message}); using standalone window`);
43
+ return openHomepageStandalone();
44
+ }
45
+ }
46
+
47
+ async function openHomepageStandalone() {
23
48
  if (homepage && !homepage.isDestroyed()) {
24
49
  if (homepage.isMinimized()) homepage.restore();
25
50
  homepage.show();
@@ -27,10 +52,11 @@ async function openHomepage() {
27
52
  return homepage;
28
53
  }
29
54
  homepage = new BrowserWindow({
30
- width: 1320,
31
- height: 800,
32
- minWidth: 360,
33
- minHeight: 480,
55
+ width: FIXED_WIDTH,
56
+ height: FIXED_HEIGHT,
57
+ resizable: false,
58
+ maximizable: false,
59
+ fullscreenable: false,
34
60
  title: "CiCy Desktop",
35
61
  icon: require("../utils/app-icon").appIconPath(), // npx/unpackaged → set the
36
62
  // window+taskbar icon ourselves (no .exe to embed it on Windows).
@@ -90,7 +116,26 @@ async function openHomepage() {
90
116
  }
91
117
 
92
118
  function isOpen() {
93
- return !!(homepage && !homepage.isDestroyed());
119
+ return !!((homeTabWc && !homeTabWc.isDestroyed()) || (homepage && !homepage.isDestroyed()));
94
120
  }
95
121
 
96
- module.exports = { openHomepage, isOpen, getHomepageWindow: () => homepage };
122
+ // A stable shim that always resolves to the live homepage surface — the resident
123
+ // tab's webContents when present, else the standalone window's. Returned object
124
+ // identity is stable so callers that hold it across the async gap before the tab
125
+ // exists (appUpdater.init) still work once the tab loads. Used for main→renderer
126
+ // pushes: auth:complete (main.js), app:update-state (app-updater.js).
127
+ const homeWinShim = {
128
+ get webContents() {
129
+ if (homeTabWc && !homeTabWc.isDestroyed()) return homeTabWc;
130
+ if (homepage && !homepage.isDestroyed()) return homepage.webContents;
131
+ return null;
132
+ },
133
+ isDestroyed() {
134
+ const wc = this.webContents;
135
+ return !wc || (typeof wc.isDestroyed === "function" && wc.isDestroyed());
136
+ },
137
+ };
138
+
139
+ function getHomepageWindow() { return homeWinShim; }
140
+
141
+ module.exports = { openHomepage, isOpen, getHomepageWindow };
@@ -214,6 +214,63 @@ function register(opts = {}) {
214
214
  return { ok: true, ...readTos() };
215
215
  });
216
216
  ipcMain.handle("logs:tail", (_e, input) => tailLog(input || {}));
217
+
218
+ // ── Trusted origins (Chrome-style site-settings allowlist) ──────────────────
219
+ // The ONLY user-controlled source of "which sites may receive the electronRPC
220
+ // bridge (= run commands locally)". Every write refreshes the cached set in
221
+ // window-utils so isTrustedUrl() takes effect immediately (no restart).
222
+ ipcMain.handle("trustedOrigins:list", () => {
223
+ return require("../profiles/trusted-origins-store").listForUi();
224
+ });
225
+ ipcMain.handle("trustedOrigins:add", (_e, host) => {
226
+ const r = require("../profiles/trusted-origins-store").add(host);
227
+ try { require("../utils/window-utils").refreshTrustedOrigins(); } catch {}
228
+ return r;
229
+ });
230
+ ipcMain.handle("trustedOrigins:remove", (_e, host) => {
231
+ const r = require("../profiles/trusted-origins-store").remove(host);
232
+ try { require("../utils/window-utils").refreshTrustedOrigins(); } catch {}
233
+ return r;
234
+ });
235
+
236
+ // ── Open / reload a URL as a TAB in profile 0's tab browser ─────────────────
237
+ // Homepage cards (incl. cloud team cards) open the same way the local card does
238
+ // — a tab in the current profile (0) — instead of a new window / system browser.
239
+ ipcMain.handle("tabs:open", async (_e, input) => {
240
+ try {
241
+ const tb = require("../tools/tab-browser-tools");
242
+ const r = await tb.openTab(0, String((input && input.url) || ""), { systemOpen: true, trusted: false, title: (input && input.title) || "" });
243
+ return { ok: true, winId: r.winId, tabId: r.tabId };
244
+ } catch (e) { return { ok: false, error: String((e && e.message) || e) }; }
245
+ });
246
+ ipcMain.handle("tabs:reload", async (_e, input) => {
247
+ try {
248
+ const tb = require("../tools/tab-browser-tools");
249
+ return await tb.reloadTabByUrl(0, String((input && input.url) || ""), { title: (input && input.title) || "" });
250
+ } catch (e) { return { ok: false, error: String((e && e.message) || e) }; }
251
+ });
252
+
253
+ // ── RPC audit log (read-only viewer) ────────────────────────────────────────
254
+ // JSONL at ~/cicy-ai/db/rpc-audit.log (utils/rpc-audit.js): every electronRPC
255
+ // call + every authorization decision (incl. temporary ones) + allowlist edits.
256
+ // Returns the most recent `limit` entries newest-first; merges the rotated .1
257
+ // file when the live log is short. Read-only — the UI reviews, never mutates.
258
+ ipcMain.handle("rpcAudit:tail", (_e, input) => {
259
+ const limit = Math.max(1, Math.min(2000, Number((input && input.limit) || 300)));
260
+ try {
261
+ const { LOG } = require("../utils/rpc-audit");
262
+ const fs = require("fs");
263
+ const read = (p) => { try { return fs.readFileSync(p, "utf-8").split("\n").filter(Boolean); } catch { return []; } };
264
+ let lines = read(LOG);
265
+ if (lines.length < limit) lines = read(LOG + ".1").concat(lines); // older file first
266
+ const entries = [];
267
+ for (const ln of lines.slice(-limit)) { try { entries.push(JSON.parse(ln)); } catch {} }
268
+ entries.reverse(); // newest first
269
+ return { ok: true, entries, path: LOG };
270
+ } catch (e) {
271
+ return { ok: false, error: String((e && e.message) || e), entries: [] };
272
+ }
273
+ });
217
274
  }
218
275
 
219
276
  module.exports = { register, openHomepage };
@@ -116,11 +116,8 @@ function probeHealth(baseUrl, token) {
116
116
  res.setEncoding("utf8");
117
117
  res.on("data", (c) => { body += c; if (body.length > 8192) body = body.slice(0, 8192); });
118
118
  res.on("end", () => {
119
- let ver = null;
120
- try {
121
- const j = JSON.parse(body);
122
- ver = j?.version || j?.data?.version || null;
123
- } catch {}
119
+ // 版本解析唯一来源:require("../sidecar/version").parseHealthVersion
120
+ const ver = require("../sidecar/version").parseHealthVersion(body);
124
121
  resolve({
125
122
  ok: res.statusCode >= 200 && res.statusCode < 300,
126
123
  status: res.statusCode,
@@ -276,14 +273,27 @@ async function openTeam(id) {
276
273
  }
277
274
  }
278
275
 
279
- const { createWindow } = require("../utils/window-utils");
280
- const win = createWindow(
281
- { url, title: `Local · ${node.name || id}` },
282
- 0, // accountIdx local teams all share account 0's session partition
283
- true, // forceNew — we already determined no match above
284
- );
285
- log.info(`[local-teams] open ${id} new win.id=${win.id}`);
286
- return { ok: true, windowId: win.id, reused: false };
276
+ // Open the team as a TAB in account 0's tab browser (一个 profile 一个窗口,
277
+ // 不再每次弹新窗口). trusted=true → the tab gets the electronRPC bridge so the
278
+ // cicy-code SPA keeps working. Falls back to a real window on any failure so
279
+ // opening a team is never blocked.
280
+ try {
281
+ const tabBrowser = require("../tools/tab-browser-tools");
282
+ // tab name = the team's title (not the cicy-code SPA's document.title)
283
+ const r = await tabBrowser.openTab(0, url, { trusted: true, systemOpen: true, title: node.name || id });
284
+ log.info(`[local-teams] open ${id} → tab in win.id=${r.winId} (reused=${r.reused})`);
285
+ return { ok: true, windowId: r.winId, reused: !!r.reused, tabbed: true };
286
+ } catch (e) {
287
+ log.warn(`[local-teams] open ${id} → tab failed (${e.message}); falling back to window`);
288
+ const { createWindow } = require("../utils/window-utils");
289
+ const win = createWindow(
290
+ { url, title: `Local · ${node.name || id}` },
291
+ 0, // accountIdx — local teams all share account 0's session partition
292
+ true, // forceNew — we already determined no match above
293
+ );
294
+ log.info(`[local-teams] open ${id} → new win.id=${win.id}`);
295
+ return { ok: true, windowId: win.id, reused: false };
296
+ }
287
297
  }
288
298
 
289
299
  // Is this URL served by something on the local machine (the cicy-code sidecar)?
@@ -409,17 +419,49 @@ async function syncNameToCloud(id) {
409
419
  if (!cc.loginToken || !cc.loginToken()) return; // not logged in
410
420
  const node = readNodes()[id];
411
421
  if (!node || !isLocalOrigin(node.base_url || "")) return;
412
- const reg = await cc.registerTeam({ teamId: node.cloud_team_id || null, title: node.name || "" });
413
- if (reg && reg.ok && reg.teamId && reg.teamId !== node.cloud_team_id) {
414
- await writeNodes((nodes) => {
415
- if (nodes[id]) nodes[id].cloud_team_id = reg.teamId;
416
- return nodes;
417
- });
418
- log.info(`[local-teams] cloud name-sync ${id} teamId=${reg.teamId}`);
419
- } else if (reg && reg.ok) {
420
- log.info(`[local-teams] cloud name-sync ${id} title updated (teamId=${node.cloud_team_id})`);
422
+ let reg = await cc.registerTeam({ teamId: node.cloud_team_id || null, title: node.name || "", titleVersion: node.titleVersion || 0 });
423
+ // Self-heal a STALE cached cloud_team_id: if we presented a cached id but the
424
+ // cloud returned ok WITHOUT an apiKey (team deleted / rotated / no longer owned
425
+ // cloud-side — e.g. after a cloud wipe), the cached id is dead. Re-register with
426
+ // teamId=null to mint a FRESH team+key instead of silently leaving the gateway
427
+ // key empty (the "apiKey stays empty after a cloud wipe → requests 发不出去" bug).
428
+ // The teamId-changed branch below persists the new id back into teams.json.
429
+ if (reg && reg.ok && !reg.apiKey && node.cloud_team_id) {
430
+ log.warn(`[local-teams] cached cloud_team_id=${node.cloud_team_id} returned no gateway key — re-creating a fresh team`);
431
+ reg = await cc.registerTeam({ teamId: null, title: node.name || "", titleVersion: node.titleVersion || 0 });
432
+ }
433
+ // The cloud assigns this team a sk-cicy- gateway apiKey on register — wire
434
+ // it (full provider items + CLI routing, 主人 spec) into this machine's
435
+ // global.json so cicy-code has an LLM key from the moment it starts.
436
+ // Idempotent: injectGatewayKey no-ops when everything is already in place.
437
+ if (reg && reg.ok && reg.apiKey) {
438
+ try {
439
+ const inj = cc.injectGatewayKey(reg.apiKey, reg.gatewayUrl);
440
+ if (inj && inj.changed) log.info(`[local-teams] gateway key injected into global.json (teamId=${reg.teamId})`);
441
+ } catch (e) { log.warn(`[local-teams] gateway key injection failed: ${e.message}`); }
442
+ }
443
+ if (reg && reg.ok) {
444
+ // 服务端权威版本号裁决(w-10032 契约):响应版本 > 本地 → 采用响应的 title+version。
445
+ // 一条规则覆盖三种情况:(a) 云端/别处改名下行(reg.title=云端名,版本更大);
446
+ // (b) 本端改名被接受(reg.title=本端名,版本=base+1);(c) 冲突被拒(base 落后→
447
+ // reg.title=云端名,版本更大→云端赢)。相同名服务端不 bump→版本不变→不动。
448
+ const respVer = Number(reg.titleVersion) || 0;
449
+ const localVer = Number(node.titleVersion) || 0;
450
+ const adopt = respVer > localVer;
451
+ const teamIdChanged = reg.teamId && reg.teamId !== node.cloud_team_id;
452
+ if (teamIdChanged || adopt) {
453
+ await writeNodes((nodes) => {
454
+ if (nodes[id]) {
455
+ if (teamIdChanged) nodes[id].cloud_team_id = reg.teamId;
456
+ if (adopt) { if (reg.title) nodes[id].name = reg.title; nodes[id].titleVersion = respVer; }
457
+ }
458
+ return nodes;
459
+ });
460
+ }
461
+ if (adopt) log.info(`[local-teams] cloud title-sync ${id} ← "${reg.title}" v${respVer} (was v${localVer})`);
462
+ else if (teamIdChanged) log.info(`[local-teams] cloud title-sync ${id} → teamId=${reg.teamId}`);
421
463
  }
422
- } catch (e) { log.warn(`[local-teams] cloud name-sync ${id} failed: ${e.message}`); }
464
+ } catch (e) { log.warn(`[local-teams] cloud title-sync ${id} failed: ${e.message}`); }
423
465
  }
424
466
 
425
467
  // Sync EVERY existing local-origin team to cloud. Runs once at startup (after
@@ -568,17 +610,22 @@ async function updateTeam(id, patch) {
568
610
  }
569
611
 
570
612
  let existed = false;
613
+ const isRename = filtered.name !== undefined;
571
614
  await writeNodes((nodes) => {
572
615
  if (Object.prototype.hasOwnProperty.call(nodes, id)) {
573
616
  existed = true;
574
617
  nodes[id] = { ...nodes[id], ...filtered, updated_at: new Date().toISOString() };
618
+ // 改名:只改 name,titleVersion 保持「最后一次从云端看到的」作为 base 不动。
619
+ // syncNameToCloud 带这个 base 去注册;服务端接受后盖 base+1,响应回来再写回本地
620
+ // (服务端权威,w-10032 契约)。冲突(base 落后)则被拒、采用云端名,见 syncNameToCloud。
575
621
  }
576
622
  return nodes;
577
623
  });
578
624
  if (!existed) return { ok: false, error: "team not found" };
579
625
  log.info(`[local-teams] update ${id} → ${Object.keys(filtered).join(",")}`);
580
- // Rename → push the new title to the cloud (best-effort, one-way).
581
- if (filtered.name !== undefined) syncNameToCloud(id).catch(() => {});
626
+ // Rename → push the new title (with the base titleVersion) to the cloud, and
627
+ // pull down a newer cloud name if there is one (two-way LWW inside syncNameToCloud).
628
+ if (isRename) syncNameToCloud(id).catch(() => {});
582
629
  const next = readNodes()[id] || {};
583
630
  let port = null;
584
631
  try { port = parseInt(new URL(next.base_url || "").port, 10) || null; } catch {}
@@ -800,7 +847,7 @@ function fetchManifestVersion() {
800
847
  res.setEncoding("utf8");
801
848
  res.on("data", (c) => { body += c; if (body.length > 8192) body = body.slice(0, 8192); });
802
849
  res.on("end", () => {
803
- try { resolve(JSON.parse(body)?.version || null); }
850
+ try { resolve(require("../sidecar/version").parseHealthVersion(body)); }
804
851
  catch { resolve(null); }
805
852
  });
806
853
  });
@@ -28,6 +28,17 @@ function register({ sidecarLogPath } = {}) {
28
28
  return { running };
29
29
  });
30
30
 
31
+ // The ONE place the homepage gets cicy-code versions (主人令:"拿版本就一个方法").
32
+ // running → the live daemon's /api/health version ("正在跑什么"的唯一真相)
33
+ // latest → newest on npm (same number 更新 upgrades to)
34
+ // installed → on-disk binary (manifest)
35
+ // The card derives 更新可用 / 已是最新 from THESE — never from ad-hoc probes.
36
+ ipcMain.handle("sidecar:versions", async () => {
37
+ const version = require("../sidecar/version");
38
+ const [running, latest] = await Promise.all([version.running(PORT), version.latest()]);
39
+ return { running: running || null, latest: latest || null, installed: version.installed() || null };
40
+ });
41
+
31
42
  // ---- Windows Docker bootstrap (homepage's "no Docker" setup flow) ----
32
43
  // docker:status → what's missing; docker:bootstrap → install Docker (if
33
44
  // needed) + load image + start container, streaming progress back to the
@@ -30,9 +30,11 @@ const relay = (type, payload) =>
30
30
  // inside the helper agent's exec-js calls) call window.electronRPC("exec_shell",
31
31
  // {...}) etc. The Team Helper genuinely needs shell access to download +
32
32
  // install cicy-code on the user's machine, so we mirror the same bridge
33
- // here. Same channel as homepage-preload's "rpc" the existing tool
34
- // registry dispatches without any further wiring.
35
- const electronRPC = (tool, args) => ipcRenderer.invoke("rpc", tool, args || {});
33
+ // here. Routed through the GUARDED channel ("rpc:guarded") this is a remote
34
+ // third party, so dangerous tools (exec_*/file_*) prompt the user for a per-page
35
+ // grant before running (a trusted-origin XSS must not be silent RCE). Normal
36
+ // tools pass straight through. The homepage uses the unguarded "rpc" channel.
37
+ const electronRPC = (tool, args) => ipcRenderer.invoke("rpc:guarded", tool, args || {});
36
38
 
37
39
  const cicyApi = {
38
40
  platform: process.platform,
@@ -164,9 +164,19 @@ async function openWindowForBackend(backend, opts = {}) {
164
164
  }
165
165
 
166
166
  registry.markUsed(backend.id);
167
- // forceNew=true so each "Open" produces a new BrowserWindow even when
168
- // oneWindow mode is on the whole point of the launcher is multi-window.
169
- return createWindow({ url }, backendAccountIdx(backend), true);
167
+ const acct = 0; // all teams open as tabs in profile 0's tab window (主人令)
168
+ // Open as a TAB in profile 0's tab window (不弹新窗口). trusted=true so the
169
+ // cicy-code SPA gets its electronRPC bridge. Fallback to a real window on any
170
+ // failure so opening a backend is never blocked.
171
+ try {
172
+ const tabBrowser = require("../tools/tab-browser-tools");
173
+ const { BrowserWindow } = require("electron");
174
+ // tab name = the backend/team's title (not the SPA's document.title)
175
+ const r = await tabBrowser.openTab(acct, url, { trusted: true, systemOpen: true, title: backend.name || "" });
176
+ return BrowserWindow.fromId(r.winId) || createWindow({ url }, acct, true);
177
+ } catch (e) {
178
+ return createWindow({ url }, acct, true);
179
+ }
170
180
  }
171
181
 
172
182
  module.exports = { openWindowForBackend, buildLocalUrl, buildRemoteUrl, resolveBackendUrl, readCicyAiApiToken, backendAccountIdx };
@@ -9,7 +9,7 @@ const { config } = require("../config");
9
9
 
10
10
  // Default profile model: one user-data-dir per accountIdx
11
11
  // Directory layout:
12
- // ~/chrome/account_<idx>/Default/...
12
+ // ~/chrome/profile_<idx>/Default/...
13
13
  const DEFAULT_USER_DATA_BASE_ROOT = path.join(os.homedir(), "chrome");
14
14
  const DEFAULT_DEBUGGER_BASE_PORT = 9320;
15
15
 
@@ -18,11 +18,12 @@ function getProfileDirectory(_accountIdx) {
18
18
  }
19
19
 
20
20
  function getDefaultUserDataDirRoot(accountIdx, baseRoot = DEFAULT_USER_DATA_BASE_ROOT) {
21
- // If caller passes a concrete account dir already, respect it.
22
- if (typeof baseRoot === "string" && /account_\d+$/.test(baseRoot)) {
21
+ // If caller passes a concrete profile dir already, respect it.
22
+ // (account_<n> accepted for backward-compat with pre-rename dirs.)
23
+ if (typeof baseRoot === "string" && /(?:profile|account)_\d+$/.test(baseRoot)) {
23
24
  return baseRoot;
24
25
  }
25
- return path.join(baseRoot, `account_${accountIdx}`);
26
+ return path.join(baseRoot, `profile_${accountIdx}`);
26
27
  }
27
28
 
28
29
  function getDefaultDebuggerPort(accountIdx, basePort = DEFAULT_DEBUGGER_BASE_PORT) {
@@ -12,7 +12,7 @@ function readPrivateChromeConfig() {
12
12
  }
13
13
 
14
14
  function getConfiguredDebuggerPort(accountIdx, chromeConfig = readPrivateChromeConfig()) {
15
- const entry = chromeConfig?.[`account_${accountIdx}`];
15
+ const entry = chromeConfig?.[`profile_${accountIdx}`];
16
16
  return typeof entry?.port === "number" ? entry.port : null;
17
17
  }
18
18