feat(FN-3935): align engineer role routing with explicit assignment and del

Centralized routing policy helpers in core and aligned both direct assignment and delegation to route engineer-role agents consistently, ensuring assigned tasks respect explicit engineer routing the same way delegated tasks do. Added comprehensive test coverage across core, CLI, dashboard, and engin

Fusion-Task-Id: FN-3935

Fusion-Task-Lineage: 069f4d54-f7a8-4bd3-a2c3-4de830de042f
This commit is contained in:
Fusion
2026-05-11 20:20:43 -07:00
committed by gsxdsm
parent f76867301c
commit e4ec922212
18 changed files with 342 additions and 29 deletions

View File

@@ -2371,6 +2371,7 @@ describe("PATCH /tasks/:id/assign and GET /agents/:id/tasks", () => {
let fusionDir: string;
let agentId: string;
let reviewerAgentId: string;
let engineerAgentId: string;
let store: TaskStore;
// Agent store init + createAgent is ~50ms per call; hoisted to beforeAll
@@ -2391,8 +2392,13 @@ describe("PATCH /tasks/:id/assign and GET /agents/:id/tasks", () => {
name: "Assignment reviewer agent",
role: "reviewer",
});
const engineer = await agentStore.createAgent({
name: "Assignment engineer agent",
role: "engineer",
});
agentId = agent.id;
reviewerAgentId = reviewer.id;
engineerAgentId = engineer.id;
}, 30_000);
beforeEach(() => {
@@ -2432,7 +2438,26 @@ describe("PATCH /tasks/:id/assign and GET /agents/:id/tasks", () => {
expect(res.body.assignedAgentId).toBe(agentId);
}, 20000);
it("returns 409 when assigning implementation task to non-executor without override", async () => {
it("allows assigning implementation task to durable engineer without override", async () => {
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue({
...FAKE_TASK_DETAIL,
id: "FN-200",
assignedAgentId: engineerAgentId,
});
const res = await REQUEST(
buildApp(),
"PATCH",
"/api/tasks/FN-200/assign",
JSON.stringify({ agentId: engineerAgentId }),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(200);
expect(store.updateTask).toHaveBeenCalledWith("FN-200", { assignedAgentId: engineerAgentId });
}, 20000);
it("returns 409 when assigning implementation task to reviewer without override", async () => {
const res = await REQUEST(
buildApp(),
"PATCH",

View File

@@ -10,7 +10,7 @@ import {
resolveTitleSummarizerSettingsModel,
toReplicatedCreateInput,
validateNodeOverrideChange,
canAgentTakeImplementationTask,
canAgentTakeImplementationTaskForExplicitRouting,
formatRoleMismatchReason,
getCurrentRepo,
} from "@fusion/core";
@@ -1880,7 +1880,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
throw notFound("Task not found");
}
if (override !== true && !canAgentTakeImplementationTask(agent, targetTask)) {
if (override !== true && !canAgentTakeImplementationTaskForExplicitRouting(agent, targetTask)) {
throw new ApiError(409, formatRoleMismatchReason(agent, targetTask));
}
}