吸收数字员工PlanStep依赖图派发策略

This commit is contained in:
baiyanyun
2026-06-10 18:41:33 +08:00
parent 3ac350eb27
commit d679639175
14 changed files with 795 additions and 212 deletions

View File

@@ -14,6 +14,12 @@ const mockState = vi.hoisted(() => ({
max_catch_up_runs: 5,
max_concurrent_schedule_runs: 1,
},
dispatchPolicy: {
enabled: true,
max_per_sweep: 3,
auto_dispatch_ready_steps: true,
updated_at: null as string | null,
},
}));
vi.mock("../startupPorts", () => ({
@@ -26,6 +32,7 @@ vi.mock("../constants", () => ({
vi.mock("./stateService", () => ({
readDigitalEmployeeCronSettings: () => mockState.settings,
readDigitalEmployeePlanStepDispatchPolicy: () => mockState.dispatchPolicy,
listDigitalEmployeeReadyPlanStepDispatchCandidates: () => mockState.readyCandidates,
listDigitalEmployeeSchedules: () => mockState.schedules,
listDueDigitalEmployeeSchedules: () => mockState.schedules,
@@ -62,6 +69,12 @@ describe("digital employee scheduler service", () => {
mockState.finishedRuns = [];
mockState.dispatchEvents = [];
mockState.governanceCommands = [];
mockState.dispatchPolicy = {
enabled: true,
max_per_sweep: 3,
auto_dispatch_ready_steps: true,
updated_at: null,
};
mockState.schedules = [{
id: "schedule-1",
planId: "plan-1",
@@ -165,6 +178,45 @@ describe("digital employee scheduler service", () => {
expect(mockState.dispatchEvents.map((event) => event.status)).toEqual(["started", "failed"]);
expect(mockState.governanceCommands).toHaveLength(0);
});
it("honors disabled ready step dispatch policy", async () => {
mockState.readyCandidates = [readyStepCandidate()];
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, enabled: false };
const { dispatchReadyPlanSteps } = await import("./schedulerService");
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
expect(result).toMatchObject({ candidates: 1, dispatched: 0, skipped: 1, failed: 0 });
expect(fetch).not.toHaveBeenCalled();
expect(mockState.dispatchEvents).toHaveLength(0);
});
it("honors ready step auto dispatch switch", async () => {
mockState.readyCandidates = [readyStepCandidate()];
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, auto_dispatch_ready_steps: false };
const { dispatchReadyPlanSteps } = await import("./schedulerService");
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
expect(result).toMatchObject({ candidates: 1, dispatched: 0, skipped: 1, failed: 0 });
expect(fetch).not.toHaveBeenCalled();
expect(mockState.dispatchEvents).toHaveLength(0);
});
it("limits ready step dispatches by policy max per sweep", async () => {
mockState.readyCandidates = [
readyStepCandidate(),
{ ...readyStepCandidate(), stepId: "step-ready-2", taskId: "task-2" },
];
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, max_per_sweep: 1 };
const { dispatchReadyPlanSteps } = await import("./schedulerService");
const result = await dispatchReadyPlanSteps(new Date("2026-06-07T01:00:10.000Z"));
expect(result).toMatchObject({ candidates: 2, dispatched: 1, skipped: 1, failed: 0 });
expect(fetch).toHaveBeenCalledTimes(1);
expect(mockState.dispatchEvents.map((event) => event.stepId)).toEqual(["step-ready", "step-ready"]);
});
});
function readyStepCandidate(): Record<string, unknown> {