feat(desktop): add monitor scheduler, Playwright CDP layer, and account health

Move monitor-task scheduling authority onto the client with a durable
file-backed queue that survives restart, drops stale cross-day tasks,
enforces per-platform serialism, and adapts global concurrency from
Electron process metrics. Publish tasks keep their existing FIFO.

Add a hidden Playwright CDP manager that attaches to Electron Chromium
on account session partitions, lets adapters opt into `executionMode:
"playwright"`, and leaves the existing hidden WebContentsView path in
place for current adapters.

Introduce an account-health subsystem with silent probes, projected
health/auth states, and IPC invalidation events so the renderer can
show accurate auth/probe status and verification timestamps.

Server-side, derive and forward title/business_date/scheduler_group_key/
question_text metadata on desktop task events so the local scheduler
can defer same-question fan-out before leasing.
This commit is contained in:
2026-04-20 17:16:15 +08:00
parent 07881a66d0
commit 55e1c2e74b
26 changed files with 3001 additions and 153 deletions
+20
View File
@@ -4,13 +4,17 @@ import { join } from "node:path";
import { BrowserWindow, WebContentsView, app, ipcMain, nativeTheme } from "electron/main";
import type {
BrowserWindow as ElectronBrowserWindow,
WebContents as ElectronWebContents,
WebContentsView as ElectronWebContentsView,
} from "electron/main";
import type { DesktopAccountInfo, DesktopRuntimeSessionSyncRequest } from "@geo/shared-types";
import { initAccountHealth } from "./account-health";
import { bindPublishAccount, openPublishAccountConsole } from "./account-binder";
import { initProcessMetricsSampler } from "./process-metrics";
import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from "./playwright-cdp";
import { registerRendererDevtoolsProxyTarget } from "./renderer-devtools-proxy";
import { onRuntimeInvalidated } from "./runtime-events";
import {
refreshRuntimeAccounts,
releaseRuntimeSession,
@@ -32,6 +36,7 @@ app.commandLine.appendSwitch(
);
app.commandLine.appendSwitch("disable-quic");
app.commandLine.appendSwitch("disable-background-networking");
app.commandLine.appendSwitch("remote-debugging-port", String(getPlaywrightCDPPort()));
app.userAgentFallback = STANDARD_USER_AGENT;
console.info("[desktop-main] boot", {
@@ -50,6 +55,7 @@ process.on("uncaughtException", (error) => {
});
let mainWindow: ElectronBrowserWindow | null = null;
let mainRendererContents: ElectronWebContents | null = null;
let quitReleaseInFlight = false;
function rendererURL(): string | null {
@@ -77,9 +83,15 @@ async function mountRendererView(window: ElectronBrowserWindow): Promise<void> {
view.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
registerRendererDevtoolsProxyTarget(view.webContents);
mainRendererContents = view.webContents;
window.contentView.addChildView(view);
syncViewBounds(window, view);
window.on("resize", () => syncViewBounds(window, view));
window.on("closed", () => {
if (mainRendererContents === view.webContents) {
mainRendererContents = null;
}
});
const devServerURL = rendererURL();
if (devServerURL) {
@@ -189,10 +201,18 @@ if (!initSingleInstance(() => {
app.whenReady().then(async () => {
registerBridgeHandlers();
await initSessionRegistry();
initAccountHealth();
initTransport();
initScheduler();
initProcessMetricsSampler();
startHotViewReaper();
startHiddenPlaywrightReaper();
onRuntimeInvalidated((event) => {
if (!mainRendererContents || mainRendererContents.isDestroyed()) {
return;
}
mainRendererContents.send("desktop:runtime-invalidated", event);
});
mainWindow = await createMainWindow();
initTray(() => {
if (!mainWindow) {