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
@@ -11,6 +11,7 @@ const error = shallowRef<string | null>(null);
let intervalHandle: ReturnType<typeof setInterval> | null = null;
let subscribers = 0;
let visibilityListenerBound = false;
let runtimeInvalidationUnsubscribe: (() => void) | null = null;
function isPageVisible(): boolean {
return typeof document === "undefined" || document.visibilityState === "visible";
@@ -95,6 +96,18 @@ function bindVisibilityListener() {
visibilityListenerBound = true;
}
function bindRuntimeInvalidationListener() {
if (runtimeInvalidationUnsubscribe || !window.desktopBridge?.app?.onRuntimeInvalidated) {
return;
}
runtimeInvalidationUnsubscribe = window.desktopBridge.app.onRuntimeInvalidated(() => {
if (isPageVisible()) {
void refreshRuntimeSnapshot();
}
});
}
function unbindVisibilityListener() {
if (!visibilityListenerBound || typeof document === "undefined") {
return;
@@ -104,10 +117,16 @@ function unbindVisibilityListener() {
visibilityListenerBound = false;
}
function unbindRuntimeInvalidationListener() {
runtimeInvalidationUnsubscribe?.();
runtimeInvalidationUnsubscribe = null;
}
export function useDesktopRuntime() {
onMounted(() => {
subscribers += 1;
bindVisibilityListener();
bindRuntimeInvalidationListener();
if (!snapshot.value && !loading.value) {
void refreshRuntimeSnapshot();
}
@@ -119,6 +138,7 @@ export function useDesktopRuntime() {
if (subscribers === 0) {
stopPolling();
unbindVisibilityListener();
unbindRuntimeInvalidationListener();
return;
}