cicy-desktop 2.1.267 → 2.1.268

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.267",
3
+ "version": "2.1.268",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@ const os = require("os");
21
21
  const { z } = require("zod");
22
22
  const { app, BrowserWindow, BrowserView, webContents, ipcMain } = require("electron");
23
23
  const { attachContextMenu } = require("../utils/context-menu-options");
24
+ const { sendCDP } = require("../utils/cdp-utils");
24
25
 
25
26
  const SHELL_HTML = path.join(__dirname, "..", "tabbrowser", "tab-shell.html");
26
27
  const SHELL_PRELOAD = path.join(__dirname, "..", "tabbrowser", "tab-shell-preload.js");
@@ -246,6 +247,10 @@ class TabManager {
246
247
  } catch (e) {}
247
248
  }
248
249
  this.win = new BrowserWindow(winOpts);
250
+ try {
251
+ this.win.cicyAccountIdx = accountIdx;
252
+ this.win.webContents.cicyAccountIdx = accountIdx;
253
+ } catch (e) {}
249
254
  // profile 0 is the system tab window (teams only) — no manual "+" new tab.
250
255
  this.win.loadFile(SHELL_HTML, { query: { p: String(accountIdx), noNew: accountIdx === 0 ? "1" : "0", plat: process.platform } });
251
256
  managerByHost.set(this.win.webContents.id, this);
@@ -536,6 +541,33 @@ function findManagerByTab(webContentsId) {
536
541
  return null;
537
542
  }
538
543
 
544
+ function listTabsForAccount(accountIdx) {
545
+ const m = managers.get(accountIdx);
546
+ const known = new Map(m && !m.win.isDestroyed()
547
+ ? m.list().map((tab) => [tab.webContentsId, tab]) : []);
548
+ // Recover BrowserViews that survived a tool-module hot reload. The account
549
+ // tag is stamped in addTab and is also present on split-panel cells.
550
+ for (const wc of webContents.getAllWebContents()) {
551
+ if (wc.isDestroyed() || wc.cicyAccountIdx !== accountIdx || known.has(wc.id)) continue;
552
+ known.set(wc.id, {
553
+ webContentsId: wc.id, title: wc.getTitle(), url: wc.getURL(), active: false,
554
+ });
555
+ }
556
+ return [...known.values()];
557
+ }
558
+
559
+ function profileIdOfWebContents(wc) {
560
+ if (Number.isInteger(wc.cicyAccountIdx)) return wc.cicyAccountIdx;
561
+ try {
562
+ const url = new URL(wc.getURL());
563
+ if (url.pathname.endsWith("/tab-shell.html")) {
564
+ const id = Number(url.searchParams.get("p"));
565
+ if (Number.isInteger(id)) return id;
566
+ }
567
+ } catch (e) {}
568
+ return null;
569
+ }
570
+
539
571
  // ── programmatic API (team-open reroute / homepage) ──────────────────────────
540
572
  // profile 0 is the system tab window. It used to accept tabs ONLY from the
541
573
  // homepage (team open, systemOpen:true); that restriction is lifted so the "+"
@@ -679,6 +711,57 @@ async function tabScreenshot(webContentsId, format) {
679
711
  }
680
712
  }
681
713
 
