diff --git a/qimingclaw/crates/agent-electron-client/src/main/ipc/digitalEmployeeHandlers.ts b/qimingclaw/crates/agent-electron-client/src/main/ipc/digitalEmployeeHandlers.ts index 5f2c4cf7..bd1d0c7d 100644 --- a/qimingclaw/crates/agent-electron-client/src/main/ipc/digitalEmployeeHandlers.ts +++ b/qimingclaw/crates/agent-electron-client/src/main/ipc/digitalEmployeeHandlers.ts @@ -40,7 +40,7 @@ export function registerDigitalEmployeeHandlers(ctx: HandlerContext): void { }); ipcMain.handle("digitalEmployee:flushSync", async () => { - return flushDigitalEmployeeSyncOutbox(); + return flushDigitalEmployeeSyncOutbox({ force: true }); }); } diff --git a/qimingclaw/crates/agent-electron-client/src/main/services/digitalEmployee/syncService.ts b/qimingclaw/crates/agent-electron-client/src/main/services/digitalEmployee/syncService.ts index 220b225b..ca5f1b7c 100644 --- a/qimingclaw/crates/agent-electron-client/src/main/services/digitalEmployee/syncService.ts +++ b/qimingclaw/crates/agent-electron-client/src/main/services/digitalEmployee/syncService.ts @@ -1,11 +1,14 @@ import log from "electron-log"; import { getDeviceId } from "../system/deviceId"; +import { getDomainTokenKey } from "@shared/utils/domain"; import { getDb, readSetting } from "../../db"; const DEFAULT_SYNC_PATH = "/api/digital-employee/sync/outbox"; const DEFAULT_INTERVAL_MS = 30_000; const DEFAULT_BATCH_SIZE = 50; const REQUEST_TIMEOUT_MS = 15_000; +const MAX_RETRY_DELAY_MS = 30 * 60_000; +const FAILED_RETRY_CANDIDATE_MULTIPLIER = 4; let syncTimer: ReturnType | null = null; let syncing = false; @@ -27,6 +30,7 @@ interface OutboxRow { operation: string; payload: string; attempts: number; + last_attempt_at?: string | null; } interface SyncAck { @@ -93,7 +97,7 @@ export function getDigitalEmployeeSyncStatus(): DigitalEmployeeSyncStatus { }; } -export async function flushDigitalEmployeeSyncOutbox(): Promise { +export async function flushDigitalEmployeeSyncOutbox(options: { force?: boolean } = {}): Promise { if (syncing) return getDigitalEmployeeSyncStatus(); const config = resolveSyncConfig(); @@ -102,7 +106,7 @@ export async function flushDigitalEmployeeSyncOutbox(): Promise= lastAttemptAt + retryDelayMs(row.attempts); +} + +function retryDelayMs(attempts: number): number { + const exponent = Math.max(0, Math.min(attempts - 1, 10)); + return Math.min(DEFAULT_INTERVAL_MS * 2 ** exponent, MAX_RETRY_DELAY_MS); } async function postOutbox( diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index f91749d6..fa5acdfe 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -439,10 +439,10 @@ crates/agent-electron-client/src/preload/webviewPerfBridge.ts ``` - 可通过 settings `digital_employee_sync_config.endpoint` 覆盖同步地址。 -- 使用 `auth.saved_key` 或域名级 saved key 作为客户端凭证。 -- 请求同时携带 `Authorization: Bearer ` 和 `X-Qiming-Client-Key`,兼容后端后续选择。 +- 使用 `auth.saved_key` 或域名级 saved key 作为客户端标识。 +- 使用管理端登录态 token 作为 `Authorization: Bearer `,同时携带 `X-Qiming-Client-Key`。 - 同步成功后回填 outbox 状态,并根据后端 ack 回填实体 `remote_id` / `last_synced_at`。 -- 同步失败时标记 failed 和错误信息,下轮继续补偿,不影响 `/computer/chat` 和 ACP 执行链路。 +- 同步失败时标记 failed 和错误信息;定时 worker 按指数退避补偿,手动 `flushSync` 可强制立即重试,不影响 `/computer/chat` 和 ACP 执行链路。 新增 IPC / Bridge: @@ -472,8 +472,7 @@ qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-a 下一步: - 在有 Maven 的环境验证 qiming-backend 编译与接口启动。 -- 数字员工页面读取 `getSyncStatus()`,展示联动状态。 -- 增加 outbox 重试退避策略,避免接口长期不可用时频繁请求。 +- 数字员工页面继续细化同步状态交互,例如手动同步按钮和失败原因展开。 ## 管理端联动原则