744 lines
22 KiB
TypeScript
744 lines
22 KiB
TypeScript
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("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",
|
|
});
|
|
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",
|
|
});
|
|
});
|
|
});
|
|
|
|
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;
|
|
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 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>();
|
|
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")) return Array.from(this.planSteps.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")) return Array.from(this.events.values());
|
|
if (sql.includes("FROM digital_artifacts")) return Array.from(this.artifacts.values());
|
|
if (sql.includes("FROM digital_approvals")) return Array.from(this.approvals.values());
|
|
return [];
|
|
}
|
|
|
|
private get(sql: string, args: unknown[]): unknown {
|
|
if (sql.startsWith("SELECT title, objective FROM digital_plans")) {
|
|
return this.plans.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);
|
|
}
|
|
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_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_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("INSERT OR IGNORE INTO digital_events")) {
|
|
const [id, planId, taskId, runId, kind, message, payload, occurredAt, createdAt] = args as [
|
|
string,
|
|
string,
|
|
string,
|
|
string,
|
|
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_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}`);
|
|
}
|
|
}
|