import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockState = vi.hoisted(() => ({ db: null as TestDb | null, ensureSchemaCalls: 0, settings: new Map(), fetch: vi.fn(), })); vi.mock("electron-log", () => ({ default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), }, })); vi.mock("../system/deviceId", () => ({ getDeviceId: vi.fn(() => "device-001"), })); vi.mock("../../db", () => ({ ensureDigitalEmployeeSchema: () => { mockState.ensureSchemaCalls += 1; return true; }, getDb: () => mockState.db, readSetting: (key: string) => mockState.settings.get(key) ?? null, writeSetting: (key: string, value: unknown) => mockState.settings.set(key, value), })); describe("digital employee sync service", () => { beforeEach(() => { vi.resetModules(); vi.useFakeTimers(); vi.setSystemTime(new Date("2026-06-07T08:00:00.000Z")); mockState.fetch = vi.fn(); mockState.ensureSchemaCalls = 0; vi.stubGlobal("fetch", mockState.fetch); mockState.settings = new Map([ ["step1_config", { serverHost: "https://manage.example.com" }], ["auth.username", "operator"], ["auth.saved_key", "client-key-001"], ["auth.tokens.manage.example.com", "login-token-001"], ]); mockState.db = new TestDb(); }); afterEach(() => { mockState.db = null; vi.unstubAllGlobals(); vi.useRealTimers(); }); it("skips flushing when management credentials are incomplete", async () => { mockState.settings.delete("auth.saved_key"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(mockState.fetch).not.toHaveBeenCalled(); expect(status.missingCredentials).toEqual(["客户端 Key"]); expect(status.pending).toBe(1); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "pending", attempts: 0, }); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("pulls remote plan step dispatch policy and records policy state", async () => { mockState.fetch.mockResolvedValue( jsonResponse({ success: true, data: { plan_step_dispatch_policy: { enabled: true, max_per_sweep: 5, auto_dispatch_ready_steps: false, updated_at: "2026-06-07T08:01:00.000Z", }, }, }), ); const { pullDigitalEmployeePlanStepDispatchPolicy } = await import("./syncService"); const { readDigitalEmployeeUiState } = await import("./stateService"); const result = await pullDigitalEmployeePlanStepDispatchPolicy({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/policies/plan-step-dispatch", policy: { source: "remote", enabled: true, max_per_sweep: 5, auto_dispatch_ready_steps: false, remote_updated_at: "2026-06-07T08:01:00.000Z", last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: null, }, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/policies/plan-step-dispatch", expect.objectContaining({ method: "GET" }), ); expect(readDigitalEmployeeUiState().planStepDispatchPolicy).toMatchObject(result.policy); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_pull_plan_step_dispatch_policy" }), ])); }); it("keeps the current plan step dispatch policy when remote policy pull fails", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "policy service unavailable" })), } as unknown as Response); const { pullDigitalEmployeePlanStepDispatchPolicy } = await import("./syncService"); const result = await pullDigitalEmployeePlanStepDispatchPolicy({ force: true }); expect(result).toMatchObject({ ok: false, error: "policy service unavailable", policy: { source: "local", enabled: true, max_per_sweep: 3, auto_dispatch_ready_steps: true, last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: "policy service unavailable", }, }); }); it("pulls remote route decision policy and records policy state", async () => { mockState.fetch.mockResolvedValue( jsonResponse({ success: true, data: { routeDecisionPolicy: { enabled: true, autoCreateGovernanceCommands: false, approvalRequiredIntents: ["Execute"], blockedIntents: ["Schedule"], preferredRouteKinds: ["PlanExecution"], updatedAt: "2026-06-07T08:02:00.000Z", }, }, }), ); const { pullDigitalEmployeeRouteDecisionPolicy } = await import("./syncService"); const { readDigitalEmployeeUiState } = await import("./stateService"); const result = await pullDigitalEmployeeRouteDecisionPolicy({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/policies/route-decision", policy: { source: "remote", enabled: true, auto_create_governance_commands: false, approval_required_intents: ["execute"], blocked_intents: ["schedule"], preferred_route_kinds: ["PlanExecution"], remote_updated_at: "2026-06-07T08:02:00.000Z", last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: null, }, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/policies/route-decision", expect.objectContaining({ method: "GET" }), ); expect(readDigitalEmployeeUiState().routeDecisionPolicy).toMatchObject(result.policy); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_pull_route_decision_policy" }), ])); }); it("keeps the current route decision policy when remote policy pull fails", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "route policy unavailable" })), } as unknown as Response); const { pullDigitalEmployeeRouteDecisionPolicy } = await import("./syncService"); const result = await pullDigitalEmployeeRouteDecisionPolicy({ force: true }); expect(result).toMatchObject({ ok: false, error: "route policy unavailable", policy: { source: "local", enabled: true, auto_create_governance_commands: true, blocked_intents: [], approval_required_intents: [], last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: "route policy unavailable", }, }); }); it("pulls remote skill policies and preserves local skill preferences", async () => { mockState.settings.set("digital_employee_skill_policies_v1", { version: 1, local_skills: { "qimingclaw-acp-session": { enabled: false, updatedAt: "2026-06-07T07:00:00.000Z", source: "local" }, }, }); mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { skill_policies: { updated_at: "2026-06-07T08:01:00.000Z", skills: { "qimingclaw-mcp-tool-crm-crm-delete": { enabled: false, updated_at: "2026-06-07T08:01:00.000Z", reason: "management_disabled", }, }, }, }, })); const { pullDigitalEmployeeSkillPolicies } = await import("./syncService"); const result = await pullDigitalEmployeeSkillPolicies({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/policies/skills", policies: { source: "remote", remote_updated_at: "2026-06-07T08:01:00.000Z", last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: null, local_skills: { "qimingclaw-acp-session": expect.objectContaining({ enabled: false, source: "local" }), }, remote_skills: { "qimingclaw-mcp-tool-crm-crm-delete": expect.objectContaining({ enabled: false, source: "remote", reason: "management_disabled" }), }, }, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/policies/skills", expect.objectContaining({ method: "GET" }), ); expect(mockState.settings.get("digital_employee_skill_policies_v1")).toMatchObject(result.policies); }); it("keeps current skill policies when remote skill policy pull fails", async () => { mockState.settings.set("digital_employee_skill_policies_v1", { version: 1, remote_skills: { "qimingclaw-computer-control": { enabled: false, updatedAt: "2026-06-07T07:30:00.000Z", source: "remote" }, }, }); mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "skill policy service unavailable" })), } as unknown as Response); const { pullDigitalEmployeeSkillPolicies } = await import("./syncService"); const result = await pullDigitalEmployeeSkillPolicies({ force: true }); expect(result).toMatchObject({ ok: false, error: "skill policy service unavailable", policies: { remote_skills: { "qimingclaw-computer-control": expect.objectContaining({ enabled: false, source: "remote" }), }, last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: "skill policy service unavailable", }, }); }); it("pulls remote risk approval policy and records UI state", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { risk_approval_policy: { enabled: true, approval_required_risk_levels: ["critical"], approval_required_actions: ["governance.run_schedule_now"], auto_reject_actions: ["artifact.access.open_location"], updated_at: "2026-06-07T08:02:00.000Z", }, }, })); const { pullDigitalEmployeeRiskApprovalPolicy } = await import("./syncService"); const { readDigitalEmployeeUiState } = await import("./stateService"); const result = await pullDigitalEmployeeRiskApprovalPolicy({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/policies/risk-approval", policy: { source: "remote", approval_required_risk_levels: ["critical"], approval_required_actions: ["governance.run_schedule_now"], auto_reject_actions: ["artifact.access.open_location"], remote_updated_at: "2026-06-07T08:02:00.000Z", last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: null, }, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/policies/risk-approval", expect.objectContaining({ method: "GET" }), ); expect(readDigitalEmployeeUiState().riskApprovalPolicy).toMatchObject(result.policy); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_pull_risk_approval_policy" }), ])); }); it("keeps current risk approval policy when pull fails", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "risk policy unavailable" })), } as unknown as Response); const { pullDigitalEmployeeRiskApprovalPolicy } = await import("./syncService"); const result = await pullDigitalEmployeeRiskApprovalPolicy({ force: true }); expect(result).toMatchObject({ ok: false, error: "risk policy unavailable", policy: { source: "local", approval_required_risk_levels: ["high", "critical"], last_pulled_at: "2026-06-07T08:00:00.000Z", last_pull_error: "risk policy unavailable", }, }); }); it.each([ ["riskApprovalPolicy"], ["policy"], ])("pulls remote risk approval policy from data.%s", async (fieldName) => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { [fieldName]: { approvalRequiredActions: ["managed_service.restart.fileServer"], }, }, })); const { pullDigitalEmployeeRiskApprovalPolicy } = await import("./syncService"); const result = await pullDigitalEmployeeRiskApprovalPolicy({ force: true }); expect(result.policy.approval_required_actions).toEqual(["managed_service.restart.fileServer"]); }); it("pulls remote approval updates into local approval workflow records", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { approvals: [ { approval_id: "approval-remote-1", title: "高风险动作审批", status: "pending", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", updated_at: "2026-06-07T08:03:00.000Z", payload: { workflow: { workflow_id: "workflow-remote-1", current_step_id: "step-2", steps: [ { step_id: "step-1", label: "主管审批", status: "approved" }, { step_id: "step-2", label: "风控复核", status: "pending", participant_label: "风控" }, ], }, }, }, ], }, })); const { pullDigitalEmployeeApprovalUpdates } = await import("./syncService"); const result = await pullDigitalEmployeeApprovalUpdates({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/approvals/updates", applied: 1, ignored: 0, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/approvals/updates", expect.objectContaining({ method: "GET" }), ); const approval = mockState.db?.approvals.get("approval-remote-1"); expect(approval).toMatchObject({ title: "高风险动作审批", status: "pending", sync_status: "pending" }); expect(JSON.parse(approval?.payload ?? "{}")).toMatchObject({ workflow: { workflow_id: "workflow-remote-1", current_step_id: "step-2", }, remote_updated_at: "2026-06-07T08:03:00.000Z", }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_approval_updates_pulled" }), ])); }); it("counts conflicted remote approval updates and records the pull summary", async () => { mockState.db?.approvals.set("approval-conflict-1", { id: "approval-conflict-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", title: "本地已同意审批", status: "approved", payload: JSON.stringify({ workflow: { current_step_id: "step-1", decision_history: [{ decision: "approved" }] } }), remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, created_at: "2026-06-07T07:50:00.000Z", updated_at: "2026-06-07T08:05:00.000Z", }); mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { approvals: [ { approval_id: "approval-conflict-1", title: "本地已同意审批", status: "rejected", updated_at: "2026-06-07T08:06:00.000Z", payload: { workflow: { current_step_id: "step-1", decision_history: [{ decision: "rejected" }] } }, }, { approval_id: "approval-remote-ok", title: "新审批", status: "pending", updated_at: "2026-06-07T08:04:00.000Z", }, ], }, })); const { pullDigitalEmployeeApprovalUpdates } = await import("./syncService"); const result = await pullDigitalEmployeeApprovalUpdates({ force: true }); expect(result).toMatchObject({ ok: true, applied: 1, ignored: 0, conflicted: 1 }); expect(mockState.db?.approvals.get("approval-conflict-1")).toMatchObject({ status: "approved" }); const pulledEvent = Array.from(mockState.db?.events.values() ?? []) .find((event) => event.kind === "digital_workday_approval_updates_pulled"); expect(JSON.parse(pulledEvent?.payload ?? "{}")).toMatchObject({ metadata: { applied: 1, ignored: 0, conflicted: 1, }, }); }); it("submits approval decisions to management and records submission events", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { accepted: true } })); const { submitDigitalEmployeeApprovalDecision } = await import("./syncService"); const result = await submitDigitalEmployeeApprovalDecision({ approval_id: "approval-1", decision: "approved" }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/approvals/decisions", submitted_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/approvals/decisions", expect.objectContaining({ method: "POST", body: expect.stringContaining("approval-1"), }), ); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_approval_decision_submitted" }), ])); }); it("pulls remote notification updates into local notification overlays", async () => { mockState.settings.set("digital_employee_ui_state_v1", { notificationStateById: { "task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" }, }, }); mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { notifications: [ { notification_id: "task:need-input:1", status: "read", read_at: "2026-06-07T08:01:00.000Z", remote_updated_at: "2026-06-07T08:02:00.000Z", source: "management-inbox", }, { notificationId: "task:reply:1", status: "resolved", reply: "已补充客户编号", repliedAt: "2026-06-07T08:03:00.000Z", }, ], }, })); const { pullDigitalEmployeeNotificationUpdates } = await import("./syncService"); const { readDigitalEmployeeUiState } = await import("./stateService"); const result = await pullDigitalEmployeeNotificationUpdates({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/notifications/updates", applied: 2, ignored: 0, pulled_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/notifications/updates", expect.objectContaining({ method: "GET" }), ); expect(readDigitalEmployeeUiState().notificationStateById).toMatchObject({ "task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" }, "task:need-input:1": { status: "read", read_at: "2026-06-07T08:01:00.000Z", remote_updated_at: "2026-06-07T08:02:00.000Z", source: "management-inbox", }, "task:reply:1": { status: "resolved", reply: "已补充客户编号", replied_at: "2026-06-07T08:03:00.000Z", source: "management-api", }, }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_notification_updates_pulled" }), ])); }); it("keeps local notification overlays when remote notification update pull fails", async () => { mockState.settings.set("digital_employee_ui_state_v1", { notificationStateById: { "task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" }, }, }); mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "notification service unavailable" })), } as unknown as Response); const { pullDigitalEmployeeNotificationUpdates } = await import("./syncService"); const { readDigitalEmployeeUiState } = await import("./stateService"); const result = await pullDigitalEmployeeNotificationUpdates({ force: true }); expect(result).toMatchObject({ ok: false, endpoint: "https://manage.example.com/api/digital-employee/notifications/updates", applied: 0, ignored: 0, error: "notification service unavailable", }); expect(readDigitalEmployeeUiState().notificationStateById).toMatchObject({ "task:existing": { status: "read", read_at: "2026-06-07T07:30:00.000Z" }, }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_notification_updates_pull_failed" }), ])); }); it("submits notification actions to management and records submission events", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { accepted: true } })); const { submitDigitalEmployeeNotificationAction } = await import("./syncService"); const result = await submitDigitalEmployeeNotificationAction({ notification_id: "task:need-input:1", action: "reply", status: "read", reply: "已补充客户编号", replied_at: "2026-06-07T08:03:00.000Z", }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/notifications/actions", submitted_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/notifications/actions", expect.objectContaining({ method: "POST", body: expect.stringContaining("task:need-input:1"), }), ); expect(lastSyncRequestBody()).toMatchObject({ notification_id: "task:need-input:1", action: "reply", status: "read", reply: "已补充客户编号", submitted_at: "2026-06-07T08:00:00.000Z", device_id: "device-001", }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_notification_action_submitted" }), ])); }); it("records notification action submit failures without throwing", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 500, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "notification action rejected" })), } as unknown as Response); const { submitDigitalEmployeeNotificationAction } = await import("./syncService"); const result = await submitDigitalEmployeeNotificationAction({ notification_id: "task:need-input:1", action: "read" }); expect(result).toMatchObject({ ok: false, endpoint: "https://manage.example.com/api/digital-employee/notifications/actions", submitted_at: "2026-06-07T08:00:00.000Z", error: "notification action rejected", }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "digital_workday_notification_action_submit_failed" }), ])); }); it("pulls low-risk admin actions, applies them locally, and reports results", async () => { mockState.fetch .mockResolvedValueOnce(jsonResponse({ success: true, data: { records: [ { id: 10, entity_type: "artifact", entity_id: "artifact-1", action: "mark_expire", request_payload: { reason: "管理端标记到期" }, }, ], }, })) .mockResolvedValueOnce(jsonResponse({ success: true, data: { ok: true } })); const { pullAndApplyDigitalEmployeeAdminActions } = await import("./syncService"); const result = await pullAndApplyDigitalEmployeeAdminActions({ force: true }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/admin/actions/pending?device_id=device-001", applied: 1, rejected: 0, failed: 0, ignored: 0, }); expect(mockState.fetch).toHaveBeenNthCalledWith( 1, "https://manage.example.com/api/digital-employee/admin/actions/pending?device_id=device-001", expect.objectContaining({ method: "GET" }), ); expect(mockState.fetch).toHaveBeenNthCalledWith( 2, "https://manage.example.com/api/digital-employee/admin/actions/10/result", expect.objectContaining({ method: "POST", body: expect.stringContaining("admin_action_applied") }), ); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "artifact_retention_marked" }), expect.objectContaining({ kind: "admin_action_applied" }), ])); }); it("rejects high-risk admin actions without changing local facts", async () => { mockState.fetch .mockResolvedValueOnce(jsonResponse({ success: true, data: { records: [{ id: 11, entity_type: "artifact", entity_id: "artifact-1", action: "cleanup_file_batch" }] }, })) .mockResolvedValueOnce(jsonResponse({ success: true, data: { ok: true } })); const { pullAndApplyDigitalEmployeeAdminActions } = await import("./syncService"); const result = await pullAndApplyDigitalEmployeeAdminActions({ force: true }); expect(result).toMatchObject({ ok: true, applied: 0, rejected: 1, failed: 0 }); expect(mockState.fetch).toHaveBeenNthCalledWith( 2, "https://manage.example.com/api/digital-employee/admin/actions/11/result", expect.objectContaining({ method: "POST", body: expect.stringContaining("admin_action_rejected") }), ); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "admin_action_rejected" }), ])); }); it.each([ ["skillPolicies"], ["policies"], ])("pulls remote skill policies from data.%s", async (fieldName) => { mockState.fetch.mockResolvedValue(jsonResponse({ success: true, data: { [fieldName]: { "qimingclaw-acp-session": false, }, }, })); const { pullDigitalEmployeeSkillPolicies } = await import("./syncService"); const result = await pullDigitalEmployeeSkillPolicies({ force: true }); expect(result.policies.remote_skills["qimingclaw-acp-session"]).toEqual(expect.objectContaining({ enabled: false, source: "remote", })); }); it("acquires a remote plan step dispatch lease through the management endpoint", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ ok: true, lease: { lease_id: "remote-lease-1", owner_device_id: "device-001", acquired_at: "2026-06-07T08:00:00.000Z", expires_at: "2026-06-07T08:05:00.000Z", }, })); const { acquireDigitalEmployeePlanStepRemoteLease } = await import("./syncService"); const result = await acquireDigitalEmployeePlanStepRemoteLease({ planId: "plan-1", stepId: "step-1", taskId: "task-1", triggerSource: "auto", ttlMs: 300_000, occurredAt: "2026-06-07T08:00:00.000Z", }); expect(result).toMatchObject({ ok: true, endpoint: "https://manage.example.com/api/digital-employee/leases/plan-step-dispatch/acquire", lease: { lease_id: "remote-lease-1", remote_lease_id: "remote-lease-1", owner_device_id: "device-001", }, }); expect(mockState.fetch).toHaveBeenCalledWith( "https://manage.example.com/api/digital-employee/leases/plan-step-dispatch/acquire", expect.objectContaining({ method: "POST", headers: expect.objectContaining({ Authorization: "Bearer login-token-001", "X-Qiming-Client-Key": "client-key-001", "X-Qiming-Device-Id": "device-001", }), body: expect.stringContaining('"step_id":"step-1"'), }), ); }); it("returns a remote lease rejection without falling back", async () => { mockState.fetch.mockResolvedValue(jsonResponse({ ok: false, reason: "locked_by_other_device" })); const { acquireDigitalEmployeePlanStepRemoteLease } = await import("./syncService"); const result = await acquireDigitalEmployeePlanStepRemoteLease({ stepId: "step-1", occurredAt: "2026-06-07T08:00:00.000Z" }); expect(result).toMatchObject({ ok: false, rejected: true, reason: "locked_by_other_device" }); }); it("falls back when remote plan step dispatch lease acquire fails", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 503, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "lease service unavailable" })), } as unknown as Response); const { acquireDigitalEmployeePlanStepRemoteLease } = await import("./syncService"); const result = await acquireDigitalEmployeePlanStepRemoteLease({ stepId: "step-1", occurredAt: "2026-06-07T08:00:00.000Z" }); expect(result).toMatchObject({ ok: false, fallback: true, error: "lease service unavailable" }); }); it("reports remote release failures without throwing", async () => { mockState.fetch.mockResolvedValue({ ok: false, status: 500, text: vi.fn().mockResolvedValue(JSON.stringify({ message: "release failed" })), } as unknown as Response); const { releaseDigitalEmployeePlanStepRemoteLease } = await import("./syncService"); const result = await releaseDigitalEmployeePlanStepRemoteLease({ planId: "plan-1", stepId: "step-1", taskId: "task-1", leaseId: "remote-lease-1", remoteLeaseId: "remote-lease-1", reason: "completed", occurredAt: "2026-06-07T08:01:00.000Z", }); expect(result).toMatchObject({ ok: false, endpoint: "https://manage.example.com/api/digital-employee/leases/plan-step-dispatch/release", error: "release failed" }); }); it("marks entity-only acknowledgements as synced", async () => { insertPlan("plan-1"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "plan", entity_id: "plan-1", remote_id: "remote-plan-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(lastSyncRequestBody()).toMatchObject({ client_key: "client-key-001", device_id: "device-001", contract_version: "qimingclaw.digital_employee.sync.v1", items: [ expect.objectContaining({ outbox_id: "plan:upsert:plan-1", entity_type: "plan", entity_id: "plan-1", operation: "upsert", contract_version: "qimingclaw.digital_employee.sync.v1", entity_summary: { title: "同步测试计划 plan-1", status: "pending", summary: "用于验证同步失败诊断的业务摘要", }, business_view: expect.objectContaining({ entity_type: "plan", local_id: "plan-1", title: "同步测试计划 plan-1", status: "pending", objective: "用于验证同步失败诊断的业务摘要", }), payload: { ok: true }, }), ], }); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "synced", error: null, attempts: 1, }); expect(readPlan("plan-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-plan-1", sync_error: null, }); expect(readAttempts()).toEqual([ expect.objectContaining({ outbox_id: "plan:upsert:plan-1", attempt_no: 1, status: "synced", error: null, }), ]); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("accepts backend ReqResult acknowledgements", async () => { insertPlan("plan-reqresult-ok"); insertOutbox("plan:upsert:plan-reqresult-ok", "plan", "plan-reqresult-ok"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0000", message: "success", data: { acked: [ { outbox_id: "plan:upsert:plan-reqresult-ok", entity_type: "plan", entity_id: "plan-reqresult-ok", remote_id: "remote-plan-reqresult-ok", }, ], }, }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBeNull(); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("plan:upsert:plan-reqresult-ok")).toMatchObject({ status: "synced", error: null, attempts: 1, }); expect(readPlan("plan-reqresult-ok")).toMatchObject({ sync_status: "synced", remote_id: "remote-plan-reqresult-ok", sync_error: null, }); }); it("marks backend ReqResult business failures as failed", async () => { insertPlan("plan-rejected"); insertOutbox("plan:upsert:plan-rejected", "plan", "plan-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "客户端 Key header 缺失", data: null, }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBe("客户端 Key header 缺失"); expect(status.pending).toBe(0); expect(status.failed).toBe(1); expect(readOutbox("plan:upsert:plan-rejected")).toMatchObject({ status: "failed", error: "客户端 Key header 缺失", attempts: 1, }); expect(readPlan("plan-rejected")).toMatchObject({ sync_status: "failed", sync_error: "客户端 Key header 缺失", }); expect(readAttempts()).toEqual([ expect.objectContaining({ outbox_id: "plan:upsert:plan-rejected", attempt_no: 1, status: "failed", error: "客户端 Key header 缺失", }), ]); }); it("keeps unacknowledged rows retryable after a partial backend ack", async () => { insertPlan("plan-1"); insertPlan("plan-2"); insertOutbox("plan:upsert:plan-1", "plan", "plan-1"); insertOutbox("plan:upsert:plan-2", "plan", "plan-2"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { outbox_id: "plan:upsert:plan-1", entity_type: "plan", entity_id: "plan-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.lastSyncError).toBe( "management sync response did not acknowledge item", ); expect(status.failed).toBe(1); expect(status.failureSummary).toMatchObject({ total: 1, dueForRetry: 0, backoff: 1, byEntityType: [{ entityType: "plan", count: 1 }], }); expect(readOutbox("plan:upsert:plan-1")).toMatchObject({ status: "synced", }); expect(readOutbox("plan:upsert:plan-2")).toMatchObject({ status: "failed", attempts: 1, error: "management sync response did not acknowledge item", }); expect(status.recentFailures[0]).toMatchObject({ id: "plan:upsert:plan-2", entitySummary: { title: "同步测试计划 plan-2", status: "pending", summary: "用于验证同步失败诊断的业务摘要", }, dueForRetry: false, managementRecordUrl: "https://manage.example.com/system/content/content-digital-employee-sync?device_id=device-001&entity_type=plan&entity_id=plan-2&outbox_id=plan%3Aupsert%3Aplan-2", attemptHistory: [ expect.objectContaining({ attemptNo: 1, status: "failed", error: "management sync response did not acknowledge item", endpoint: "https://manage.example.com/api/digital-employee/sync/outbox", }), ], }); }); it("marks artifact and approval acknowledgements as synced", async () => { insertEntity("artifact", "artifact-1"); insertEntity("approval", "approval-1"); insertOutbox("artifact:upsert:artifact-1", "artifact", "artifact-1"); insertOutbox("approval:upsert:approval-1", "approval", "approval-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "artifact", entity_id: "artifact-1", remote_id: "remote-artifact-1", }, { entity_type: "approval", entity_id: "approval-1", remote_id: "remote-approval-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.pending).toBe(0); expect(status.failed).toBe(0); expect(readOutbox("artifact:upsert:artifact-1")).toMatchObject({ status: "synced", attempts: 1, }); expect(readOutbox("approval:upsert:approval-1")).toMatchObject({ status: "synced", attempts: 1, }); expect(readEntity("artifact", "artifact-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-artifact-1", sync_error: null, }); expect(readEntity("approval", "approval-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-approval-1", sync_error: null, }); }); it("syncs plan step business views and acknowledgements", async () => { insertEntity("plan_step", "step-1"); insertOutbox( "plan_step:upsert:step-1", "plan_step", "step-1", JSON.stringify({ planId: "plan-1", title: "整理客户列表", status: "pending", seq: 2, dependsOn: ["step-0"], preferredSkillIds: ["qimingclaw-mcp-tool-crm-crm-search"], payload: { dependencyGraph: { status: "blocked", blockedDependencies: ["step-0"], waitingDependencies: ["step-2"], }, lastDispatch: { status: "failed" }, skipReason: "approval_pending", dispatchLease: { lease_id: "lease-step-1", state: "active", owner_device_id: "device-001", acquired_at: "2026-06-07T07:59:00.000Z", expires_at: "2026-06-07T08:04:00.000Z", trigger_source: "manual", orchestration_id: "manual-ready-steps-plan-1", candidate_count: 1, }, }, }), ); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "plan_step", entity_id: "step-1", remote_id: "remote-step-1", }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(lastSyncRequestBody()).toMatchObject({ items: [ expect.objectContaining({ entity_type: "plan_step", business_view: expect.objectContaining({ entity_type: "plan_step", local_id: "step-1", plan_id: "plan-1", step_id: "step-1", title: "整理客户列表", status: "pending", seq: 2, depends_on: ["step-0"], dependency_count: 1, dependency_status: "blocked", blocked_dependencies: ["step-0"], blocked_dependency_count: 1, waiting_dependencies: ["step-2"], waiting_dependency_count: 1, preferred_skill_ids: ["qimingclaw-mcp-tool-crm-crm-search"], dispatchable: false, skip_reason: "lease_active", last_dispatch_status: "failed", lease_id: "lease-step-1", lease_state: "active", lease_owner_device_id: "device-001", lease_acquired_at: "2026-06-07T07:59:00.000Z", lease_expires_at: "2026-06-07T08:04:00.000Z", lease_active: true, }), }), ], }); expect(status.failed).toBe(0); expect(readEntity("plan_step", "step-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-step-1", sync_error: null, }); }); it("marks plan step business failures as failed", async () => { insertEntity("plan_step", "step-rejected"); insertOutbox("plan_step:upsert:step-rejected", "plan_step", "step-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "步骤同步被拒绝", data: null }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(1); expect(readEntity("plan_step", "step-rejected")).toMatchObject({ sync_status: "failed", sync_error: "步骤同步被拒绝", }); }); it("syncs schedule and schedule run business views", async () => { insertEntity("schedule", "schedule-1"); insertEntity("schedule_run", "schedule-run-1"); insertOutbox("schedule:upsert:schedule-1", "schedule", "schedule-1"); insertOutbox("schedule_run:upsert:schedule-run-1", "schedule_run", "schedule-run-1"); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "schedule", entity_id: "schedule-1", remote_id: "remote-schedule-1" }, { entity_type: "schedule_run", entity_id: "schedule-run-1", remote_id: "remote-schedule-run-1" }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(0); expect(lastSyncRequestBody().items).toEqual([ expect.objectContaining({ entity_type: "schedule", business_view: expect.objectContaining({ title: "同步测试计划 schedule-1", status: "pending", }), }), expect.objectContaining({ entity_type: "schedule_run", business_view: expect.objectContaining({ title: "同步测试计划 schedule-run-1", status: "pending", }), }), ]); expect(readEntity("schedule", "schedule-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-schedule-1", }); expect(readEntity("schedule_run", "schedule-run-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-schedule-run-1", }); }); it("syncs memory business views and acknowledgements", async () => { insertEntity("memory", "memory-1"); insertOutbox( "memory:upsert:memory-1", "memory", "memory-1", JSON.stringify({ key: "commit-language", content: "用户要求提交信息使用中文,并保留相关上下文。", category: "preference", source: "qimingclaw-memory-service", status: "active", }), ); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [{ entity_type: "memory", entity_id: "memory-1", remote_id: "remote-memory-1" }], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(0); expect(lastSyncRequestBody()).toMatchObject({ items: [ expect.objectContaining({ entity_type: "memory", business_view: expect.objectContaining({ entity_type: "memory", local_id: "memory-1", key: "commit-language", category: "preference", source: "qimingclaw-memory-service", status: "active", content_preview: "用户要求提交信息使用中文,并保留相关上下文。", }), }), ], }); expect(readEntity("memory", "memory-1")).toMatchObject({ sync_status: "synced", remote_id: "remote-memory-1", sync_error: null, }); }); it("syncs specialized event business views for management consumption", async () => { insertEventEntity("event-route", "route_decision", { source: "qimingclaw-route-decision", routeDecision: { id: "route-1", route_kind: "PlanStepDispatch", intent_kind: "schedule", policy_result: "accepted", reason_codes: ["ready_plan_step"], candidate_skills: ["crm-search"], created_command_id: "governance-command-1", policy_source: "remote", blocked_reason: null, approval_id: "route-approval-1", matched_intent: "schedule", }, }); insertEventEntity("event-route-intervention", "route_decision_intervention_resolved", { route_decision_id: "route-1", approval_id: "route-approval-1", route_kind: "PlanStepDispatch", intent_kind: "schedule", decision: "approved", status: "executed", policy_result: "approval_required", policy_source: "remote", created_command_id: "governance-command-restore", command_accepted: true, reason_codes: ["route_intent_requires_approval"], }); insertEventEntity("event-approval", "approval_action_recorded", { approval_id: "approval-1", action: "mark_risk", status: "recorded", actor: "operator", comment_summary: { preview: "存在风险", length: 4 }, delegate_summary: { target: "manager-1", label: "经理" }, sla_summary: { due_at: "2026-06-08T09:00:00.000Z", sla_status: "tracked" }, risk_summary: { risk_level: "high" }, }); insertEventEntity("event-approval-decision", "approval_decision", { approvalId: "approval-1", decision: "approved", previousStatus: "pending", nextStatus: "approved", actor: "operator", decidedAt: "2026-06-07T08:05:00.000Z", planId: "plan-1", taskId: "task-1", runId: "run-1", }); insertEventEntity("event-approval-conflict-resolved", "approval_conflict_resolved", { approval_id: "approval-conflict-1", resolution: "keep_local", status: "approved", actor: "lead", reason_codes: ["conflict_resolved"], previous_reason: "terminal_status_mismatch", previous_local_status: "approved", previous_remote_status: "rejected", resolved_at: "2026-06-07T08:00:00.000Z", }); insertEventEntity("event-approval-conflict-rejected", "approval_conflict_resolution_rejected", { approval_id: "approval-conflict-2", resolution: "accept_remote", status: "rejected", actor: "lead", reason_codes: ["remote_snapshot_missing"], }); insertEventEntity("event-artifact", "artifact_retention_marked", { artifact_id: "artifact-1", action: "mark_keep", status: "recorded", actor: "operator", retention_status: "kept", retention_until: "2026-07-01T00:00:00.000Z", }); insertEventEntity("event-artifact-cleanup", "artifact_cleanup_executed", { artifact_id: "artifact-1", status: "deleted", actor: "operator", reason: "retention_expired", reason_codes: ["retention_expired"], deleted_at: "2026-06-07T08:06:00.000Z", }); insertEventEntity("event-daily-report-send", "daily_report_send_recorded", { report_id: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", action: "send", status: "sent", actor: "operator", endpoint: "management-console", recorded_at: "2026-06-07T08:05:00.000Z", }); insertEventEntity("event-daily-report-confirm", "daily_report_confirmed", { report_id: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", action: "confirm", status: "confirmed", actor: "management", remote_id: "remote-report-1", recorded_at: "2026-06-07T08:06:00.000Z", }); insertEventEntity("event-skill", "skill_call_rejected", { call_id: "call-1", source: "qimingclaw-acp", server_id: "server-1", tool_name: "crm.search", skill_id: "crm-search", decision: "rejected", reason_codes: ["skill_disabled"], }); insertEventEntity("event-sandbox-audit", "sandbox_audit", { session_id: "sandbox-session-1", operation: "execute", status: "failed", exit_code: 126, duration_ms: 2400, command: "curl https://example.com --header Authorization: Bearer secret-token", command_preview: "curl https://example.com", reason_codes: ["permission_denied"], authorization: "Bearer secret-token", }); insertEventEntity("event-token-cost", "token_cost", { session_id: "session-cost-1", model: "gpt-4.1-mini", input_tokens: 120, output_tokens: 80, total_tokens: 200, estimated_cost_usd: 0.0125, prompt: "完整客户资料和敏感输入不应同步", raw_input: { token: "raw-secret" }, token: "secret-token", }); insertEventEntity("event-tool-audit", "tool_call_audit", { session_id: "session-tool-1", server_id: "crm-server", tool_name: "crm.search", operation: "tool_call", status: "allowed", duration_ms: 640, input_length: 36, raw_input: { customer_phone: "13800000000" }, authorization: "Bearer tool-secret", }); insertEventEntity("event-dispatch", "plan_step_dispatch_failed", { step_id: "step-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", status: "failed", reason: "computer chat failed", trigger_source: "manual", orchestration_id: "manual-ready-steps-plan-1", }); insertEventEntity("event-lease", "plan_step_lease_released", { step_id: "step-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", lease_id: "lease-step-1", lease_state: "released", lease_owner_device_id: "device-001", lease_expires_at: "2026-06-07T08:04:00.000Z", lease_source: "remote", remote_lease_id: "remote-lease-step-1", remote_release_error: "release failed", reason: "completed", lease: { lease_id: "lease-step-1", state: "released", owner_device_id: "device-001", acquired_at: "2026-06-07T07:59:00.000Z", expires_at: "2026-06-07T08:04:00.000Z", released_at: "2026-06-07T08:01:00.000Z", release_reason: "completed", source: "remote", remote_lease_id: "remote-lease-step-1", remote_release_error: "release failed", }, }); insertEventEntity("event-plan-step-ready", "plan_step_ready", { stepId: "step-2", planId: "plan-1", status: "ready", dependsOn: ["step-1"], dependencyGraph: { status: "ready", blockedDependencies: [], waitingDependencies: [], satisfiedDependencies: ["step-1"], }, }); insertEventEntity("event-governance-input", "governance_provide_task_input", { commandKind: "provide_task_input", commandId: "command-provide-input", planId: "plan-1", taskId: "task-1", runId: "run-1", accepted: true, payload: { input: "请改用客户备用手机号", source: "notification_reply", notification_id: "input:task-1", plan_id: "plan-1", task_id: "task-1", }, result: { plan_id: "plan-1", task_id: "task-1", run_id: "run-1", source: "qimingclaw-task-agent", }, }); insertEventEntity("event-governance-pause", "governance_pause_plan", { commandKind: "pause_plan", commandId: "command-pause-plan", correlationId: "route-1", planId: "plan-1", accepted: true, message: "暂停计划", riskLevel: "normal", payload: { source: "qimingclaw-ingress-route", route_decision_id: "route-1", route_kind: "PlanControl", intent_kind: "control", plan_id: "plan-1", }, result: { plan_id: "plan-1", status: "accepted", source: "qimingclaw-adapter", }, }); insertEventEntity("event-governance-skip", "governance_skip_task", { command_kind: "skip_task", command_id: "command-skip-task", correlation_id: "route-2", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", step_id: "step-1", accepted: false, error: "policy_blocked", risk_level: "medium", payload: { source: "qimingclaw-ingress-route", route_decision_id: "route-2", route_kind: "PlanControl", intent_kind: "control", task_id: "task-1", }, result: { status: "rejected", error: "policy_blocked", }, }); insertEventEntity("event-notification-read", "digital_workday_notification_read", { metadata: { notification_id: "task:task-1", action: "read", }, state: { notificationStateById: { "task:task-1": { status: "read", read_at: "2026-06-07T08:01:00.000Z", }, }, }, }); insertEventEntity("event-notification-dismiss", "digital_workday_notification_dismiss", { metadata: { notification_id: "task:task-2", action: "dismiss", }, state: { notificationStateById: { "task:task-2": { status: "dismissed", dismissed_at: "2026-06-07T08:02:00.000Z", }, }, }, }); insertEventEntity("event-notification-reply", "digital_workday_notification_reply", { metadata: { notification_id: "input:task-3", action: "reply", }, state: { notificationStateById: { "input:task-3": { status: "read", read_at: "2026-06-07T08:03:00.000Z", reply: "请改用客户备用手机号", replied_at: "2026-06-07T08:03:30.000Z", }, }, }, }); insertEventEntity("event-notification-preferences", "digital_workday_update_notification_preferences", { metadata: { notification_preferences: { enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], updated_at: "2026-06-07T08:04:00.000Z", }, }, state: { notificationPreferences: { enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], updated_at: "2026-06-07T08:04:00.000Z", }, }, }); insertEventEntity("event-approval-subscriptions", "digital_workday_update_approval_subscriptions", { metadata: { action: "update_approval_subscriptions", approval_subscriptions: { enabled: false, subscribed_actions: ["mark_risk", "decision"], subscribed_risk_levels: ["high", "critical"], notify_on_timeout: false, updated_at: "2026-06-07T08:06:00.000Z", }, }, state: { approvalSubscriptionPreferences: { enabled: false, subscribed_actions: ["mark_risk", "decision"], subscribed_risk_levels: ["high", "critical"], notify_on_timeout: false, updated_at: "2026-06-07T08:06:00.000Z", }, }, }); insertEventEntity("event-plan-step-policy", "digital_workday_update_plan_step_dispatch_policy", { metadata: { action: "update_plan_step_dispatch_policy", plan_step_dispatch_policy: { enabled: false, max_per_sweep: 1, auto_dispatch_ready_steps: false, updated_at: "2026-06-07T08:07:00.000Z", }, }, state: { planStepDispatchPolicy: { enabled: false, max_per_sweep: 1, auto_dispatch_ready_steps: false, updated_at: "2026-06-07T08:07:00.000Z", }, }, }); insertEventEntity("event-plan-step-policy-pull", "digital_workday_pull_plan_step_dispatch_policy", { metadata: { action: "pull_plan_step_dispatch_policy", source: "management-api", ok: false, error: "policy service unavailable", endpoint: "https://manage.example.com/api/digital-employee/policies/plan-step-dispatch", plan_step_dispatch_policy: { enabled: true, max_per_sweep: 2, auto_dispatch_ready_steps: true, updated_at: "2026-06-07T08:08:00.000Z", source: "remote", remote_updated_at: "2026-06-07T08:08:00.000Z", last_pulled_at: "2026-06-07T08:09:00.000Z", last_pull_error: "policy service unavailable", }, }, }); insertEventEntity("event-risk-policy-pull", "digital_workday_pull_risk_approval_policy", { metadata: { action: "pull_risk_approval_policy", source: "management-api", ok: true, risk_approval_policy: { enabled: true, source: "remote", version: "risk-policy-v2", approval_required_risk_levels: ["high", "critical"], approval_required_actions: ["managed_service.restart.*"], auto_reject_actions: ["service.stop.*"], remote_updated_at: "2026-06-07T08:10:00.000Z", last_pulled_at: "2026-06-07T08:11:00.000Z", }, }, }); insertEventEntity("event-route-policy-pull", "digital_workday_pull_route_decision_policy", { metadata: { action: "pull_route_decision_policy", source: "management-api", ok: true, route_decision_policy: { enabled: true, source: "remote", version: "route-policy-v3", auto_create_governance_commands: false, blocked_intents: ["delete"], approval_required_intents: ["restart_service"], preferred_route_kinds: ["PlanStepDispatch"], remote_updated_at: "2026-06-07T08:12:00.000Z", last_pulled_at: "2026-06-07T08:13:00.000Z", }, }, }); insertEventEntity("event-risk-approval", "risk_approval_required", { action_kind: "managed_service.restart.crm-sync", action_label: "重启 CRM 同步服务", risk_level: "high", decision: "approval_required", status: "pending", reason_codes: ["risk_level_requires_approval"], policy_source: "remote", approval_id: "risk-approval-1", correlation_id: "restart-1", actor: "operator", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", }); mockState.fetch.mockResolvedValue( jsonResponse({ success: true, acked: [ { entity_type: "event", entity_id: "event-route" }, { entity_type: "event", entity_id: "event-route-intervention" }, { entity_type: "event", entity_id: "event-approval" }, { entity_type: "event", entity_id: "event-approval-decision" }, { entity_type: "event", entity_id: "event-approval-conflict-resolved" }, { entity_type: "event", entity_id: "event-approval-conflict-rejected" }, { entity_type: "event", entity_id: "event-artifact" }, { entity_type: "event", entity_id: "event-artifact-cleanup" }, { entity_type: "event", entity_id: "event-daily-report-send" }, { entity_type: "event", entity_id: "event-daily-report-confirm" }, { entity_type: "event", entity_id: "event-skill" }, { entity_type: "event", entity_id: "event-sandbox-audit" }, { entity_type: "event", entity_id: "event-token-cost" }, { entity_type: "event", entity_id: "event-tool-audit" }, { entity_type: "event", entity_id: "event-dispatch" }, { entity_type: "event", entity_id: "event-lease" }, { entity_type: "event", entity_id: "event-plan-step-ready" }, { entity_type: "event", entity_id: "event-governance-input" }, { entity_type: "event", entity_id: "event-governance-pause" }, { entity_type: "event", entity_id: "event-governance-skip" }, { entity_type: "event", entity_id: "event-notification-read" }, { entity_type: "event", entity_id: "event-notification-dismiss" }, { entity_type: "event", entity_id: "event-notification-reply" }, { entity_type: "event", entity_id: "event-notification-preferences" }, { entity_type: "event", entity_id: "event-approval-subscriptions" }, { entity_type: "event", entity_id: "event-plan-step-policy" }, { entity_type: "event", entity_id: "event-plan-step-policy-pull" }, { entity_type: "event", entity_id: "event-risk-policy-pull" }, { entity_type: "event", entity_id: "event-route-policy-pull" }, { entity_type: "event", entity_id: "event-risk-approval" }, ], }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); const items = lastSyncRequestBody().items as Array<{ entity_id: string; business_view: Record }>; const businessView = (eventId: string) => items.find((item) => item.entity_id === eventId)?.business_view; expect(status.failed).toBe(0); expect(businessView("event-route")).toMatchObject({ entity_type: "event", local_id: "event-route", category: "route", kind: "route_decision", route_decision_id: "route-1", route_kind: "PlanStepDispatch", intent_kind: "schedule", policy_result: "accepted", reason_codes: ["ready_plan_step"], candidate_skills: ["crm-search"], created_command_id: "governance-command-1", policy_source: "remote", approval_id: "route-approval-1", matched_intent: "schedule", attention_level: "action", }); expect(businessView("event-route-intervention")).toMatchObject({ category: "route", route_decision_id: "route-1", approval_id: "route-approval-1", decision: "approved", status: "executed", created_command_id: "governance-command-restore", policy_source: "remote", command_accepted: true, attention_level: "action", }); expect(businessView("event-approval")).toMatchObject({ category: "approval", approval_id: "approval-1", action: "mark_risk", status: "recorded", actor: "operator", risk_level: "high", due_at: "2026-06-08T09:00:00.000Z", delegate_to: "manager-1", comment_length: 4, }); expect(businessView("event-approval")).not.toHaveProperty("comment"); expect(businessView("event-approval")).not.toHaveProperty("comment_preview"); expect(businessView("event-approval-decision")).toMatchObject({ category: "approval", approval_id: "approval-1", action: "decision", decision: "approved", previous_status: "pending", next_status: "approved", actor: "operator", decided_at: "2026-06-07T08:05:00.000Z", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", }); expect(businessView("event-approval-conflict-resolved")).toMatchObject({ category: "approval", approval_id: "approval-conflict-1", action: "conflict_resolution", approval_conflict_resolution: "keep_local", approval_conflict_resolution_actor: "lead", approval_conflict_resolved_at: "2026-06-07T08:00:00.000Z", status: "approved", reason_codes: ["conflict_resolved"], previous_reason: "terminal_status_mismatch", previous_local_status: "approved", previous_remote_status: "rejected", }); expect(businessView("event-approval-conflict-rejected")).toMatchObject({ category: "approval", approval_id: "approval-conflict-2", action: "conflict_resolution", approval_conflict_resolution: "accept_remote", approval_conflict_resolution_actor: "lead", status: "rejected", reason_codes: ["remote_snapshot_missing"], }); expect(businessView("event-artifact")).toMatchObject({ category: "artifact", artifact_id: "artifact-1", action: "mark_keep", status: "recorded", retention_status: "kept", retention_until: "2026-07-01T00:00:00.000Z", actor: "operator", }); expect(businessView("event-artifact-cleanup")).toMatchObject({ category: "artifact", artifact_id: "artifact-1", status: "deleted", cleanup_status: "deleted", cleanup_reason: "retention_expired", deleted_at: "2026-06-07T08:06:00.000Z", reason_codes: ["retention_expired"], actor: "operator", }); expect(businessView("event-daily-report-send")).toMatchObject({ category: "daily_report", report_id: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", action: "send", status: "sent", actor: "operator", endpoint: "management-console", }); expect(businessView("event-daily-report-confirm")).toMatchObject({ category: "daily_report", report_id: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", action: "confirm", status: "confirmed", remote_id: "remote-report-1", }); expect(businessView("event-skill")).toMatchObject({ category: "skill", call_id: "call-1", source: "qimingclaw-acp", server_id: "server-1", tool_name: "crm.search", skill_id: "crm-search", decision: "rejected", reason_codes: ["skill_disabled"], }); expect(businessView("event-sandbox-audit")).toMatchObject({ category: "sandbox_audit", session_id: "sandbox-session-1", operation: "execute", status: "failed", exit_code: 126, duration_ms: 2400, command_preview: "curl https://example.com", reason_codes: ["permission_denied"], }); expect(JSON.stringify(businessView("event-sandbox-audit"))).not.toContain("secret-token"); expect(JSON.stringify(businessView("event-sandbox-audit"))).not.toContain("Authorization"); expect(businessView("event-token-cost")).toMatchObject({ category: "token_cost", session_id: "session-cost-1", model: "gpt-4.1-mini", input_tokens: 120, output_tokens: 80, total_tokens: 200, estimated_cost_usd: 0.0125, estimated: false, }); expect(JSON.stringify(businessView("event-token-cost"))).not.toContain("完整客户资料"); expect(JSON.stringify(businessView("event-token-cost"))).not.toContain("raw-secret"); expect(businessView("event-tool-audit")).toMatchObject({ category: "tool_call_audit", session_id: "session-tool-1", server_id: "crm-server", tool_name: "crm.search", operation: "tool_call", status: "allowed", duration_ms: 640, input_length: 36, }); expect(JSON.stringify(businessView("event-tool-audit"))).not.toContain("13800000000"); expect(JSON.stringify(businessView("event-tool-audit"))).not.toContain("tool-secret"); expect(businessView("event-dispatch")).toMatchObject({ category: "dispatch", step_id: "step-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", status: "failed", reason: "computer chat failed", trigger_source: "manual", orchestration_id: "manual-ready-steps-plan-1", }); expect(businessView("event-lease")).toMatchObject({ category: "dispatch", step_id: "step-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", lease_id: "lease-step-1", lease_state: "released", lease_owner_device_id: "device-001", lease_acquired_at: "2026-06-07T07:59:00.000Z", lease_expires_at: "2026-06-07T08:04:00.000Z", lease_released_at: "2026-06-07T08:01:00.000Z", lease_release_reason: "completed", lease_source: "remote", remote_lease_id: "remote-lease-step-1", remote_release_error: "release failed", remote_lease_rejected: false, reason: "completed", }); expect(businessView("event-plan-step-ready")).toMatchObject({ category: "dispatch", step_id: "step-2", plan_id: "plan-1", status: "ready", dependency_count: 1, blocked_dependencies: [], waiting_dependencies: [], satisfied_dependencies: ["step-1"], }); expect(businessView("event-governance-input")).toMatchObject({ category: "governance", command_kind: "provide_task_input", plan_id: "plan-1", task_id: "task-1", source: "notification_reply", notification_id: "input:task-1", input_length: 10, }); expect(businessView("event-governance-input")).not.toHaveProperty("input"); expect(businessView("event-governance-pause")).toMatchObject({ category: "governance", command_kind: "pause_plan", accepted: true, command_id: "command-pause-plan", correlation_id: "route-1", plan_id: "plan-1", route_decision_id: "route-1", route_kind: "PlanControl", intent_kind: "control", source: "qimingclaw-ingress-route", status: "accepted", risk_level: "normal", }); expect(businessView("event-governance-skip")).toMatchObject({ category: "governance", command_kind: "skip_task", accepted: false, error: "policy_blocked", command_id: "command-skip-task", correlation_id: "route-2", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", step_id: "step-1", route_decision_id: "route-2", route_kind: "PlanControl", intent_kind: "control", risk_level: "medium", }); expect(businessView("event-notification-read")).toMatchObject({ category: "notification", notification_id: "task:task-1", action: "read", status: "read", read_at: "2026-06-07T08:01:00.000Z", }); expect(businessView("event-notification-dismiss")).toMatchObject({ category: "notification", notification_id: "task:task-2", action: "dismiss", status: "dismissed", dismissed_at: "2026-06-07T08:02:00.000Z", }); expect(businessView("event-notification-reply")).toMatchObject({ category: "notification", notification_id: "input:task-3", action: "reply", status: "read", read_at: "2026-06-07T08:03:00.000Z", replied_at: "2026-06-07T08:03:30.000Z", reply_length: 10, }); expect(businessView("event-notification-reply")).not.toHaveProperty("reply"); expect(businessView("event-notification-preferences")).toMatchObject({ category: "notification", preferences_enabled: false, muted_kinds: ["task_finished", "need_input"], muted_levels: ["info"], }); expect(businessView("event-approval-subscriptions")).toMatchObject({ category: "approval", action: "update_approval_subscriptions", preferences_enabled: false, subscribed_actions: ["mark_risk", "decision"], subscribed_risk_levels: ["high", "critical"], notify_on_timeout: false, updated_at: "2026-06-07T08:06:00.000Z", }); expect(businessView("event-approval-subscriptions")).not.toHaveProperty("state"); expect(businessView("event-approval-subscriptions")).not.toHaveProperty("approvalSubscriptionPreferences"); expect(businessView("event-plan-step-policy")).toMatchObject({ category: "policy_snapshot", policy_kind: "dispatch", policy_key: "dispatch", action: "update_plan_step_dispatch_policy", enabled: false, max_per_sweep: 1, auto_dispatch_ready_steps: false, policy_version: "2026-06-07T08:07:00.000Z", }); expect(businessView("event-plan-step-policy")).not.toHaveProperty("state"); expect(businessView("event-plan-step-policy")).not.toHaveProperty("planStepDispatchPolicy"); expect(businessView("event-plan-step-policy-pull")).toMatchObject({ category: "policy_snapshot", policy_kind: "dispatch", policy_key: "dispatch", action: "pull_plan_step_dispatch_policy", source: "management-api", pull_ok: false, pull_error: "policy service unavailable", enabled: true, max_per_sweep: 2, auto_dispatch_ready_steps: true, remote_updated_at: "2026-06-07T08:08:00.000Z", last_pulled_at: "2026-06-07T08:09:00.000Z", }); expect(businessView("event-risk-policy-pull")).toMatchObject({ category: "policy_snapshot", policy_kind: "risk_approval", policy_key: "risk_approval", policy_version: "risk-policy-v2", policy_source: "management-api", enabled: true, approval_required_risk_levels: ["high", "critical"], approval_required_actions: ["managed_service.restart.*"], auto_reject_actions: ["service.stop.*"], remote_updated_at: "2026-06-07T08:10:00.000Z", last_pulled_at: "2026-06-07T08:11:00.000Z", }); expect(businessView("event-route-policy-pull")).toMatchObject({ category: "policy_snapshot", policy_kind: "route_decision", policy_key: "route_decision", policy_version: "route-policy-v3", policy_source: "management-api", enabled: true, auto_create_governance_commands: false, blocked_intents: ["delete"], approval_required_intents: ["restart_service"], preferred_route_kinds: ["PlanStepDispatch"], remote_updated_at: "2026-06-07T08:12:00.000Z", last_pulled_at: "2026-06-07T08:13:00.000Z", }); expect(businessView("event-risk-approval")).toMatchObject({ category: "approval", action_kind: "managed_service.restart.crm-sync", action_label: "重启 CRM 同步服务", risk_level: "high", decision: "approval_required", status: "pending", reason_codes: ["risk_level_requires_approval"], policy_source: "remote", approval_id: "risk-approval-1", correlation_id: "restart-1", actor: "operator", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", }); expect(readEntity("event", "event-route")).toMatchObject({ sync_status: "synced", sync_error: null, }); }); it("marks memory sync failures as failed", async () => { insertEntity("memory", "memory-rejected"); insertOutbox("memory:upsert:memory-rejected", "memory", "memory-rejected"); mockState.fetch.mockResolvedValue( jsonResponse({ code: "0001", message: "记忆同步被拒绝", data: null }), ); const { flushDigitalEmployeeSyncOutbox } = await import("./syncService"); const status = await flushDigitalEmployeeSyncOutbox({ force: true }); expect(status.failed).toBe(1); expect(readEntity("memory", "memory-rejected")).toMatchObject({ sync_status: "failed", sync_error: "记忆同步被拒绝", }); }); }); function insertPlan(id: string): void { insertEntity("plan", id); } function insertEntity(entityType: string, id: string): void { entityMap(entityType)?.set(id, { id, title: `同步测试计划 ${id}`, status: "pending", objective: "用于验证同步失败诊断的业务摘要", payload: "{}", remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, }); } function insertEventEntity(id: string, kind: string, payload: Record): void { mockState.db?.events.set(id, { id, plan_id: "plan-1", task_id: "task-1", run_id: "run-1", kind, message: `事件 ${kind}`, payload: JSON.stringify(payload), remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, occurred_at: "2026-06-07T07:59:00.000Z", }); insertOutbox(`event:insert:${id}`, "event", id, JSON.stringify({ event_id: id, kind, message: `事件 ${kind}`, plan_id: "plan-1", task_id: "task-1", occurred_at: "2026-06-07T07:59:00.000Z", }), "insert"); } function insertOutbox( id: string, entityType: string, entityId: string, payload = '{"ok":true}', operation = "upsert", ): void { mockState.db?.outbox.set(id, { id, entity_type: entityType, entity_id: entityId, operation, payload, status: "pending", attempts: 0, last_attempt_at: null, error: null, created_at: "2026-06-07T07:55:00.000Z", updated_at: "2026-06-07T07:55:00.000Z", }); } function readOutbox(id: string): Record | undefined { return mockState.db?.outbox.get(id); } function readPlan(id: string): Record | undefined { return readEntity("plan", id); } function readEntity( entityType: string, id: string, ): Record | undefined { return entityMap(entityType)?.get(id); } function entityMap( entityType: string, ): Map | undefined { if (!mockState.db) return undefined; if (entityType === "plan") return mockState.db.plans; if (entityType === "task") return mockState.db.tasks; if (entityType === "plan_step") return mockState.db.planSteps; if (entityType === "run") return mockState.db.runs; if (entityType === "schedule") return mockState.db.schedules; if (entityType === "schedule_run") return mockState.db.scheduleRuns; if (entityType === "artifact") return mockState.db.artifacts; if (entityType === "approval") return mockState.db.approvals; if (entityType === "memory") return mockState.db.memories; if (entityType === "event") return mockState.db.events; return undefined; } function readAttempts(): Array> { return mockState.db?.attempts.map((attempt) => ({ ...attempt })) ?? []; } function jsonResponse(body: unknown): Response { return { ok: true, status: 200, text: vi.fn().mockResolvedValue(JSON.stringify(body)), } as unknown as Response; } function lastSyncRequestBody(): Record { const init = mockState.fetch.mock.calls.at(-1)?.[1] as RequestInit | undefined; return JSON.parse(String(init?.body ?? "{}")) as Record; } interface TestOutboxRow { id: string; entity_type: string; entity_id: string; operation: string; payload: string; status: string; attempts: number; last_attempt_at: string | null; error: string | null; created_at: string; updated_at: string; } interface TestSyncEntityRow { id: string; title?: string; status?: string; objective?: string | null; payload?: string; plan_id?: string | null; task_id?: string | null; run_id?: string | null; kind?: string; message?: string; occurred_at?: string; remote_id: string | null; sync_status: string; sync_error: string | null; last_synced_at: string | null; created_at?: string; updated_at?: string; } interface TestAttemptRow { id: number; outbox_id: string; entity_type: string; entity_id: string; operation: string; attempt_no: number; status: string; error: string | null; endpoint: string | null; started_at: string; finished_at: string; } class TestDb { outbox = new Map(); plans = new Map(); tasks = new Map(); planSteps = new Map(); runs = new Map(); schedules = new Map(); scheduleRuns = new Map(); artifacts = new Map(); approvals = new Map(); memories = new Map(); events = new Map(); attempts: TestAttemptRow[] = []; private nextAttemptId = 1; transaction(callback: () => T): () => T { return callback; } prepare(sql: string): { all: (...args: unknown[]) => unknown[]; get: (...args: unknown[]) => unknown; run: (...args: unknown[]) => unknown; } { const normalized = sql.replace(/\s+/g, " ").trim(); return { all: (...args: unknown[]) => this.all(normalized, args), get: (...args: unknown[]) => this.get(normalized, args), run: (...args: unknown[]) => this.run(normalized, args), }; } private all(sql: string, args: unknown[]): unknown[] { if ( sql.includes("FROM digital_sync_outbox") && sql.includes("status IN ('pending', 'failed')") ) { const limit = Number(args[0]); return Array.from(this.outbox.values()) .filter((row) => row.status === "pending" || row.status === "failed") .sort((left, right) => left.created_at.localeCompare(right.created_at)) .slice(0, limit) .map(copyRow); } if ( sql.includes("FROM digital_sync_outbox") && sql.includes("WHERE status = 'failed'") && sql.includes("SELECT entity_type") ) { return Array.from(this.outbox.values()) .filter((row) => row.status === "failed") .map((row) => ({ entity_type: row.entity_type, attempts: row.attempts, last_attempt_at: row.last_attempt_at, })); } if ( sql.includes("FROM digital_sync_outbox") && sql.includes("WHERE status = 'failed'") && sql.includes("ORDER BY COALESCE") ) { const limit = Number(args[0]); return Array.from(this.outbox.values()) .filter((row) => row.status === "failed") .sort((left, right) => recentFailureSortKey(right).localeCompare(recentFailureSortKey(left)), ) .slice(0, limit) .map(copyRow); } if (sql.includes("FROM digital_sync_attempts")) { const [outboxId, limit] = args as [string, number]; return this.attempts .filter((row) => row.outbox_id === outboxId) .sort((left, right) => { const byTime = right.finished_at.localeCompare(left.finished_at); return byTime || right.id - left.id; }) .slice(0, limit) .map((row) => ({ attempt_no: row.attempt_no, status: row.status, error: row.error, endpoint: row.endpoint, started_at: row.started_at, finished_at: row.finished_at, })); } throw new Error(`Unhandled all query: ${sql}`); } private get(sql: string, args: unknown[]): unknown { if (sql.startsWith("SELECT title, status, objective, payload FROM digital_plans")) { const [id] = args as [string]; return this.plans.get(id); } if (sql.startsWith("SELECT title, status, NULL AS objective, payload FROM digital_tasks")) { const [id] = args as [string]; return this.tasks.get(id); } if (sql.startsWith("SELECT id AS title, status, NULL AS objective, payload FROM digital_runs")) { const [id] = args as [string]; const row = this.runs.get(id); if (!row) return undefined; return { title: row.id, status: row.status, objective: null, payload: row.payload, }; } if (sql.startsWith("SELECT name AS title, kind AS status, uri AS summary, payload FROM digital_artifacts")) { const [id] = args as [string]; return this.artifacts.get(id); } if (sql.startsWith("SELECT title, status, NULL AS objective, payload FROM digital_approvals")) { const [id] = args as [string]; return this.approvals.get(id); } if (sql.startsWith("SELECT id, plan_id, task_id, run_id, title, status, payload, created_at, updated_at FROM digital_approvals")) { const [id] = args as [string]; return this.approvals.get(id); } if (sql.startsWith("SELECT key AS title, status, content AS summary, payload FROM digital_memories")) { const [id] = args as [string]; return this.memories.get(id); } if (sql.startsWith("SELECT title, status, NULL AS objective, payload FROM digital_plan_steps")) { const [id] = args as [string]; return this.planSteps.get(id); } if (sql.startsWith("SELECT name AS title, status, description AS objective, payload FROM digital_schedules")) { const [id] = args as [string]; return this.schedules.get(id); } if (sql.startsWith("SELECT id AS title, status, error AS objective, payload FROM digital_schedule_runs")) { const [id] = args as [string]; return this.scheduleRuns.get(id); } if (sql.startsWith("SELECT kind AS title, kind AS status, message, payload FROM digital_events")) { const [id] = args as [string]; const row = this.events.get(id); if (!row) return undefined; return { title: row.kind, status: row.kind, message: row.message, payload: row.payload, }; } if (sql.startsWith("SELECT plan_id, task_id, run_id, kind, message, payload, occurred_at FROM digital_events")) { const [id] = args as [string]; return this.events.get(id); } if ( sql.includes("COUNT(*) AS count FROM digital_sync_outbox WHERE status = ?") ) { const [status] = args; return { count: Array.from(this.outbox.values()).filter( (row) => row.status === status, ).length, }; } throw new Error(`Unhandled get query: ${sql}`); } private run(sql: string, args: unknown[]): unknown { if (sql.startsWith("INSERT INTO digital_plans")) { const [id, title, objective, status, payload, createdAt, updatedAt] = args as [ string, string, string | null, string, string, string, string, ]; const previous = this.plans.get(id); this.plans.set(id, { id, title, objective, status, payload, remote_id: previous?.remote_id ?? null, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", sync_error: previous?.sync_error ?? null, last_synced_at: previous?.last_synced_at ?? null, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_tasks")) { const [id, planId, title, status, assignedSkillId, payload] = args as [ string, string, string, string, string, string, string, string, ]; const previous = this.tasks.get(id); this.tasks.set(id, { id, plan_id: planId, title, status, payload: JSON.stringify({ ...JSON.parse(payload), assigned_skill_id: assignedSkillId }), remote_id: previous?.remote_id ?? null, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", sync_error: previous?.sync_error ?? null, last_synced_at: previous?.last_synced_at ?? null, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_runs")) { const [id, planId, taskId, status, startedAt, payload] = args as [ string, string, string, string, string, string, string, string, ]; const previous = this.runs.get(id); this.runs.set(id, { id, plan_id: planId, task_id: taskId, status, payload: JSON.stringify({ ...JSON.parse(payload), started_at: startedAt }), remote_id: previous?.remote_id ?? null, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", sync_error: previous?.sync_error ?? null, last_synced_at: previous?.last_synced_at ?? null, }); return { changes: 1 }; } if (sql.startsWith("UPDATE digital_runs SET status = ?, finished_at = ?, updated_at = ?")) { const [status, finishedAt, _updatedAt, id] = args as [string, string, string, string]; const row = this.runs.get(id); if (row) { row.status = status; row.payload = JSON.stringify({ ...JSON.parse(row.payload ?? "{}"), finished_at: finishedAt }); row.sync_status = "pending"; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("INSERT OR IGNORE INTO digital_events")) { const [id, planId, taskId, runId, kind, message, payload, occurredAt] = args as [ string, string | null, string | null, string | null, string, string, string, string, string, ]; if (!this.events.has(id)) { this.events.set(id, { id, plan_id: planId, task_id: taskId, run_id: runId, kind, message, payload, remote_id: null, sync_status: "pending", sync_error: null, last_synced_at: null, occurred_at: occurredAt, }); } return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_approvals")) { const [id, planId, taskId, runId, title, status, payload, createdAt, updatedAt] = args as [ string, string, string, string, string, string, string, string, string, ]; const previous = this.approvals.get(id); this.approvals.set(id, { id, plan_id: planId, task_id: taskId, run_id: runId, title, status, payload, remote_id: previous?.remote_id ?? null, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", sync_error: previous?.sync_error ?? null, last_synced_at: previous?.last_synced_at ?? null, created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_sync_outbox")) { const [id, entityType, entityId, operation, payload, createdAt, updatedAt] = args as [ string, string, string, string, string, string, string, ]; const previous = this.outbox.get(id); this.outbox.set(id, { id, entity_type: entityType, entity_id: entityId, operation, payload, status: "pending", attempts: previous?.attempts ?? 0, last_attempt_at: previous?.last_attempt_at ?? null, error: null, created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'syncing'")) { const [lastAttemptAt, updatedAt, id] = args as [string, string, string]; const row = this.outbox.get(id); if (row) { row.status = "syncing"; row.attempts += 1; row.last_attempt_at = lastAttemptAt; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'synced'")) { const [updatedAt, id] = args as [string, string]; const row = this.outbox.get(id); if (row) { row.status = "synced"; row.error = null; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_sync_outbox SET status = 'failed'")) { const [error, updatedAt, id] = args as [string, string, string]; const row = this.outbox.get(id); if (row) { row.status = "failed"; row.error = error; row.updated_at = updatedAt; } return { changes: row ? 1 : 0 }; } if (sql.startsWith("INSERT INTO digital_sync_attempts")) { const [ outboxId, entityType, entityId, operation, attemptNo, status, error, endpoint, startedAt, finishedAt, ] = args as [ string, string, string, string, number, string, string | null, string | null, string, string, ]; this.attempts.push({ id: this.nextAttemptId, outbox_id: outboxId, entity_type: entityType, entity_id: entityId, operation, attempt_no: attemptNo, status, error, endpoint, started_at: startedAt, finished_at: finishedAt, }); this.nextAttemptId += 1; return { changes: 1 }; } if (sql.startsWith("DELETE FROM digital_sync_attempts WHERE finished_at < ?")) { const [cutoff] = args as [string]; const before = this.attempts.length; this.attempts = this.attempts.filter((row) => row.finished_at >= cutoff); return { changes: before - this.attempts.length }; } if (/^UPDATE digital_(plans|tasks|plan_steps|runs|schedules|schedule_runs|events|artifacts|approvals|memories) SET sync_status = 'synced'/.test(sql)) { const [remoteId, lastSyncedAt, id] = args as [string | null, string, string]; const row = this.entityRowsForUpdate(sql).get(id); if (row) { row.sync_status = "synced"; row.remote_id = remoteId ?? row.remote_id; row.last_synced_at = lastSyncedAt; row.sync_error = null; } return { changes: row ? 1 : 0 }; } if (/^UPDATE digital_(plans|tasks|plan_steps|runs|schedules|schedule_runs|events|artifacts|approvals|memories) SET sync_status = 'failed'/.test(sql)) { const [syncError, id] = args as [string, string]; const row = this.entityRowsForUpdate(sql).get(id); if (row) { row.sync_status = "failed"; row.sync_error = syncError; } return { changes: row ? 1 : 0 }; } throw new Error(`Unhandled run query: ${sql}`); } private entityRowsForUpdate(sql: string): Map { if (sql.startsWith("UPDATE digital_artifacts")) return this.artifacts; if (sql.startsWith("UPDATE digital_approvals")) return this.approvals; if (sql.startsWith("UPDATE digital_memories")) return this.memories; if (sql.startsWith("UPDATE digital_events")) return this.events; if (sql.startsWith("UPDATE digital_runs")) return this.runs; if (sql.startsWith("UPDATE digital_schedule_runs")) return this.scheduleRuns; if (sql.startsWith("UPDATE digital_schedules")) return this.schedules; if (sql.startsWith("UPDATE digital_plan_steps")) return this.planSteps; if (sql.startsWith("UPDATE digital_tasks")) return this.tasks; return this.plans; } } function copyRow(row: T): T { return { ...row }; } function recentFailureSortKey(row: TestOutboxRow): string { return row.last_attempt_at || row.updated_at || row.created_at; }