feat(desktop-client): queue account probes and report health to server

Replace serial probe-on-lock with a bounded probe queue (concurrency 3) that
adds a "queued" probe state, manual cooldown, and retry backoff. After each
probe the runtime debounces a batched POST to the new
/desktop/accounts/health-reports endpoint so the server learns about live,
expired, and risk transitions without waiting for a re-bind. Adds an
account-scoped IPC (probeRuntimeAccount + runtimeAccountSnapshot) so the
renderer can refresh a single row instead of replaying the full snapshot,
and exposes the resulting "重新校验" action in AccountsView/AiPlatformsView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 21:33:55 +08:00
parent 051976e4a9
commit c3feb7477a
15 changed files with 817 additions and 129 deletions
+6 -2
View File
@@ -92,10 +92,14 @@ const desktopBridge = {
ipcRenderer.invoke("desktop:client-id", scope) as Promise<string>,
runtimeSnapshot: () =>
ipcRenderer.invoke("desktop:runtime-snapshot") as Promise<DesktopRuntimeSnapshot>,
runtimeAccountSnapshot: (accountId: string) =>
ipcRenderer.invoke("desktop:runtime-account-snapshot", accountId) as Promise<DesktopRuntimeSnapshot["accounts"][number] | null>,
bindPublishAccount: (platformId: string) =>
ipcRenderer.invoke("desktop:bind-publish-account", platformId) as Promise<DesktopAccountInfo>,
refreshRuntimeAccounts: () =>
ipcRenderer.invoke("desktop:refresh-runtime-accounts") as Promise<null>,
probeRuntimeAccount: (accountId: string) =>
ipcRenderer.invoke("desktop:probe-runtime-account", accountId) as Promise<DesktopRuntimeSnapshot["accounts"][number] | null>,
openPublishAccountConsole: (account: {
id: string;
platform: string;
@@ -118,9 +122,9 @@ const desktopBridge = {
setWindowMode: (mode: "login" | "main") =>
ipcRenderer.invoke("desktop:set-window-mode", mode) as Promise<null>,
onRuntimeInvalidated: (
listener: (event: { reason: "account-health"; at: number }) => void,
listener: (event: { reason: "account-health"; at: number; accountId?: string }) => void,
) => {
const wrapped = (_event: IpcRendererEvent, payload: { reason: "account-health"; at: number }) => {
const wrapped = (_event: IpcRendererEvent, payload: { reason: "account-health"; at: number; accountId?: string }) => {
listener(payload);
};
ipcRenderer.on("desktop:runtime-invalidated", wrapped);