feat(desktop): add Kimi and Yuanbao monitoring adapters
- implement kimi/yuanbao monitor adapters with dedicated citation panel detection - detect Kimi session via kimi-auth cookie during account binding - harden hidden Playwright and bound windows with skipTaskbar/focusable/hiddenInMissionControl to stop stealing focus - tag hidden bootstrap windows with a unique token so CDP retention resolves the correct page - enable yuanbao/kimi/wenxin platforms in dev-seed and default monitoring quota - update kimi loginUrl to www.kimi.com Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ const hiddenPlaywrightIdleTTLms = 5 * 60_000;
|
||||
const hiddenPlaywrightReaperIntervalMs = 60_000;
|
||||
const defaultConnectTimeoutMs = 10_000;
|
||||
const defaultPageTimeoutMs = 45_000;
|
||||
const hiddenBootstrapMetaName = "geo-hidden-playwright-token";
|
||||
const cdpPort = Number.parseInt(process.env.GEO_ELECTRON_CDP_PORT ?? "9339", 10);
|
||||
const cdpEndpointURL = `http://127.0.0.1:${cdpPort}`;
|
||||
const cdpTargetsListURL = `${cdpEndpointURL}/json/list`;
|
||||
@@ -79,8 +80,10 @@ export async function retainHiddenPlaywrightPage(options: {
|
||||
|
||||
const browser = await connectBrowserOverCDP();
|
||||
const knownPages = new Set(defaultContext(browser).pages());
|
||||
const window = createHiddenWindow(options.title, options.session);
|
||||
const page = await waitForNewPage(browser, knownPages);
|
||||
const bootstrapToken = createHiddenBootstrapToken();
|
||||
const bootstrapURL = buildHiddenWindowBootstrapURL(options.title, bootstrapToken);
|
||||
const window = createHiddenWindow(options.title, options.session, bootstrapURL);
|
||||
const page = await waitForNewPage(browser, knownPages, bootstrapToken);
|
||||
|
||||
page.setDefaultTimeout(defaultPageTimeoutMs);
|
||||
page.setDefaultNavigationTimeout(defaultPageTimeoutMs);
|
||||
@@ -181,11 +184,18 @@ function createLease(record: HiddenPlaywrightRecord): HiddenPlaywrightLease {
|
||||
};
|
||||
}
|
||||
|
||||
function createHiddenWindow(title: string, session: Session): ElectronBrowserWindow {
|
||||
function createHiddenWindow(
|
||||
title: string,
|
||||
session: Session,
|
||||
bootstrapURL: string,
|
||||
): ElectronBrowserWindow {
|
||||
const window = new BrowserWindow({
|
||||
show: false,
|
||||
width: 1320,
|
||||
height: 900,
|
||||
skipTaskbar: true,
|
||||
focusable: false,
|
||||
hiddenInMissionControl: true,
|
||||
autoHideMenuBar: true,
|
||||
backgroundColor: nativeTheme.shouldUseDarkColors ? "#15191a" : "#f4f1ea",
|
||||
title,
|
||||
@@ -199,10 +209,41 @@ function createHiddenWindow(title: string, session: Session): ElectronBrowserWin
|
||||
});
|
||||
|
||||
window.webContents.setUserAgent(STANDARD_USER_AGENT);
|
||||
void window.loadURL("about:blank");
|
||||
void window.loadURL(bootstrapURL);
|
||||
return window;
|
||||
}
|
||||
|
||||
function createHiddenBootstrapToken(): string {
|
||||
if (typeof globalThis.crypto?.randomUUID === "function") {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("\"", """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
function buildHiddenWindowBootstrapURL(title: string, token: string): string {
|
||||
const html = [
|
||||
"<!doctype html>",
|
||||
"<html lang=\"en\">",
|
||||
"<head>",
|
||||
" <meta charset=\"utf-8\">",
|
||||
` <meta name=\"${hiddenBootstrapMetaName}\" content=\"${escapeHtml(token)}\">`,
|
||||
` <title>${escapeHtml(title)} · hidden-playwright</title>`,
|
||||
"</head>",
|
||||
` <body data-${hiddenBootstrapMetaName}=\"${escapeHtml(token)}\"></body>`,
|
||||
"</html>",
|
||||
].join("");
|
||||
return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;
|
||||
}
|
||||
|
||||
async function ensureRecordTarget(record: HiddenPlaywrightRecord, targetURL: string): Promise<void> {
|
||||
record.targetURL = targetURL;
|
||||
|
||||
@@ -346,18 +387,21 @@ function isConnectOverCDPTimeout(error: unknown): boolean {
|
||||
async function waitForNewPage(
|
||||
browser: PlaywrightBrowser,
|
||||
knownPages: Set<PlaywrightPage>,
|
||||
bootstrapToken: string,
|
||||
): Promise<PlaywrightPage> {
|
||||
const context = defaultContext(browser);
|
||||
const startedAt = Date.now();
|
||||
|
||||
while (Date.now() - startedAt < defaultPageTimeoutMs) {
|
||||
const nextPage = context.pages().find((page) => !knownPages.has(page));
|
||||
if (nextPage) {
|
||||
const nextPages = context.pages().filter((page) => !knownPages.has(page));
|
||||
for (const nextPage of nextPages) {
|
||||
if (isDevtoolsPage(nextPage)) {
|
||||
knownPages.add(nextPage);
|
||||
continue;
|
||||
}
|
||||
return nextPage;
|
||||
if (await matchesHiddenBootstrapPage(nextPage, bootstrapToken)) {
|
||||
return nextPage;
|
||||
}
|
||||
}
|
||||
await wait(100);
|
||||
}
|
||||
@@ -365,6 +409,34 @@ async function waitForNewPage(
|
||||
throw new Error("playwright_hidden_page_unavailable");
|
||||
}
|
||||
|
||||
async function matchesHiddenBootstrapPage(
|
||||
page: PlaywrightPage,
|
||||
bootstrapToken: string,
|
||||
): Promise<boolean> {
|
||||
const url = safePageURL(page);
|
||||
if (url.includes(bootstrapToken) || url.includes(encodeURIComponent(bootstrapToken))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return await page.evaluate(
|
||||
({ token, metaName }) => {
|
||||
const metaToken =
|
||||
document.querySelector(`meta[name="${metaName}"]`)?.getAttribute("content")
|
||||
?? document.body?.getAttribute(`data-${metaName}`)
|
||||
?? null;
|
||||
return metaToken === token;
|
||||
},
|
||||
{
|
||||
token: bootstrapToken,
|
||||
metaName: hiddenBootstrapMetaName,
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function defaultContext(browser: PlaywrightBrowser) {
|
||||
const context = browser.contexts()[0];
|
||||
if (!context) {
|
||||
|
||||
Reference in New Issue
Block a user