feat(monitoring): dispatch monitoring tasks to desktop via AMQP outbox

Replace SSE /desktop/events with priority AMQP dispatch for monitoring
runs, and add phase1/phase2 infrastructure so desktop workers can lease,
resume, report, skip, and cancel monitoring tasks over the existing
dispatch WebSocket.

- Add monitoring collect outbox worker and phase2 desktop task fields
- Add /desktop/monitoring/tasks/{lease,resume,result,skip,cancel} routes
- Introduce execution-devtools and network-observer on desktop runtime
- Refactor runtime-controller, LoginView, and doubao adapter for the new flow
- Add channelName column to tracking views

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 00:24:21 +08:00
parent 749b6b99cd
commit 4142c53fa6
57 changed files with 7897 additions and 985 deletions
+101 -15
View File
@@ -10,6 +10,14 @@ const defaultConnectTimeoutMs = 10_000;
const defaultPageTimeoutMs = 45_000;
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`;
interface CDPTargetDescriptor {
id?: string;
type?: string;
title?: string;
url?: string;
}
interface HiddenPlaywrightRecord {
key: string;
@@ -220,17 +228,22 @@ async function connectBrowserOverCDP(): Promise<PlaywrightBrowser> {
connectPromise = (async () => {
await waitForCDPEndpoint();
const { chromium } = await import("playwright-core");
const browser = await chromium.connectOverCDP(cdpEndpointURL, {
timeout: defaultConnectTimeoutMs,
});
await pruneStaleCDPTargets();
browser.on("disconnected", () => {
connectedBrowser = null;
connectPromise = null;
});
try {
return await openBrowserOverCDP(chromium);
} catch (error) {
if (!isConnectOverCDPTimeout(error)) {
throw error;
}
connectedBrowser = browser;
return browser;
const prunedTargetCount = await pruneStaleCDPTargets();
if (prunedTargetCount <= 0) {
throw error;
}
return openBrowserOverCDP(chromium);
}
})();
try {
@@ -260,6 +273,76 @@ async function waitForCDPEndpoint(timeoutMs = defaultConnectTimeoutMs): Promise<
throw new Error("playwright_cdp_endpoint_unavailable");
}
async function openBrowserOverCDP(
chromium: typeof import("playwright-core").chromium,
): Promise<PlaywrightBrowser> {
const browser = await chromium.connectOverCDP(cdpEndpointURL, {
timeout: defaultConnectTimeoutMs,
});
browser.on("disconnected", () => {
connectedBrowser = null;
connectPromise = null;
});
connectedBrowser = browser;
return browser;
}
async function pruneStaleCDPTargets(): Promise<number> {
let targets: CDPTargetDescriptor[] = [];
try {
const response = await fetch(cdpTargetsListURL);
if (!response.ok) {
return 0;
}
const payload = await response.json();
if (Array.isArray(payload)) {
targets = payload as CDPTargetDescriptor[];
}
} catch {
return 0;
}
let closedCount = 0;
for (const target of targets) {
if (!isStaleCDPTarget(target)) {
continue;
}
try {
const closeURL = `${cdpEndpointURL}/json/close/${encodeURIComponent(target.id ?? "")}`;
const response = await fetch(closeURL);
if (response.ok) {
closedCount += 1;
}
} catch {
// Ignore best-effort stale target cleanup failures.
}
}
if (closedCount > 0) {
await wait(150);
}
return closedCount;
}
function isStaleCDPTarget(target: CDPTargetDescriptor): boolean {
if ((target.type ?? "").trim() !== "page") {
return false;
}
if (!(target.id ?? "").trim()) {
return false;
}
return !(target.title ?? "").trim() && !(target.url ?? "").trim();
}
function isConnectOverCDPTimeout(error: unknown): boolean {
return error instanceof Error
&& error.name === "TimeoutError"
&& error.message.includes("connectOverCDP");
}
async function waitForNewPage(
browser: PlaywrightBrowser,
knownPages: Set<PlaywrightPage>,
@@ -270,6 +353,10 @@ async function waitForNewPage(
while (Date.now() - startedAt < defaultPageTimeoutMs) {
const nextPage = context.pages().find((page) => !knownPages.has(page));
if (nextPage) {
if (isDevtoolsPage(nextPage)) {
knownPages.add(nextPage);
continue;
}
return nextPage;
}
await wait(100);
@@ -298,13 +385,12 @@ function safePageURL(page: PlaywrightPage): string {
}
}
function closeRecord(record: HiddenPlaywrightRecord): void {
try {
record.page.close().catch(() => undefined);
} catch {
// Ignore and fall through to window close.
}
function isDevtoolsPage(page: PlaywrightPage): boolean {
const url = safePageURL(page);
return url.startsWith("devtools://");
}
function closeRecord(record: HiddenPlaywrightRecord): void {
if (!record.window.isDestroyed()) {
record.window.close();
}