Files
qiming/qimingclaw/crates/agent-electron-client/src/main/services/digitalEmployee/stateService.test.ts
2026-06-09 22:16:03 +08:00

2021 lines
66 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it, vi } from "vitest";
const mockState = vi.hoisted(() => ({
db: null as TestDb | null,
ensureSchemaCalls: 0,
settings: new Map<string, unknown>(),
}));
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<string, unknown>();
});
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<string, TestPlanRow>();
planSteps = new Map<string, TestPlanStepRow>();
tasks = new Map<string, TestTaskRow>();
runs = new Map<string, TestRunRow>();
events = new Map<string, TestEventRow>();
artifacts = new Map<string, TestArtifactRow>();
approvals = new Map<string, TestApprovalRow>();
memories = new Map<string, TestMemoryRow>();
schedules = new Map<string, TestScheduleRow>();
scheduleRuns = new Map<string, TestScheduleRunRow>();
outbox = new Map<string, TestOutboxRow>();
transaction<T>(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}`);
}
}