Files
geo/apps/desktop-client/src/main/lease-manager.ts
T
root 55e1c2e74b 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.
2026-04-20 17:16:15 +08:00

136 lines
3.7 KiB
TypeScript

interface LeaseSnapshot {
activeLeaseId: string | null;
activeTaskId: string | null;
activeTaskIds: string[];
activeCount: number;
attemptId: string | null;
startedAt: number | null;
leaseExpiresAt: number | null;
lastExtendedAt: number | null;
lastReleasedAt: number | null;
lastOutcome: "leased" | "extended" | "completed" | "failed" | "cleared" | null;
}
interface ActiveLeaseState {
taskId: string;
attemptId: string | null;
leaseExpiresAt: number | null;
startedAt: number;
lastExtendedAt: number | null;
}
const activeLeases = new Map<string, ActiveLeaseState>();
const leaseState: LeaseSnapshot = {
activeLeaseId: null,
activeTaskId: null,
activeTaskIds: [],
activeCount: 0,
attemptId: null,
startedAt: null,
leaseExpiresAt: null,
lastExtendedAt: null,
lastReleasedAt: null,
lastOutcome: null,
};
function normalizeLeaseExpiresAt(value: number | string | null | undefined): number | null {
if (typeof value === "number") {
return Number.isFinite(value) ? value : null;
}
if (typeof value !== "string" || !value.trim()) {
return null;
}
const timestamp = Date.parse(value);
return Number.isNaN(timestamp) ? null : timestamp;
}
export function setActiveLease(input: {
taskId: string;
attemptId?: string | null;
leaseExpiresAt?: number | string | null;
} | null): void {
if (!input) {
activeLeases.clear();
leaseState.activeLeaseId = null;
leaseState.activeTaskId = null;
leaseState.activeTaskIds = [];
leaseState.activeCount = 0;
leaseState.attemptId = null;
leaseState.startedAt = null;
leaseState.leaseExpiresAt = null;
leaseState.lastOutcome = "cleared";
leaseState.lastReleasedAt = Date.now();
return;
}
const normalized: ActiveLeaseState = {
taskId: input.taskId,
attemptId: input.attemptId ?? null,
leaseExpiresAt: normalizeLeaseExpiresAt(input.leaseExpiresAt),
startedAt: Date.now(),
lastExtendedAt: null,
};
activeLeases.set(normalized.taskId, normalized);
syncLeaseSnapshot();
leaseState.lastOutcome = "leased";
}
export function noteLeaseExtended(
leaseExpiresAt?: number | string | null,
taskId?: string,
): void {
if (taskId) {
const active = activeLeases.get(taskId);
if (active) {
active.leaseExpiresAt = normalizeLeaseExpiresAt(leaseExpiresAt) ?? active.leaseExpiresAt;
active.lastExtendedAt = Date.now();
activeLeases.set(taskId, active);
}
} else {
const first = activeLeases.values().next().value as ActiveLeaseState | undefined;
if (first) {
first.leaseExpiresAt = normalizeLeaseExpiresAt(leaseExpiresAt) ?? first.leaseExpiresAt;
first.lastExtendedAt = Date.now();
activeLeases.set(first.taskId, first);
}
}
syncLeaseSnapshot();
leaseState.lastExtendedAt = Date.now();
leaseState.lastOutcome = "extended";
}
export function noteLeaseReleased(
reason: Exclude<LeaseSnapshot["lastOutcome"], "leased" | "extended" | null>,
taskId?: string,
): void {
if (taskId) {
activeLeases.delete(taskId);
} else {
activeLeases.clear();
}
syncLeaseSnapshot();
leaseState.lastReleasedAt = Date.now();
leaseState.lastOutcome = reason;
}
export function getLeaseManagerSnapshot(): LeaseSnapshot {
return { ...leaseState };
}
function syncLeaseSnapshot(): void {
const first = activeLeases.values().next().value as ActiveLeaseState | undefined;
leaseState.activeTaskIds = [...activeLeases.keys()];
leaseState.activeCount = activeLeases.size;
leaseState.activeLeaseId = first?.taskId ?? null;
leaseState.activeTaskId = first?.taskId ?? null;
leaseState.attemptId = first?.attemptId ?? null;
leaseState.startedAt = first?.startedAt ?? null;
leaseState.leaseExpiresAt = first?.leaseExpiresAt ?? null;
}