import { beforeEach, describe, expect, it, vi } from "vitest"; const mockState = vi.hoisted(() => ({ db: null as TestDb | null, ensureSchemaCalls: 0, settings: new Map(), })); 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 state service", () => { beforeEach(() => { vi.resetModules(); vi.setSystemTime(new Date("2026-06-07T08:00:00.000Z")); mockState.db = new TestDb(); mockState.ensureSchemaCalls = 0; mockState.settings = new Map(); }); it("extracts artifacts and approvals from runtime event payload into syncable records", async () => { const { recordDigitalEmployeeRuntimeEvent } = await import("./stateService"); recordDigitalEmployeeRuntimeEvent({ kind: "tool_result", request_id: "req-1", message: "生成交付产物并等待确认", status: "completed", payload: { artifacts: [ { id: "summary-csv", name: "summary.csv", uri: "file:///tmp/summary.csv", }, ], outputPath: "/tmp/final-report.txt", approval_requests: [ { id: "approval-1", title: "确认发送日报给管理端", }, ], }, }); expect(Array.from(mockState.db?.artifacts.values() ?? [])).toEqual([ expect.objectContaining({ id: "computer-chat-run-req-1:artifact:summary-csv", name: "summary.csv", uri: "file:///tmp/summary.csv", }), expect.objectContaining({ id: "computer-chat-run-req-1:artifact:tmp-final-report-txt", name: "final-report.txt", uri: "/tmp/final-report.txt", }), ]); expect(Array.from(mockState.db?.approvals.values() ?? [])).toEqual([ expect.objectContaining({ id: "computer-chat-run-req-1:approval:approval-1", title: "确认发送日报给管理端", status: "pending", }), ]); expect(mockState.db?.outbox.get("artifact:upsert:computer-chat-run-req-1:artifact:summary-csv")).toMatchObject({ entity_type: "artifact", entity_id: "computer-chat-run-req-1:artifact:summary-csv", status: "pending", }); expect(mockState.db?.outbox.get("approval:upsert:computer-chat-run-req-1:approval:approval-1")).toMatchObject({ entity_type: "approval", entity_id: "computer-chat-run-req-1:approval:approval-1", status: "pending", }); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("normalizes file server workspace events into delivery artifact payload", async () => { const { recordDigitalEmployeeRuntimeEvent } = await import("./stateService"); recordDigitalEmployeeRuntimeEvent({ kind: "file_server.workspace.created", request_id: "file-workspace-1", message: "create-workspace 完成", status: "completed", payload: { source: "qiming-file-server", toolName: "create_workspace", workspaceId: "workspace-123", projectId: "project-file", path: "/tmp/qiming/workspace-123", status: "completed", }, }); const artifacts = Array.from(mockState.db?.artifacts.values() ?? []); expect(artifacts).toHaveLength(1); expect(artifacts[0]).toMatchObject({ kind: "workspace", name: "workspace-123", uri: "/tmp/qiming/workspace-123", }); expect(JSON.parse(artifacts[0]?.payload ?? "{}")).toMatchObject({ delivery: { stage: "workspace", status: "completed", workspace_id: "workspace-123", project_id: "project-file", uri: "/tmp/qiming/workspace-123", source_tool: "create_workspace", source_event_kind: "file_server.workspace.created", }, }); expect(mockState.db?.outbox.get(`artifact:upsert:${artifacts[0]?.id}`)).toMatchObject({ entity_type: "artifact", status: "pending", }); }); it("upserts file server file delivery status without duplicating artifacts", async () => { const { recordDigitalEmployeeRuntimeEvent } = await import("./stateService"); const basePayload = { source: "qiming-file-server", workspaceId: "workspace-123", projectId: "project-file", fileId: "file-1", filename: "report.pdf", version: "v2", size: 2048, path: "/tmp/qiming/workspace-123/report.pdf", }; recordDigitalEmployeeRuntimeEvent({ kind: "file_server.upload.completed", request_id: "file-delivery-1", message: "upload-file 完成", status: "completed", payload: { ...basePayload, toolName: "upload_file", status: "uploaded", }, }); recordDigitalEmployeeRuntimeEvent({ kind: "file_server.download.completed", request_id: "file-delivery-1", message: "download-all-files 完成", status: "completed", payload: { ...basePayload, toolName: "download_all_files", status: "downloaded", downloadUrl: "file:///tmp/qiming/workspace-123/report.pdf", }, }); const artifacts = Array.from(mockState.db?.artifacts.values() ?? []); expect(artifacts).toHaveLength(1); expect(artifacts[0]).toMatchObject({ kind: "download", name: "report.pdf", uri: "file:///tmp/qiming/workspace-123/report.pdf", }); expect(JSON.parse(artifacts[0]?.payload ?? "{}")).toMatchObject({ delivery: { stage: "download", status: "downloaded", workspace_id: "workspace-123", project_id: "project-file", file_id: "file-1", version: "v2", filename: "report.pdf", size: 2048, uri: "file:///tmp/qiming/workspace-123/report.pdf", source_tool: "download_all_files", source_event_kind: "file_server.download.completed", }, }); expect(Array.from(mockState.db?.outbox.values() ?? []).filter((row) => row.entity_type === "artifact")).toHaveLength(1); }); it("records daily reports as syncable artifact and event history", async () => { const { recordDigitalEmployeeDailyReport } = await import("./stateService"); const first = recordDigitalEmployeeDailyReport({ date: "2026-06-07", title: "2026-06-07 数字员工日报", summary: "今日完成任务汇总。", totals: { task_count: 2, execution_count: 3, success_count: 2, failure_count: 1, running_count: 0, }, detailLines: ["09:00 任务 A:完成", "10:00 任务 B:异常"], artifactSources: [{ id: "artifact-1", name: "summary.csv" }], filename: "qimingclaw-digital-report-2026-06-07.txt", textContent: "日报正文", generatedAt: "2026-06-07T08:00:00.000Z", reportScheduleTime: "18:00", source: "qimingclaw", model: "qimingclaw-adapter", }); const second = recordDigitalEmployeeDailyReport({ date: "2026-06-07", title: "2026-06-07 数字员工日报", summary: "第二次生成。", totals: { task_count: 2, execution_count: 4, success_count: 3, failure_count: 1 }, detailLines: ["18:00 重新生成日报"], filename: "qimingclaw-digital-report-2026-06-07.txt", textContent: "第二版日报正文", generatedAt: "2026-06-07T09:00:00.000Z", }); expect(first).toMatchObject({ reportId: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", planId: "daily-report:2026-06-07", taskId: "daily-report:2026-06-07:generate", runId: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z", artifactId: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z:artifact:text", eventId: "daily-report:2026-06-07:2026-06-07T08-00-00-000Z:event:generated", }); expect(second?.reportId).toBe("daily-report:2026-06-07:2026-06-07T09-00-00-000Z"); expect(mockState.db?.plans.get("daily-report:2026-06-07")).toMatchObject({ status: "completed" }); expect(mockState.db?.tasks.get("daily-report:2026-06-07:generate")).toMatchObject({ status: "completed", assigned_skill_id: "qimingclaw-artifact-report", }); expect(mockState.db?.runs.get(first!.runId)).toMatchObject({ status: "completed", finished_at: "2026-06-07T08:00:00.000Z", }); expect(Array.from(mockState.db?.artifacts.values() ?? []).filter((row) => row.kind === "daily_report")).toHaveLength(2); expect(mockState.db?.events.get(first!.eventId)).toMatchObject({ kind: "daily_report_generated", message: "数字员工日报已生成:2026-06-07", run_id: first!.runId, }); expect(JSON.parse(mockState.db?.artifacts.get(first!.artifactId)?.payload ?? "{}")).toMatchObject({ report_id: first!.reportId, date: "2026-06-07", filename: "qimingclaw-digital-report-2026-06-07.txt", format: "text", text_content: "日报正文", totals: { task_count: 2, execution_count: 3 }, artifact_sources: [{ id: "artifact-1", name: "summary.csv" }], }); expect(mockState.db?.outbox.get(`artifact:upsert:${first!.artifactId}`)).toMatchObject({ entity_type: "artifact", status: "pending", }); expect(mockState.db?.outbox.get(`event:insert:${first!.eventId}`)).toMatchObject({ entity_type: "event", status: "pending", }); }); it("records and lists route decisions as syncable events", async () => { const { listDigitalEmployeeRouteDecisions, recordDigitalEmployeeRouteDecision, } = await import("./stateService"); const first = recordDigitalEmployeeRouteDecision({ idempotencyKey: "route-key-1", correlationId: "corr-1", routeKind: "PlanExecution", intentKind: "execute", reasonCodes: ["ready_step_context"], candidateSkills: ["qimingclaw-computer-control"], contextRefs: { plan_id: "plan-1", step_id: "step-ready" }, createdCommandId: "qimingclaw-digital-start_run-1", createsTaskRun: true, entrypoint: "task_center", actor: "operator", envelope: { text: "开始执行这个步骤" }, recordedAt: "2026-06-07T08:01:00.000Z", }); const second = recordDigitalEmployeeRouteDecision({ idempotencyKey: "route-key-2", correlationId: "corr-2", routeKind: "ProjectionQuery", intentKind: "query", reasonCodes: ["projection_query_text"], candidateSkills: ["qimingclaw-artifact-report"], contextRefs: { plan_id: "plan-1" }, recordedAt: "2026-06-07T08:02:00.000Z", }); expect(first).toMatchObject({ id: "route-decision:route-key-1", route_kind: "PlanExecution", created_command_id: "qimingclaw-digital-start_run-1", creates_task_run: true, context_refs: { step_id: "step-ready" }, }); expect(mockState.db?.events.get(first.id)).toMatchObject({ kind: "route_decision", run_id: null, plan_id: null, }); expect(JSON.parse(mockState.db?.events.get(first.id)?.payload ?? "{}")).toMatchObject({ source: "qimingclaw-route-decision", routeDecision: { id: first.id, route_kind: "PlanExecution", intent_kind: "execute", created_command_id: "qimingclaw-digital-start_run-1", }, envelope: { text: "开始执行这个步骤" }, }); expect(mockState.db?.outbox.get(`event:insert:${first.id}`)).toMatchObject({ entity_type: "event", operation: "insert", status: "pending", }); expect(listDigitalEmployeeRouteDecisions()).toEqual([ expect.objectContaining({ id: second.id, route_kind: "ProjectionQuery" }), expect.objectContaining({ id: first.id, route_kind: "PlanExecution", created_command_id: "qimingclaw-digital-start_run-1" }), ]); }); it("keeps route decision ids stable for idempotency keys", async () => { const { listDigitalEmployeeRouteDecisions, recordDigitalEmployeeRouteDecision } = await import("./stateService"); const first = recordDigitalEmployeeRouteDecision({ idempotencyKey: "repeat-route-key", routeKind: "ChatOnly", intentKind: "chat", recordedAt: "2026-06-07T08:01:00.000Z", }); const second = recordDigitalEmployeeRouteDecision({ idempotencyKey: "repeat-route-key", routeKind: "PlanExecution", intentKind: "execute", recordedAt: "2026-06-07T08:02:00.000Z", }); expect(second.id).toBe(first.id); expect(Array.from(mockState.db?.events.values() ?? []).filter((event) => event.kind === "route_decision")).toHaveLength(1); expect(listDigitalEmployeeRouteDecisions()).toEqual([ expect.objectContaining({ id: first.id, route_kind: "ChatOnly" }), ]); }); it("updates formal approval records when UI approval decisions are saved", async () => { const { saveDigitalEmployeeUiState } = await import("./stateService"); mockState.db?.approvals.set("approval-1", { id: "approval-1", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", title: "确认发送日报给管理端", status: "pending", payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); saveDigitalEmployeeUiState({ action: "decide_approval", date: "2026-06-07", metadata: { decision_id: "approval-1", decision: "approved", acp_permission: { permission_id: "permission-1", session_id: "session-1", response: "once", success: true, }, }, state: { selectedDutyIdsByDate: {}, confirmedDates: {}, reportScheduleTime: "18:00", reportGeneratedAtByDate: {}, decisionResultsByDate: { "2026-06-07": { "approval-1": "approved", }, }, profile: { name: "飞天数字员工", company: "qimingclaw 客户端", position: "客户端任务执行员", currentStatus: "working", }, }, }); expect(mockState.db?.approvals.get("approval-1")).toMatchObject({ id: "approval-1", status: "approved", sync_status: "pending", updated_at: "2026-06-07T08:00:00.000Z", }); expect(JSON.parse(mockState.db?.approvals.get("approval-1")?.payload ?? "{}")).toMatchObject({ source: "test", decision: "approved", decidedAt: "2026-06-07T08:00:00.000Z", previousStatus: "pending", acpPermission: { permission_id: "permission-1", session_id: "session-1", response: "once", success: true, }, }); expect(mockState.db?.outbox.get("approval:upsert:approval-1")).toMatchObject({ entity_type: "approval", entity_id: "approval-1", status: "pending", }); const decisionEvents = Array.from(mockState.db?.events.values() ?? []).filter((event) => event.kind === "approval_decision"); expect(decisionEvents).toHaveLength(1); expect(decisionEvents[0]).toMatchObject({ id: "approval-1:decision:approved:2026-06-07T08-00-00-000Z", plan_id: "plan-1", task_id: "task-1", run_id: "run-1", message: "审批处理:确认发送日报给管理端 -> approved", sync_status: "pending", occurred_at: "2026-06-07T08:00:00.000Z", }); expect(JSON.parse(decisionEvents[0]?.payload ?? "{}")).toMatchObject({ source: "qimingclaw-approval-decision", approvalId: "approval-1", decision: "approved", previousStatus: "pending", nextStatus: "approved", title: "确认发送日报给管理端", planId: "plan-1", taskId: "task-1", runId: "run-1", decidedAt: "2026-06-07T08:00:00.000Z", acpPermission: { permission_id: "permission-1", session_id: "session-1", }, }); expect(mockState.db?.outbox.get("event:insert:approval-1:decision:approved:2026-06-07T08-00-00-000Z")).toMatchObject({ entity_type: "event", entity_id: "approval-1:decision:approved:2026-06-07T08-00-00-000Z", operation: "insert", status: "pending", }); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("persists console role and role preferences in UI state", async () => { const { readDigitalEmployeeUiState, saveDigitalEmployeeUiState } = await import("./stateService"); saveDigitalEmployeeUiState({ action: "update_role_preferences", metadata: { source: "ops-settings" }, state: { selectedDutyIdsByDate: {}, confirmedDates: {}, reportScheduleTime: "17:30", reportGeneratedAtByDate: {}, decisionResultsByDate: {}, consoleRole: "engineering", rolePreferences: { workStart: "10:00", workEnd: "19:00", notification: "system", }, profile: { name: "飞天数字员工", company: "qimingclaw 客户端", position: "客户端任务执行员", currentStatus: "working", }, }, }); expect(readDigitalEmployeeUiState()).toMatchObject({ reportScheduleTime: "17:30", consoleRole: "engineering", rolePreferences: { workStart: "10:00", workEnd: "19:00", notification: "system", }, }); const preferenceEvent = Array.from(mockState.db?.events.values() ?? []) .find((event) => event.kind === "digital_workday_update_role_preferences"); expect(preferenceEvent).toMatchObject({ kind: "digital_workday_update_role_preferences", message: "数字员工运营偏好已更新", }); }); it("records governance commands as formal runtime records", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); const ids = recordDigitalEmployeeGovernanceCommand({ commandKind: "create_plan", commandId: "command-1", correlationId: "corr-1", accepted: true, message: "已创建计划草稿", payload: { title: "生成今日客户跟进计划", objective: "把今日客户跟进事项整理成可执行计划", steps: [ { step_id: "collect-crm", title: "整理客户列表", skills: ["qimingclaw-mcp-tool-crm-crm-search"], tasks: [ { task_id: "task-collect-crm", title: "拉取 CRM 客户记录", description: "读取今日需要跟进的客户", }, ], }, ], }, result: { plan_id: "plan-customer-followup", status: "draft", }, }); expect(ids).toEqual({ planId: "plan-customer-followup", taskId: "governance-task-command-1", runId: "governance-run-command-1", }); expect(mockState.db?.plans.get("plan-customer-followup")).toMatchObject({ id: "plan-customer-followup", title: "生成今日客户跟进计划", status: "draft", sync_status: "pending", }); expect(mockState.db?.tasks.get("governance-task-command-1")).toMatchObject({ plan_id: "plan-customer-followup", title: "创建数字员工计划", assigned_skill_id: "qimingclaw-governance-command", }); expect(mockState.db?.tasks.get("task-collect-crm")).toMatchObject({ plan_id: "plan-customer-followup", title: "拉取 CRM 客户记录", status: "pending", assigned_skill_id: "qimingclaw-mcp-tool-crm-crm-search", }); expect(mockState.db?.planSteps.get("collect-crm")).toMatchObject({ plan_id: "plan-customer-followup", title: "整理客户列表", status: "pending", seq: 1, preferred_skill_ids: JSON.stringify(["qimingclaw-mcp-tool-crm-crm-search"]), }); expect(JSON.parse(mockState.db?.tasks.get("task-collect-crm")?.payload ?? "{}")).toMatchObject({ stepId: "collect-crm", }); expect(mockState.db?.runs.get("governance-run-command-1")).toMatchObject({ plan_id: "plan-customer-followup", task_id: "governance-task-command-1", status: "draft", finished_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.db?.events.get("governance-run-command-1:create_plan:command-1")).toMatchObject({ kind: "governance_create_plan", message: "已创建计划草稿", }); expect(mockState.db?.outbox.get("plan:upsert:plan-customer-followup")).toMatchObject({ entity_type: "plan", entity_id: "plan-customer-followup", status: "pending", }); expect(mockState.db?.outbox.get("plan_step:upsert:collect-crm")).toMatchObject({ entity_type: "plan_step", entity_id: "collect-crm", status: "pending", }); expect(mockState.ensureSchemaCalls).toBeGreaterThan(0); }); it("creates a default plan step for top-level plan tasks", async () => { const { readDigitalEmployeeRuntimeRecords, recordDigitalEmployeeGovernanceCommand, } = await import("./stateService"); recordDigitalEmployeeGovernanceCommand({ commandKind: "create_plan", commandId: "command-default-step", accepted: true, payload: { title: "客户回访计划", tasks: [{ task_id: "task-call", title: "拨打客户电话" }], }, result: { plan_id: "plan-default-step", status: "draft" }, }); expect(mockState.db?.planSteps.get("plan-default-step:step:default")).toMatchObject({ plan_id: "plan-default-step", title: "默认执行步骤", seq: 1, }); expect(JSON.parse(mockState.db?.tasks.get("task-call")?.payload ?? "{}")).toMatchObject({ stepId: "plan-default-step:step:default", }); expect(mockState.db?.outbox.get("plan_step:upsert:plan-default-step:step:default")).toMatchObject({ entity_type: "plan_step", entity_id: "plan-default-step:step:default", }); expect(readDigitalEmployeeRuntimeRecords()).toMatchObject({ planSteps: [ expect.objectContaining({ id: "plan-default-step:step:default", planId: "plan-default-step", title: "默认执行步骤", }), ], }); }); it("preserves created plan title across follow-up governance commands", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); recordDigitalEmployeeGovernanceCommand({ commandKind: "create_plan", commandId: "command-create", accepted: true, message: "已创建计划草稿", payload: { title: "客户回访重新执行", objective: "重新执行客户回访流程" }, result: { plan_id: "plan-restart", status: "draft" }, }); recordDigitalEmployeeGovernanceCommand({ commandKind: "approve_plan", commandId: "command-approve", accepted: true, message: "已批准计划", result: { plan_id: "plan-restart", status: "approved" }, }); expect(mockState.db?.plans.get("plan-restart")).toMatchObject({ title: "客户回访重新执行", objective: "重新执行客户回访流程", status: "approved", }); }); it("records plan action governance commands as local run facts", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); recordDigitalEmployeeGovernanceCommand({ commandKind: "pause_plan", commandId: "command-pause-plan", planId: "plan-action", accepted: true, message: "暂停计划执行", payload: { plan_id: "plan-action", requested_action: "plan_pause" }, result: { plan_id: "plan-action", status: "paused" }, }); expect(mockState.db?.plans.get("plan-action")).toMatchObject({ status: "paused", sync_status: "pending", }); expect(mockState.db?.tasks.get("governance-task-command-pause-plan")).toMatchObject({ plan_id: "plan-action", status: "paused", assigned_skill_id: "qimingclaw-governance-command", }); expect(mockState.db?.runs.get("governance-run-command-pause-plan")).toMatchObject({ plan_id: "plan-action", status: "paused", finished_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.db?.events.get("governance-run-command-pause-plan:pause_plan:command-pause-plan")).toMatchObject({ kind: "governance_pause_plan", message: "暂停计划执行", }); expect(mockState.db?.outbox.get("plan:upsert:plan-action")).toMatchObject({ entity_type: "plan" }); expect(mockState.db?.outbox.get("event:insert:governance-run-command-pause-plan:pause_plan:command-pause-plan")).toMatchObject({ entity_type: "event" }); }); it("records schedule lifecycle commands and manual runs", async () => { const { readDigitalEmployeeRuntimeRecords, recordDigitalEmployeeGovernanceCommand, } = await import("./stateService"); recordDigitalEmployeeGovernanceCommand({ commandKind: "create_schedule", commandId: "command-schedule-create", planId: "plan-scheduled", scheduleId: "schedule-morning", accepted: true, payload: { name: "晨间巡检", prompt: "执行晨间巡检", cron_expr: "0 9 * * *", }, result: { schedule_id: "schedule-morning", status: "enabled" }, }); expect(mockState.db?.schedules.get("schedule-morning")).toMatchObject({ id: "schedule-morning", plan_id: "plan-scheduled", name: "晨间巡检", cron_expr: "0 9 * * *", status: "enabled", enabled: 1, }); expect(mockState.db?.outbox.get("schedule:upsert:schedule-morning")).toMatchObject({ entity_type: "schedule", entity_id: "schedule-morning", }); recordDigitalEmployeeGovernanceCommand({ commandKind: "pause_schedule", commandId: "command-schedule-pause", planId: "plan-scheduled", scheduleId: "schedule-morning", accepted: true, result: { schedule_id: "schedule-morning", status: "paused" }, }); expect(mockState.db?.schedules.get("schedule-morning")).toMatchObject({ status: "paused", enabled: 0, }); recordDigitalEmployeeGovernanceCommand({ commandKind: "run_schedule_now", commandId: "command-schedule-run", planId: "plan-scheduled", scheduleId: "schedule-morning", accepted: true, payload: { source: "test" }, result: { schedule_id: "schedule-morning", status: "running" }, }); expect(mockState.db?.scheduleRuns.get("schedule-morning:2026-06-07T08-00-00-000Z:1")).toMatchObject({ schedule_id: "schedule-morning", plan_id: "plan-scheduled", status: "running", trigger_kind: "manual", }); expect(mockState.db?.outbox.get("schedule_run:upsert:schedule-morning:2026-06-07T08-00-00-000Z:1")).toMatchObject({ entity_type: "schedule_run", }); expect(readDigitalEmployeeRuntimeRecords()).toMatchObject({ schedules: [expect.objectContaining({ id: "schedule-morning", status: "paused" })], scheduleRuns: [expect.objectContaining({ scheduleId: "schedule-morning", triggerKind: "manual" })], }); }); it("applies skip governance to the real task, plan step, event, and outbox", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); const ids = recordDigitalEmployeeGovernanceCommand({ commandKind: "skip_task", commandId: "command-skip-task", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, message: "跳过当前任务", payload: { reason: "用户选择跳过" }, result: { task_id: "task-governance", run_id: "run-governance", step_id: "step-governance" }, }); expect(ids).toEqual({ planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", }); expect(mockState.db?.tasks.get("task-governance")).toMatchObject({ status: "skipped", sync_status: "pending", }); expect(mockState.db?.planSteps.get("step-governance")).toMatchObject({ status: "skipped", sync_status: "pending", }); expect(mockState.db?.runs.get("run-governance")).toMatchObject({ status: "skipped", finished_at: "2026-06-07T08:00:00.000Z", sync_status: "pending", }); expect(JSON.parse(mockState.db?.tasks.get("task-governance")?.payload ?? "{}")).toMatchObject({ lastGovernanceCommand: { commandKind: "skip_task", taskId: "task-governance", stepId: "step-governance", status: "skipped", }, }); expect(mockState.db?.events.get("run-governance:skip_task:command-skip-task")).toMatchObject({ kind: "governance_skip_task", message: "跳过当前任务", task_id: "task-governance", }); expect(mockState.db?.outbox.get("task:upsert:task-governance")).toMatchObject({ entity_type: "task" }); expect(mockState.db?.outbox.get("plan_step:upsert:step-governance")).toMatchObject({ entity_type: "plan_step" }); expect(mockState.db?.outbox.get("run:upsert:run-governance")).toMatchObject({ entity_type: "run" }); }); it("marks dependent plan steps ready when skipped dependencies are satisfied", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); addPlanStep({ id: "step-dependent", title: "后续执行步骤", seq: 2, dependsOn: ["step-governance"] }); recordDigitalEmployeeGovernanceCommand({ commandKind: "skip_task", commandId: "command-unlock-dependent", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "跳过前置步骤" }, }); expect(mockState.db?.planSteps.get("step-dependent")).toMatchObject({ status: "ready", sync_status: "pending", }); expect(JSON.parse(mockState.db?.planSteps.get("step-dependent")?.payload ?? "{}")).toMatchObject({ dependencyGraph: { status: "ready", triggeredByStepId: "step-governance", satisfiedDependencies: ["step-governance"], }, }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "plan_step_ready", plan_id: "plan-governance" }), ])); expect(mockState.db?.outbox.get("plan_step:upsert:step-dependent")).toMatchObject({ entity_type: "plan_step", entity_id: "step-dependent", status: "pending", }); }); it("blocks dependent plan steps on cancellation and returns them to pending on retry", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); addPlanStep({ id: "step-dependent", title: "后续执行步骤", seq: 2, dependsOn: ["step-governance"] }); recordDigitalEmployeeGovernanceCommand({ commandKind: "cancel_task", commandId: "command-block-dependent", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "取消前置步骤" }, }); expect(mockState.db?.planSteps.get("step-dependent")).toMatchObject({ status: "blocked" }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "plan_step_blocked" }), ])); recordDigitalEmployeeGovernanceCommand({ commandKind: "retry_task", commandId: "command-wait-dependent", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "重试前置步骤" }, }); expect(mockState.db?.planSteps.get("step-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.planSteps.get("step-dependent")).toMatchObject({ status: "pending" }); expect(Array.from(mockState.db?.events.values() ?? [])).toEqual(expect.arrayContaining([ expect.objectContaining({ kind: "plan_step_waiting" }), ])); expect(JSON.parse(mockState.db?.planSteps.get("step-dependent")?.payload ?? "{}")).toMatchObject({ dependencyGraph: { status: "pending", waitingDependencies: ["step-governance"], }, }); }); it("waits for all dependencies before marking a plan step ready", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); addPlanStep({ id: "step-other", title: "第二个前置步骤", seq: 2 }); addGovernableTaskForStep({ taskId: "task-other", runId: "run-other", stepId: "step-other" }); addPlanStep({ id: "step-dependent", title: "汇总步骤", seq: 3, dependsOn: ["step-governance", "step-other"] }); recordDigitalEmployeeGovernanceCommand({ commandKind: "skip_task", commandId: "command-first-dependency", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, }); expect(mockState.db?.planSteps.get("step-dependent")).toMatchObject({ status: "pending" }); recordDigitalEmployeeGovernanceCommand({ commandKind: "skip_task", commandId: "command-second-dependency", planId: "plan-governance", taskId: "task-other", runId: "run-other", stepId: "step-other", accepted: true, }); expect(mockState.db?.planSteps.get("step-dependent")).toMatchObject({ status: "ready" }); expect(JSON.parse(mockState.db?.planSteps.get("step-dependent")?.payload ?? "{}")).toMatchObject({ dependencyGraph: { status: "ready", satisfiedDependencies: ["step-governance", "step-other"], }, }); }); it("does not overwrite already running or completed dependent plan steps", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); addPlanStep({ id: "step-running-dependent", status: "running", seq: 2, dependsOn: ["step-governance"] }); addPlanStep({ id: "step-completed-dependent", status: "completed", seq: 3, dependsOn: ["step-governance"] }); recordDigitalEmployeeGovernanceCommand({ commandKind: "skip_task", commandId: "command-preserve-dependent", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, }); expect(mockState.db?.planSteps.get("step-running-dependent")).toMatchObject({ status: "running", sync_status: "synced", }); expect(mockState.db?.planSteps.get("step-completed-dependent")).toMatchObject({ status: "completed", sync_status: "synced", }); expect(mockState.db?.outbox.has("plan_step:upsert:step-running-dependent")).toBe(false); expect(mockState.db?.outbox.has("plan_step:upsert:step-completed-dependent")).toBe(false); }); it("creates a retry run and preserves the failed source run", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask({ taskStatus: "failed", runStatus: "failed", finishedAt: "2026-06-07T07:30:00.000Z" }); const ids = recordDigitalEmployeeGovernanceCommand({ commandKind: "retry_task", commandId: "command-retry-task", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "失败后重试", prompt: "重新执行任务" }, result: { task_id: "task-governance", run_id: "run-governance", step_id: "step-governance" }, }); expect(ids.runId).toBe("task-governance:run:retry_task:command-retry-task"); expect(mockState.db?.tasks.get("task-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.planSteps.get("step-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.runs.get("run-governance")).toMatchObject({ status: "failed", finished_at: "2026-06-07T07:30:00.000Z", }); expect(mockState.db?.runs.get(ids.runId)).toMatchObject({ plan_id: "plan-governance", task_id: "task-governance", status: "running", finished_at: null, }); expect(JSON.parse(mockState.db?.runs.get(ids.runId)?.payload ?? "{}")).toMatchObject({ commandKind: "retry_task", sourceRunId: "run-governance", attemptNo: 2, }); expect(mockState.db?.outbox.get(`run:upsert:${ids.runId}`)).toMatchObject({ entity_type: "run" }); }); it("cancels the current task run without deleting run history", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask(); mockState.db?.runs.set("run-history", { id: "run-history", plan_id: "plan-governance", task_id: "task-governance", status: "completed", started_at: "2026-06-07T07:00:00.000Z", finished_at: "2026-06-07T07:05:00.000Z", payload: JSON.stringify({ source: "history" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:05:00.000Z", }); recordDigitalEmployeeGovernanceCommand({ commandKind: "cancel_task", commandId: "command-cancel-task", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "用户取消" }, }); expect(mockState.db?.tasks.get("task-governance")).toMatchObject({ status: "cancelled" }); expect(mockState.db?.planSteps.get("step-governance")).toMatchObject({ status: "cancelled" }); expect(mockState.db?.runs.get("run-governance")).toMatchObject({ status: "cancelled", finished_at: "2026-06-07T08:00:00.000Z", }); expect(mockState.db?.runs.get("run-history")).toMatchObject({ status: "completed", finished_at: "2026-06-07T07:05:00.000Z", }); }); it("resumes paused tasks and records provided task input", async () => { const { recordDigitalEmployeeGovernanceCommand } = await import("./stateService"); createGovernableTask({ taskStatus: "paused", stepStatus: "paused", runStatus: "paused" }); recordDigitalEmployeeGovernanceCommand({ commandKind: "provide_task_input", commandId: "command-provide-input", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { input: "继续使用最新客户名单" }, }); expect(mockState.db?.tasks.get("task-governance")).toMatchObject({ status: "paused" }); expect(JSON.parse(mockState.db?.tasks.get("task-governance")?.payload ?? "{}")).toMatchObject({ lastGovernanceCommand: { commandKind: "provide_task_input", payload: { input: "继续使用最新客户名单" }, }, }); recordDigitalEmployeeGovernanceCommand({ commandKind: "resume_task", commandId: "command-resume-task", planId: "plan-governance", taskId: "task-governance", runId: "run-governance", stepId: "step-governance", accepted: true, payload: { reason: "资料已补充" }, }); expect(mockState.db?.tasks.get("task-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.planSteps.get("step-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.runs.get("run-governance")).toMatchObject({ status: "running" }); expect(mockState.db?.events.get("run-governance:provide_task_input:command-provide-input")).toMatchObject({ kind: "governance_provide_task_input", }); expect(mockState.db?.events.get("run-governance:resume_task:command-resume-task")).toMatchObject({ kind: "governance_resume_task", }); }); it("upserts, lists, and syncs digital employee memories", async () => { const { listDigitalEmployeeMemories, readDigitalEmployeeRuntimeRecords, syncQimingclawMemoryEntries, upsertDigitalEmployeeMemory, } = await import("./stateService"); const localMemory = upsertDigitalEmployeeMemory({ key: "preferred-report-style", content: "用户偏好日报先给结论再列证据。", category: "preference", source: "qimingclaw-digital-local", score: 0.88, payload: { source: "test" }, }); expect(localMemory).toMatchObject({ id: "memory-preferred-report-style", key: "preferred-report-style", category: "preference", status: "active", syncStatus: "pending", }); expect(mockState.db?.outbox.get("memory:upsert:memory-preferred-report-style")).toMatchObject({ entity_type: "memory", entity_id: "memory-preferred-report-style", operation: "upsert", status: "pending", }); syncQimingclawMemoryEntries([ { id: "mem-from-service", text: "用户希望所有提交信息使用中文。", category: "preference", source: "core", sourcePath: "MEMORY.md", importance: 0.9, status: "active", createdAt: 1780800000000, updatedAt: 1780800300000, }, ]); expect(listDigitalEmployeeMemories({ query: "中文" })).toEqual([ expect.objectContaining({ id: "mem-from-service", key: "mem-from-service", content: "用户希望所有提交信息使用中文。", }), ]); expect(listDigitalEmployeeMemories({ category: "preference" })).toHaveLength(2); expect(readDigitalEmployeeRuntimeRecords().memories).toEqual(expect.arrayContaining([ expect.objectContaining({ id: "mem-from-service" }), expect.objectContaining({ id: "memory-preferred-report-style" }), ])); }); it("soft deletes digital employee memories and writes delete outbox", async () => { const { deleteDigitalEmployeeMemory, listDigitalEmployeeMemories, upsertDigitalEmployeeMemory, } = await import("./stateService"); upsertDigitalEmployeeMemory({ id: "memory-delete-me", key: "delete-me", content: "这条记忆将被删除。", category: "fact", }); const deleted = deleteDigitalEmployeeMemory("delete-me"); expect(deleted).toMatchObject({ id: "memory-delete-me", key: "delete-me", status: "deleted", deletedAt: "2026-06-07T08:00:00.000Z", syncStatus: "pending", }); expect(listDigitalEmployeeMemories({ includeDeleted: false })).toEqual([]); expect(listDigitalEmployeeMemories({ includeDeleted: true })).toEqual([ expect.objectContaining({ id: "memory-delete-me", status: "deleted" }), ]); expect(mockState.db?.outbox.get("memory:delete:memory-delete-me")).toMatchObject({ entity_type: "memory", entity_id: "memory-delete-me", operation: "delete", status: "pending", }); }); }); function createGovernableTask(options: { taskStatus?: string; stepStatus?: string; runStatus?: string; finishedAt?: string | null; } = {}): void { mockState.db?.plans.set("plan-governance", { id: "plan-governance", title: "步骤治理计划", objective: "验证任务治理闭环", status: "running", payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); mockState.db?.planSteps.set("step-governance", { id: "step-governance", plan_id: "plan-governance", title: "执行治理步骤", status: options.stepStatus ?? options.taskStatus ?? "running", seq: 1, depends_on: JSON.stringify([]), preferred_skill_ids: JSON.stringify(["qimingclaw-task-agent"]), payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); mockState.db?.tasks.set("task-governance", { id: "task-governance", plan_id: "plan-governance", title: "执行治理任务", status: options.taskStatus ?? "running", assigned_skill_id: "qimingclaw-task-agent", payload: JSON.stringify({ source: "test", stepId: "step-governance" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); mockState.db?.runs.set("run-governance", { id: "run-governance", plan_id: "plan-governance", task_id: "task-governance", status: options.runStatus ?? "running", started_at: "2026-06-07T07:10:00.000Z", finished_at: options.finishedAt ?? null, payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:10:00.000Z", updated_at: "2026-06-07T07:10:00.000Z", }); } function addPlanStep(options: { id: string; title?: string; status?: string; seq?: number; dependsOn?: string[]; }): void { mockState.db?.planSteps.set(options.id, { id: options.id, plan_id: "plan-governance", title: options.title ?? options.id, status: options.status ?? "pending", seq: options.seq ?? 2, depends_on: JSON.stringify(options.dependsOn ?? []), preferred_skill_ids: JSON.stringify([]), payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); } function addGovernableTaskForStep(options: { taskId: string; runId: string; stepId: string; status?: string; }): void { mockState.db?.tasks.set(options.taskId, { id: options.taskId, plan_id: "plan-governance", title: `${options.stepId} 任务`, status: options.status ?? "running", assigned_skill_id: "qimingclaw-task-agent", payload: JSON.stringify({ source: "test", stepId: options.stepId }), sync_status: "synced", created_at: "2026-06-07T07:00:00.000Z", updated_at: "2026-06-07T07:00:00.000Z", }); mockState.db?.runs.set(options.runId, { id: options.runId, plan_id: "plan-governance", task_id: options.taskId, status: options.status ?? "running", started_at: "2026-06-07T07:10:00.000Z", finished_at: null, payload: JSON.stringify({ source: "test" }), sync_status: "synced", created_at: "2026-06-07T07:10:00.000Z", updated_at: "2026-06-07T07:10:00.000Z", }); } interface TestPlanRow { id: string; title: string; objective: string; status: string; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestTaskRow { id: string; plan_id: string; title: string; status: string; assigned_skill_id: string; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestPlanStepRow { id: string; plan_id: string; title: string; status: string; seq: number; depends_on: string; preferred_skill_ids: string; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestRunRow { id: string; plan_id: string; task_id: string; status: string; started_at: string; finished_at: string | null; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestEventRow { id: string; plan_id: string; task_id: string; run_id: string | null; kind: string; message: string; payload: string; sync_status: string; occurred_at: string; created_at: string; } interface TestArtifactRow { id: string; plan_id: string; task_id: string; run_id: string; kind: string; name: string | null; uri: string | null; payload: string; sync_status: string; created_at: string; } interface TestApprovalRow { id: string; plan_id: string; task_id: string; run_id: string; title: string; status: string; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestMemoryRow { id: string; key: string; content: string; category: string; source: string; source_path: string | null; session_id: string | null; score: number | null; status: string; payload: string; sync_status: string; created_at: string; updated_at: string; deleted_at: string | null; } interface TestScheduleRow { id: string; plan_id: string | null; name: string; description: string | null; cron_expr: string; timezone: string; status: string; enabled: number; prompt: string | null; command: string | null; payload: string; next_run_at: string | null; last_run_at: string | null; catch_up_on_startup: number; max_catch_up_runs: number; max_run_history: number; failure_count: number; last_error: string | null; sync_status: string; created_at: string; updated_at: string; deleted_at: string | null; } interface TestScheduleRunRow { id: string; schedule_id: string; plan_id: string | null; run_id: string | null; due_at: string; started_at: string | null; finished_at: string | null; status: string; attempt_no: number; trigger_kind: string; error: string | null; payload: string; sync_status: string; created_at: string; updated_at: string; } interface TestOutboxRow { id: string; entity_type: string; entity_id: string; operation: string; payload: string; status: string; attempts: number; created_at: string; updated_at: string; error: string | null; } class TestDb { plans = new Map(); planSteps = new Map(); tasks = new Map(); runs = new Map(); events = new Map(); artifacts = new Map(); approvals = new Map(); memories = new Map(); schedules = new Map(); scheduleRuns = new Map(); outbox = new Map(); 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_plans")) return Array.from(this.plans.values()); if (sql.includes("FROM digital_plan_steps")) { const rows = Array.from(this.planSteps.values()); if (sql.includes("WHERE plan_id = ?")) { return rows .filter((step) => step.plan_id === args[0]) .sort((left, right) => left.seq - right.seq || left.created_at.localeCompare(right.created_at)); } return rows; } if (sql.includes("FROM digital_schedules")) return Array.from(this.schedules.values()); if (sql.includes("FROM digital_schedule_runs")) return Array.from(this.scheduleRuns.values()); if (sql.includes("FROM digital_tasks")) return Array.from(this.tasks.values()); if (sql.includes("FROM digital_runs")) return Array.from(this.runs.values()); if (sql.includes("FROM digital_events")) { const rows = Array.from(this.events.values()); const filtered = sql.includes("WHERE kind = 'route_decision'") ? rows.filter((event) => event.kind === "route_decision") : rows; return filtered.sort((left, right) => right.occurred_at.localeCompare(left.occurred_at) || right.created_at.localeCompare(left.created_at)); } if (sql.includes("FROM digital_artifacts")) return Array.from(this.artifacts.values()); if (sql.includes("FROM digital_approvals")) return Array.from(this.approvals.values()); if (sql.includes("FROM digital_memories")) return Array.from(this.memories.values()); return []; } private get(sql: string, args: unknown[]): unknown { if (sql.startsWith("SELECT COUNT(*) AS count FROM digital_runs")) { return { count: Array.from(this.runs.values()).filter((run) => run.task_id === args[0]).length }; } if (sql.startsWith("SELECT title, objective FROM digital_plans")) { return this.plans.get(args[0] as string); } if (sql.includes("FROM digital_tasks") && sql.includes("WHERE id = ?")) { return this.tasks.get(args[0] as string); } if (sql.includes("FROM digital_runs") && sql.includes("WHERE id = ?")) { return this.runs.get(args[0] as string); } if (sql.includes("FROM digital_runs") && sql.includes("WHERE task_id = ?")) { return Array.from(this.runs.values()) .filter((run) => run.task_id === args[0]) .sort((left, right) => right.updated_at.localeCompare(left.updated_at) || right.created_at.localeCompare(left.created_at))[0]; } if (sql.startsWith("SELECT id FROM digital_plan_steps")) { return Array.from(this.planSteps.values()) .filter((step) => step.plan_id === args[0]) .sort((left, right) => left.seq - right.seq || left.created_at.localeCompare(right.created_at))[0]; } if (sql.includes("FROM digital_plan_steps") && sql.includes("WHERE id = ?")) { return this.planSteps.get(args[0] as string); } if (sql.startsWith("SELECT id, plan_id, task_id, run_id, title, status, payload, created_at FROM digital_approvals")) { return this.approvals.get(args[0] as string); } if (sql.includes("FROM digital_schedules") && sql.includes("WHERE id = ?")) { return this.schedules.get(args[0] as string); } if (sql.includes("FROM digital_schedule_runs") && sql.includes("WHERE id = ?")) { return this.scheduleRuns.get(args[0] as string); } if (sql.includes("FROM digital_memories") && sql.includes("WHERE id = ? OR key = ?")) { return Array.from(this.memories.values()) .filter((memory) => memory.id === args[0] || memory.key === args[1]) .sort((left, right) => { const leftDeleted = left.status === "deleted" ? 1 : 0; const rightDeleted = right.status === "deleted" ? 1 : 0; return leftDeleted - rightDeleted || right.updated_at.localeCompare(left.updated_at) || right.created_at.localeCompare(left.created_at); })[0]; } if (sql.includes("FROM digital_memories") && sql.includes("WHERE id = ?")) { return this.memories.get(args[0] as string); } return { count: 0 }; } 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, string, string, string, string, ]; const previous = this.plans.get(id); this.plans.set(id, { id, title, objective, status, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_plan_steps")) { const [ id, planId, title, status, seq, dependsOn, preferredSkillIds, payload, createdAt, updatedAt, ] = args as [string, string, string, string, number, string, string, string, string, string]; const previous = this.planSteps.get(id); this.planSteps.set(id, { id, plan_id: planId, title, status, seq, depends_on: dependsOn, preferred_skill_ids: preferredSkillIds, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_schedules")) { const [ id, planId, name, description, cronExpr, timezone, status, enabled, prompt, command, payload, nextRunAt, lastRunAt, catchUpOnStartup, maxCatchUpRuns, maxRunHistory, failureCount, lastError, createdAt, updatedAt, deletedAt, ] = args as [string, string | null, string, string | null, string, string, string, number, string | null, string | null, string, string | null, string | null, number, number, number, number, string | null, string, string, string | null]; const previous = this.schedules.get(id); this.schedules.set(id, { id, plan_id: planId, name, description, cron_expr: cronExpr, timezone, status, enabled, prompt, command, payload, next_run_at: nextRunAt, last_run_at: lastRunAt, catch_up_on_startup: catchUpOnStartup, max_catch_up_runs: maxCatchUpRuns, max_run_history: maxRunHistory, failure_count: failureCount, last_error: lastError, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, deleted_at: deletedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_schedule_runs")) { const [id, scheduleId, planId, runId, dueAt, startedAt, status, attemptNo, triggerKind, payload, createdAt, updatedAt] = args as [ string, string, string | null, string | null, string, string, string, number, string, string, string, string, ]; const previous = this.scheduleRuns.get(id); this.scheduleRuns.set(id, { id, schedule_id: scheduleId, plan_id: planId, run_id: runId, due_at: dueAt, started_at: previous?.started_at ?? startedAt, finished_at: previous?.finished_at ?? null, status, attempt_no: attemptNo, trigger_kind: triggerKind, error: previous?.error ?? null, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_tasks")) { const [id, planId, title, status, assignedSkillId, payload, createdAt, updatedAt] = 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, assigned_skill_id: assignedSkillId, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_runs")) { const [id, planId, taskId, status, startedAt, payload, createdAt, updatedAt] = 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, started_at: previous?.started_at ?? startedAt, finished_at: previous?.finished_at ?? null, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("UPDATE digital_tasks")) { const [status, payload, updatedAt, id] = args as [string, string, string, string]; const previous = this.tasks.get(id); if (previous) { this.tasks.set(id, { ...previous, status, payload, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_plan_steps")) { const [status, payload, updatedAt, id] = args as [string, string, string, string]; const previous = this.planSteps.get(id); if (previous) { this.planSteps.set(id, { ...previous, status, payload, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_runs") && sql.includes("payload") && sql.includes("finished_at")) { const [status, payload, finishedAt, updatedAt, id] = args as [string, string, string, string, string]; const previous = this.runs.get(id); if (previous) { this.runs.set(id, { ...previous, status, payload, finished_at: finishedAt, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_runs") && sql.includes("payload")) { const [status, payload, updatedAt, id] = args as [string, string, string, string]; const previous = this.runs.get(id); if (previous) { this.runs.set(id, { ...previous, status, payload, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_runs")) { const [status, finishedAt, updatedAt, id] = args as [string, string, string, string]; const previous = this.runs.get(id); if (previous) { this.runs.set(id, { ...previous, status, finished_at: finishedAt, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("UPDATE digital_schedule_runs")) { const [status, finishedAt, error, payload, updatedAt, id] = args as [string, string, string | null, string, string, string]; const previous = this.scheduleRuns.get(id); if (previous) { this.scheduleRuns.set(id, { ...previous, status, finished_at: finishedAt, error, payload, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } if (sql.startsWith("INSERT OR IGNORE INTO digital_events")) { const [id, planId, taskId, runId, kind, message, payload, occurredAt, createdAt] = args as [ string, string, string, 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, sync_status: "pending", occurred_at: occurredAt, created_at: createdAt, }); } return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_artifacts")) { const [id, planId, taskId, runId, kind, name, uri, payload, createdAt] = args as [ string, string, string, string, string, string | null, string | null, string, string, ]; this.artifacts.set(id, { id, plan_id: planId, task_id: taskId, run_id: runId, kind, name, uri, payload, sync_status: "pending", created_at: createdAt, }); 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, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, }); return { changes: 1 }; } if (sql.startsWith("INSERT INTO digital_memories")) { const [id, key, content, category, source, sourcePath, sessionId, score, status, payload, createdAt, updatedAt, deletedAt] = args as [ string, string, string, string, string, string | null, string | null, number | null, string, string, string, string, string | null, ]; const previous = this.memories.get(id); this.memories.set(id, { id, key, content, category, source, source_path: sourcePath, session_id: sessionId, score, status, payload, sync_status: previous?.sync_status === "synced" ? "pending" : previous?.sync_status ?? "pending", created_at: previous?.created_at ?? createdAt, updated_at: updatedAt, deleted_at: deletedAt, }); return { changes: 1 }; } if (sql.startsWith("UPDATE digital_memories")) { const [deletedAt, updatedAt, id] = args as [string, string, string]; const previous = this.memories.get(id); if (previous) { this.memories.set(id, { ...previous, status: "deleted", deleted_at: deletedAt, updated_at: updatedAt, sync_status: "pending", }); } return { changes: previous ? 1 : 0 }; } 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, ]; this.outbox.set(id, { id, entity_type: entityType, entity_id: entityId, operation, payload, status: "pending", attempts: 0, created_at: createdAt, updated_at: updatedAt, error: null, }); return { changes: 1 }; } throw new Error(`Unhandled run query: ${sql}`); } }