吸收数字员工PlanStep跨任务编排执行
This commit is contained in:
@@ -33,7 +33,10 @@ vi.mock("../constants", () => ({
|
||||
vi.mock("./stateService", () => ({
|
||||
readDigitalEmployeeCronSettings: () => mockState.settings,
|
||||
readDigitalEmployeePlanStepDispatchPolicy: () => mockState.dispatchPolicy,
|
||||
listDigitalEmployeeReadyPlanStepDispatchCandidates: () => mockState.readyCandidates,
|
||||
listDigitalEmployeeReadyPlanStepDispatchCandidates: (options?: number | { planId?: string | null }) => {
|
||||
const planId = typeof options === "object" && options ? options.planId : null;
|
||||
return planId ? mockState.readyCandidates.filter((candidate) => candidate.planId === planId) : mockState.readyCandidates;
|
||||
},
|
||||
listDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
listDueDigitalEmployeeSchedules: () => mockState.schedules,
|
||||
readDigitalEmployeeScheduleRuns: () => [],
|
||||
@@ -203,6 +206,58 @@ describe("digital employee scheduler service", () => {
|
||||
expect(mockState.dispatchEvents).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("allows manual plan dispatch to bypass the 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({
|
||||
planId: "plan-1",
|
||||
triggerSource: "manual",
|
||||
bypassAutoDispatchSwitch: true,
|
||||
now: new Date("2026-06-07T01:00:10.000Z"),
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ plan_id: "plan-1", trigger_source: "manual", candidates: 1, dispatched: 1, skipped: 0, failed: 0 });
|
||||
expect(mockState.dispatchEvents[0]).toMatchObject({ status: "started", payload: expect.objectContaining({ trigger_source: "manual" }) });
|
||||
});
|
||||
|
||||
it("keeps the enabled switch as a manual plan dispatch kill switch", async () => {
|
||||
mockState.readyCandidates = [readyStepCandidate()];
|
||||
mockState.dispatchPolicy = { ...mockState.dispatchPolicy, enabled: false, auto_dispatch_ready_steps: false };
|
||||
const { dispatchReadyPlanSteps } = await import("./schedulerService");
|
||||
|
||||
const result = await dispatchReadyPlanSteps({
|
||||
planId: "plan-1",
|
||||
triggerSource: "manual",
|
||||
bypassAutoDispatchSwitch: true,
|
||||
now: 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("dispatches only the requested plan in manual plan dispatch", async () => {
|
||||
mockState.readyCandidates = [
|
||||
readyStepCandidate(),
|
||||
readyStepCandidate({ planId: "plan-2", stepId: "step-plan-2", taskId: "task-plan-2" }),
|
||||
];
|
||||
const { dispatchReadyPlanSteps } = await import("./schedulerService");
|
||||
|
||||
const result = await dispatchReadyPlanSteps({
|
||||
planId: "plan-2",
|
||||
triggerSource: "manual",
|
||||
bypassAutoDispatchSwitch: true,
|
||||
now: new Date("2026-06-07T01:00:10.000Z"),
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ plan_id: "plan-2", candidates: 1, dispatched: 1, skipped: 0, failed: 0 });
|
||||
expect(result.dispatched_step_ids).toEqual(["step-plan-2"]);
|
||||
expect(mockState.dispatchEvents.map((event) => event.stepId)).toEqual(["step-plan-2", "step-plan-2"]);
|
||||
});
|
||||
|
||||
it("limits ready step dispatches by policy max per sweep", async () => {
|
||||
mockState.readyCandidates = [
|
||||
readyStepCandidate(),
|
||||
@@ -219,18 +274,22 @@ describe("digital employee scheduler service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function readyStepCandidate(): Record<string, unknown> {
|
||||
function readyStepCandidate(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const stepId = typeof overrides.stepId === "string" ? overrides.stepId : "step-ready";
|
||||
const taskId = typeof overrides.taskId === "string" ? overrides.taskId : "task-1";
|
||||
const planId = typeof overrides.planId === "string" ? overrides.planId : "plan-1";
|
||||
return {
|
||||
stepId: "step-ready",
|
||||
planId: "plan-1",
|
||||
taskId: "task-1",
|
||||
stepId,
|
||||
planId,
|
||||
taskId,
|
||||
title: "汇总客户跟进",
|
||||
taskTitle: "执行客户跟进汇总",
|
||||
prompt: "执行客户跟进汇总",
|
||||
preferredSkillIds: ["qimingclaw-acp-session"],
|
||||
dispatchable: true,
|
||||
step: { id: "step-ready", payload: {} },
|
||||
task: { id: "task-1", payload: {} },
|
||||
plan: { id: "plan-1" },
|
||||
step: { id: stepId, payload: {} },
|
||||
task: { id: taskId, payload: {} },
|
||||
plan: { id: planId },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user