714
+ async function tabSnapshot(webContentsId, maxElements = 20, showOverlays = false) {
715
+ const wc = webContents.fromId(webContentsId);
716
+ if (!wc) throw new Error(`tab ${webContentsId} not found`);
717
+ const result = await wc.executeJavaScript(`
718
+ (() => {
719
+ const info = (el) => {
720
+ const rect = el.getBoundingClientRect();
721
+ return {
722
+ tag: el.tagName.toLowerCase(),
723
+ text: (el.textContent || '').substring(0, 100).trim(),
724
+ x: Math.round(rect.x), y: Math.round(rect.y),
725
+ width: Math.round(rect.width), height: Math.round(rect.height)
726
+ };
727
+ };
728
+ const interactive = [];
729
+ document.querySelectorAll('a, button, input, select, textarea, [onclick], [role="button"], [role="link"]').forEach((el) => {
730
+ if (el.offsetParent !== null) interactive.push(info(el));
731
+ });
732
+ return {
733
+ url: location.href, title: document.title,
734
+ viewportWidth: innerWidth, viewportHeight: innerHeight,
735
+ scrollX, scrollY, interactive
736
+ };
737
+ })()
738
+ `, true);
739
+ if (showOverlays) {
740
+ await wc.executeJavaScript(`
741
+ (() => {
742
+ const elements = ${JSON.stringify(result.interactive)};
743
+ elements.forEach((el, i) => {
744
+ const overlay = document.createElement('div');
745
+ overlay.style.cssText = \`position:fixed;left:\${el.x}px;top:\${el.y}px;width:\${el.width}px;height:\${el.height}px;background:rgba(255,0,0,.2);border:2px solid red;z-index:999999;pointer-events:none;box-sizing:border-box\`;
746
+ const label = document.createElement('div');
747
+ label.textContent = i + 1;
748
+ label.style.cssText = 'position:absolute;top:2px;left:2px;background:red;color:white;padding:2px 6px;font-size:12px;font-weight:bold;font-family:monospace;line-height:1';
749
+ overlay.appendChild(label); document.body.appendChild(overlay);
750
+ setTimeout(() => overlay.remove(), 5000);
751
+ });
752
+ })()
753
+ `, true);
754
+ }
755
+ const shown = result.interactive.slice(0, maxElements || 20);
756
+ return "Page Snapshot\n"
757
+ + `url: ${result.url}\n`
758
+ + `title: ${result.title}\n`
759
+ + `viewport: ${result.viewportWidth}x${result.viewportHeight}\n`
760
+ + `scroll: (${result.scrollX}, ${result.scrollY})\n`
761
+ + `Interactive Elements (${result.interactive.length}):\n`
762
+ + shown.map((el, i) => `${i + 1}. [${el.tag}] ${el.text || "(no text)"} @ (${el.x}, ${el.y}) ${el.width}x${el.height}`).join("\n");
763
+ }
764
+
682
765
  function registerTabBrowserTools(registerTool) {
683
766
  const ok = (obj, isErr = false) => ({
684
767
  content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
@@ -724,8 +807,31 @@ function registerTabBrowserTools(registerTool) {
724
807
  z.object({ accountIdx: z.number().describe("账户索引(profile)") }),
725
808
  async ({ accountIdx }) => {
726
809
  try {
727
- const m = managers.get(accountIdx);
728
- return ok({ accountIdx, tabs: m && !m.win.isDestroyed() ? m.list() : [] });
810
+ return ok({ accountIdx, tabs: listTabsForAccount(accountIdx) });
811
+ } catch (e) { return ok({ error: e.message }, true); }
812
+ },
813
+ { tag: "TabBrowser" }
814
+ );
815
+
816
+ registerTool(
817
+ "electron_webcontents",
818
+ "列出 cicy-desktop 当前所有 WebContents(窗口、BrowserView 标签页、webview 等)。",
819
+ z.object({}),
820
+ async () => {
821
+ try {
822
+ const items = webContents.getAllWebContents()
823
+ .filter((wc) => !wc.isDestroyed())
824
+ .map((wc) => ({
825
+ webContentsId: wc.id,
826
+ url: wc.getURL(),
827
+ profileId: profileIdOfWebContents(wc),
828
+ type: wc.getType(),
829
+ title: wc.getTitle(),
830
+ loading: wc.isLoading(),
831
+ crashed: wc.isCrashed(),
832
+ }))
833
+ .sort((a, b) => a.webContentsId - b.webContentsId);
834
+ return ok(items);
729
835
  } catch (e) { return ok({ error: e.message }, true); }
730
836
  },
731
837
  { tag: "TabBrowser" }
@@ -761,6 +867,44 @@ function registerTabBrowserTools(registerTool) {
761
867
  { tag: "TabBrowser" }
762
868
  );
763
869
 
870
+ registerTool(
871
+ "electron_tab_cdp",
872
+ "对某个标签(按 webContentsId)发送任意 Chrome DevTools Protocol 命令。",
873
+ z.object({
874
+ webContentsId: z.number(),
875
+ method: z.string(),
876
+ params: z.record(z.any()).optional().default({}),
877
+ }),
878
+ async ({ webContentsId, method, params }) => {
879
+ try {
880
+ const wc = webContents.fromId(webContentsId);
881
+ if (!wc) throw new Error(`tab ${webContentsId} not found`);
882
+ const result = await sendCDP(wc, method, params);
883
+ return ok({ success: true, webContentsId, result });
884
+ } catch (e) { return ok({ error: e.message }, true); }
885
+ },
886
+ { tag: "TabBrowser" }
887
+ );
888
+
889
+ registerTool(
890
+ "electron_tab_snapshot",
891
+ "捕获某个标签(按 webContentsId)的结构快照和交互元素。",
892
+ z.object({
893
+ webContentsId: z.number(),
894
+ max_elements: z.number().optional().default(20),
895
+ show_overlays: z.boolean().optional().default(false),
896
+ }),
897
+ async ({ webContentsId, max_elements, show_overlays }) => {
898
+ try {
899
+ const text = await tabSnapshot(webContentsId, max_elements, show_overlays);
900
+ return { content: [{ type: "text", text }] };
901
+ } catch (e) {
902
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
903
+ }
904
+ },
905
+ { tag: "TabBrowser" }
906
+ );
907
+
764
908
  registerTool(
765
909
  "electron_tab_screenshot",
766
910
  "截取某个标签(按 webContentsId)。走 CDP,后台标签也能截。",