吸收数字员工计划步骤依赖图
This commit is contained in:
@@ -255,6 +255,16 @@ describe("digital employee state service", () => {
|
||||
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",
|
||||
@@ -270,9 +280,54 @@ describe("digital employee state service", () => {
|
||||
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");
|
||||
|
||||
@@ -324,6 +379,20 @@ interface TestTaskRow {
|
||||
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;
|
||||
@@ -391,6 +460,7 @@ interface TestOutboxRow {
|
||||
|
||||
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>();
|
||||
@@ -409,12 +479,23 @@ class TestDb {
|
||||
} {
|
||||
const normalized = sql.replace(/\s+/g, " ").trim();
|
||||
return {
|
||||
all: () => [],
|
||||
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);
|
||||
@@ -450,6 +531,36 @@ class TestDb {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user