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 +1 -1
- package/src/tools/tab-browser-tools.js +146 -2
package/package.json
CHANGED
|
@@ -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
|
-
|
|
728
|
-
|
|
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,后台标签也能截。",
|