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:
@@ -0,0 +1,102 @@
|
||||
import { aiPlatformCatalog } from "@geo/shared-types";
|
||||
|
||||
import {
|
||||
probePublishAccountSession,
|
||||
silentRefreshPublishAccountSession,
|
||||
type PublishAccountIdentity,
|
||||
} from "./account-binder";
|
||||
import type {
|
||||
AuthProbeResult,
|
||||
PlatformFailureClassification,
|
||||
} from "./auth-types";
|
||||
|
||||
export interface PlatformFailureInput {
|
||||
summary?: string | null;
|
||||
error?: Record<string, unknown> | null;
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformAdapter {
|
||||
probe(account: PublishAccountIdentity, partition: string): Promise<AuthProbeResult>;
|
||||
silentRefresh(account: PublishAccountIdentity, partition: string): Promise<boolean>;
|
||||
classifyFailure(input: PlatformFailureInput): PlatformFailureClassification;
|
||||
}
|
||||
|
||||
function normalizeFailureText(input: PlatformFailureInput): string {
|
||||
const values = [
|
||||
input.summary,
|
||||
input.message,
|
||||
typeof input.error?.code === "string" ? input.error.code : null,
|
||||
typeof input.error?.message === "string" ? input.error.message : null,
|
||||
typeof input.error?.detail === "string" ? input.error.detail : null,
|
||||
typeof input.error?.verify_scene === "string" ? input.error.verify_scene : null,
|
||||
];
|
||||
|
||||
return values
|
||||
.filter((value): value is string => typeof value === "string" && Boolean(value.trim()))
|
||||
.join(" | ")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function classifyFailure(input: PlatformFailureInput): PlatformFailureClassification {
|
||||
const text = normalizeFailureText(input);
|
||||
if (!text) {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
if (
|
||||
/(verify_scene|verification required|captcha|challenge|验证码|滑块|人机验证|安全验证|请完成验证|短信验证|扫码验证)/i.test(text)
|
||||
) {
|
||||
return "challenge";
|
||||
}
|
||||
|
||||
if (
|
||||
/(not_logged_in|unauthorized|unauthenticated|login redirect|login required|signin|sign in|expired|401|登录态失效|请先登录|未登录)/i
|
||||
.test(text)
|
||||
) {
|
||||
return "auth_failure";
|
||||
}
|
||||
|
||||
if (
|
||||
/(network|timeout|timed out|failed to fetch|econnreset|econnrefused|enotfound|dns|socket hang up|502|503|504)/i
|
||||
.test(text)
|
||||
) {
|
||||
return "network";
|
||||
}
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
const genericAdapter: PlatformAdapter = {
|
||||
async probe(account, partition) {
|
||||
return await probePublishAccountSession(account, partition);
|
||||
},
|
||||
async silentRefresh(account, partition) {
|
||||
return await silentRefreshPublishAccountSession(account, partition);
|
||||
},
|
||||
classifyFailure,
|
||||
};
|
||||
|
||||
const adapterRegistry = new Map<string, PlatformAdapter>(
|
||||
[
|
||||
...aiPlatformCatalog.map((platform) => platform.id),
|
||||
"toutiaohao",
|
||||
"baijiahao",
|
||||
"sohu",
|
||||
"qiehao",
|
||||
"zhihu",
|
||||
"wangyihao",
|
||||
"jianshu",
|
||||
"bilibili",
|
||||
"juejin",
|
||||
"smzdm",
|
||||
"weixin_gzh",
|
||||
"zol",
|
||||
"dongchedi",
|
||||
].map((platform) => [platform, genericAdapter]),
|
||||
);
|
||||
|
||||
export function getPlatformAdapter(platformId: string): PlatformAdapter {
|
||||
return adapterRegistry.get(platformId) ?? genericAdapter;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user