Integrate master agent runtime orchestration updates
This commit is contained in:
@@ -7,6 +7,7 @@ import { NextRequest } from "next/server";
|
||||
|
||||
let runtimeRoot = "";
|
||||
let postMessageRoute: (typeof import("../src/app/api/v1/projects/[projectId]/messages/route"))["POST"];
|
||||
let getProjectRoute: (typeof import("../src/app/api/v1/projects/[projectId]/route"))["GET"];
|
||||
let getDispatchPlansRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/route"))["GET"];
|
||||
let confirmDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm/route"))["POST"];
|
||||
let rejectDispatchPlanRoute: (typeof import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/reject/route"))["POST"];
|
||||
@@ -29,8 +30,9 @@ async function setup() {
|
||||
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
||||
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
||||
|
||||
const [messageModule, plansModule, confirmModule, rejectModule, retryModule, reminderModule, data, auth] = await Promise.all([
|
||||
const [messageModule, projectModule, plansModule, confirmModule, rejectModule, retryModule, reminderModule, data, auth] = await Promise.all([
|
||||
import("../src/app/api/v1/projects/[projectId]/messages/route.ts"),
|
||||
import("../src/app/api/v1/projects/[projectId]/route.ts"),
|
||||
import("../src/app/api/v1/projects/[projectId]/dispatch-plans/route.ts"),
|
||||
import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm/route.ts"),
|
||||
import("../src/app/api/v1/projects/[projectId]/dispatch-plans/[planId]/reject/route.ts"),
|
||||
@@ -41,6 +43,7 @@ async function setup() {
|
||||
]);
|
||||
|
||||
postMessageRoute = messageModule.POST;
|
||||
getProjectRoute = projectModule.GET;
|
||||
getDispatchPlansRoute = plansModule.GET;
|
||||
confirmDispatchPlanRoute = confirmModule.POST;
|
||||
rejectDispatchPlanRoute = rejectModule.POST;
|
||||
@@ -334,6 +337,234 @@ test("POST /api/v1/projects/[projectId]/dispatch-plans/[planId]/confirm confirms
|
||||
assert.equal(executionTask?.orchestrationBackendLabel, "Boss Native Orchestrator");
|
||||
});
|
||||
|
||||
test("GET /api/v1/projects/[projectId]/dispatch-plans includes execution summaries after confirmation", async () => {
|
||||
const { groupProject, dispatchPlan } = await createDispatchPlanForTest();
|
||||
const approvedTargetProjectId = dispatchPlan.targets[0]?.projectId;
|
||||
assert.ok(approvedTargetProjectId, "expected a recommended target project");
|
||||
|
||||
const confirmResponse = await confirmDispatchPlanRoute(
|
||||
await createAuthedRequest(
|
||||
`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/confirm`,
|
||||
"POST",
|
||||
{ approvedTargetProjectIds: [approvedTargetProjectId] },
|
||||
),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id, planId: dispatchPlan.planId }) },
|
||||
);
|
||||
assert.equal(confirmResponse.status, 200);
|
||||
|
||||
const response = await getDispatchPlansRoute(
|
||||
await createAuthedRequest(
|
||||
`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans`,
|
||||
"GET",
|
||||
),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id }) },
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
plans: Array<{
|
||||
planId: string;
|
||||
executions?: Array<{
|
||||
executionId: string;
|
||||
targetProjectId: string;
|
||||
targetThreadId: string;
|
||||
status: string;
|
||||
resultMessageId?: string;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.plans[0]?.planId, dispatchPlan.planId);
|
||||
assert.ok(payload.plans[0]?.executions?.[0], "expected confirmed plan to expose its execution summaries");
|
||||
assert.equal(payload.plans[0]?.executions?.[0]?.targetProjectId, approvedTargetProjectId);
|
||||
assert.equal(payload.plans[0]?.executions?.[0]?.status, "queued");
|
||||
});
|
||||
|
||||
test("GET /api/v1/projects/[projectId] includes group dispatch and participant state for the chat surface", async () => {
|
||||
const { groupProject, dispatchPlan } = await createDispatchPlanForTest();
|
||||
|
||||
const response = await getProjectRoute(
|
||||
await createAuthedRequest(`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}`, "GET"),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id }) },
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
dispatchPlans?: Array<{
|
||||
planId: string;
|
||||
status?: string;
|
||||
summary?: string;
|
||||
targets?: Array<{ projectId: string; threadDisplayName: string }>;
|
||||
executions?: Array<{ executionId: string; status: string }>;
|
||||
}>;
|
||||
participantsPayload?: {
|
||||
projectId: string;
|
||||
participants: Array<{ projectId: string; status?: string; canOpenProject?: boolean }>;
|
||||
repairRequired: boolean;
|
||||
};
|
||||
};
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.dispatchPlans?.[0]?.planId, dispatchPlan.planId);
|
||||
assert.equal(payload.dispatchPlans?.[0]?.status, "pending_user_confirmation");
|
||||
assert.ok(payload.dispatchPlans?.[0]?.summary, "expected project detail to include dispatch summary");
|
||||
assert.ok(payload.dispatchPlans?.[0]?.targets?.length, "expected project detail to include dispatch targets");
|
||||
assert.equal(payload.dispatchPlans?.[0]?.targets?.[0]?.projectId, dispatchPlan.targets[0]?.projectId);
|
||||
assert.ok(payload.participantsPayload, "expected project detail to include participantsPayload");
|
||||
assert.equal(payload.participantsPayload?.projectId, groupProject.id);
|
||||
assert.equal(payload.participantsPayload?.repairRequired, false);
|
||||
assert.ok((payload.participantsPayload?.participants.length ?? 0) >= 2);
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.status, "active");
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.canOpenProject, true);
|
||||
});
|
||||
|
||||
test("GET /api/v1/projects/[projectId] includes dispatch execution summaries after confirmation", async () => {
|
||||
const { groupProject, dispatchPlan } = await createDispatchPlanForTest();
|
||||
const approvedTargetProjectId = dispatchPlan.targets[0]?.projectId;
|
||||
assert.ok(approvedTargetProjectId, "expected a recommended target project");
|
||||
|
||||
const confirmResponse = await confirmDispatchPlanRoute(
|
||||
await createAuthedRequest(
|
||||
`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}/dispatch-plans/${dispatchPlan.planId}/confirm`,
|
||||
"POST",
|
||||
{ approvedTargetProjectIds: [approvedTargetProjectId] },
|
||||
),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id, planId: dispatchPlan.planId }) },
|
||||
);
|
||||
assert.equal(confirmResponse.status, 200);
|
||||
|
||||
const response = await getProjectRoute(
|
||||
await createAuthedRequest(`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}`, "GET"),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id }) },
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
dispatchPlans?: Array<{
|
||||
planId: string;
|
||||
executions?: Array<{
|
||||
executionId: string;
|
||||
targetProjectId: string;
|
||||
targetThreadId: string;
|
||||
status: string;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.dispatchPlans?.[0]?.planId, dispatchPlan.planId);
|
||||
assert.ok(payload.dispatchPlans?.[0]?.executions?.[0], "expected project detail to include confirmed execution summaries");
|
||||
assert.equal(payload.dispatchPlans?.[0]?.executions?.[0]?.targetProjectId, approvedTargetProjectId);
|
||||
assert.equal(payload.dispatchPlans?.[0]?.executions?.[0]?.status, "queued");
|
||||
});
|
||||
|
||||
test("GET /api/v1/projects/[projectId] marks invalid group members as repair-required in detail payload", async () => {
|
||||
const singles = await ensureTwoSingleThreadProjects();
|
||||
const groupProject = await createProjectGroupChat({
|
||||
sourceProjectId: singles[0].id,
|
||||
memberProjectIds: [singles[1].id],
|
||||
createdBy: "17600003315",
|
||||
});
|
||||
|
||||
const state = await readState();
|
||||
await writeState({
|
||||
...state,
|
||||
projects: state.projects.map((project) =>
|
||||
project.id === groupProject.id
|
||||
? {
|
||||
...project,
|
||||
groupMembers: [
|
||||
{
|
||||
projectId: "master-agent",
|
||||
deviceId: "mac-studio",
|
||||
threadId: "master-agent-thread",
|
||||
threadDisplayName: "主 Agent 汇总",
|
||||
folderName: "主控线程",
|
||||
},
|
||||
],
|
||||
}
|
||||
: project,
|
||||
),
|
||||
});
|
||||
|
||||
const response = await getProjectRoute(
|
||||
await createAuthedRequest(`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}`, "GET"),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id }) },
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
participantsPayload?: {
|
||||
repairRequired: boolean;
|
||||
validParticipantCount: number;
|
||||
invalidParticipantCount: number;
|
||||
participants: Array<{ projectId: string; status?: string; canOpenProject?: boolean }>;
|
||||
};
|
||||
};
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.participantsPayload?.repairRequired, true);
|
||||
assert.equal(payload.participantsPayload?.validParticipantCount, 0);
|
||||
assert.equal(payload.participantsPayload?.invalidParticipantCount, 1);
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.projectId, "master-agent");
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.status, "invalid_target");
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.canOpenProject, true);
|
||||
});
|
||||
|
||||
test("GET /api/v1/projects/[projectId] marks missing group members as repair-required in detail payload", async () => {
|
||||
const singles = await ensureTwoSingleThreadProjects();
|
||||
const groupProject = await createProjectGroupChat({
|
||||
sourceProjectId: singles[0].id,
|
||||
memberProjectIds: [singles[1].id],
|
||||
createdBy: "17600003315",
|
||||
});
|
||||
|
||||
const state = await readState();
|
||||
await writeState({
|
||||
...state,
|
||||
projects: state.projects.map((project) =>
|
||||
project.id === groupProject.id
|
||||
? {
|
||||
...project,
|
||||
groupMembers: [
|
||||
{
|
||||
projectId: "missing-project-1",
|
||||
deviceId: "mac-studio",
|
||||
threadId: "missing-thread-1",
|
||||
threadDisplayName: "丢失线程引用",
|
||||
folderName: "异常引用",
|
||||
},
|
||||
],
|
||||
}
|
||||
: project,
|
||||
),
|
||||
});
|
||||
|
||||
const response = await getProjectRoute(
|
||||
await createAuthedRequest(`http://127.0.0.1:3000/api/v1/projects/${groupProject.id}`, "GET"),
|
||||
{ params: Promise.resolve({ projectId: groupProject.id }) },
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean;
|
||||
participantsPayload?: {
|
||||
repairRequired: boolean;
|
||||
validParticipantCount: number;
|
||||
invalidParticipantCount: number;
|
||||
participants: Array<{ projectId: string; status?: string; canOpenProject?: boolean }>;
|
||||
};
|
||||
};
|
||||
assert.equal(payload.ok, true);
|
||||
assert.equal(payload.participantsPayload?.repairRequired, true);
|
||||
assert.equal(payload.participantsPayload?.validParticipantCount, 0);
|
||||
assert.equal(payload.participantsPayload?.invalidParticipantCount, 1);
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.projectId, "missing-project-1");
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.status, "missing_project");
|
||||
assert.equal(payload.participantsPayload?.participants[0]?.canOpenProject, false);
|
||||
});
|
||||
|
||||
test("confirming a dispatch plan with rememberLightReminder persists the group reminder preference", async () => {
|
||||
const { groupProject, dispatchPlan } = await createDispatchPlanForTest();
|
||||
const approvedTargetProjectId = dispatchPlan.targets[0]?.projectId;
|
||||
|
||||
Reference in New Issue
Block a user