feat(FN-4045): surface and handle delegate task collision errors
Added delegate collision detection and error surfacing across the task creation and delegation pipeline. The core store now throws a typed `DelegateCollisionError` when `createTask` encounters a duplicate task ID, the engine's `delegate_task` tool propagates these errors with context, and the CLI ex Fusion-Task-Id: FN-4045
This commit is contained in:
@@ -256,6 +256,22 @@ describe("createDelegateTaskTool", () => {
|
||||
expect(taskStore.createTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns explicit collision error when delegated createTask hits existing id", async () => {
|
||||
const agent = createAgent({ id: "agent-001", name: "Bob" });
|
||||
vi.mocked(agentStore.getAgent).mockResolvedValue(agent);
|
||||
vi.mocked(taskStore.createTask).mockRejectedValue(new Error("Task ID already exists: FN-050"));
|
||||
|
||||
const tool = createDelegateTaskTool(agentStore, taskStore);
|
||||
const result = await tool.execute("session-1", {
|
||||
agent_id: "agent-001",
|
||||
description: "Write tests",
|
||||
}, undefined as any, undefined as any, undefined as any);
|
||||
|
||||
expect((result as { isError?: boolean }).isError).toBe(true);
|
||||
expect((result.content[0] as { text: string }).text).toBe("ERROR: Task ID already exists: FN-050");
|
||||
expect(result.details).toEqual({});
|
||||
});
|
||||
|
||||
it("allows durable engineer target without override", async () => {
|
||||
const engineer = createAgent({ id: "agent-009", name: "Eli", role: "engineer" });
|
||||
vi.mocked(agentStore.getAgent).mockResolvedValue(engineer);
|
||||
|
||||
@@ -623,24 +623,35 @@ export function createTaskCreateTool(
|
||||
"or the current task should wait for the new one).",
|
||||
parameters: taskCreateParams,
|
||||
execute: async (_id: string, params: Static<typeof taskCreateParams>) => {
|
||||
const task = await createAgentTask(store, {
|
||||
description: params.description,
|
||||
dependencies: params.dependencies,
|
||||
column: "triage",
|
||||
source: provenance ? {
|
||||
sourceType: provenance.sourceType,
|
||||
sourceAgentId: provenance.sourceAgentId,
|
||||
sourceRunId: provenance.sourceRunId,
|
||||
} : undefined,
|
||||
}, options);
|
||||
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: `Created ${task.id}: ${params.description}${deps}`,
|
||||
}],
|
||||
details: { taskId: task.id },
|
||||
};
|
||||
try {
|
||||
const task = await createAgentTask(store, {
|
||||
description: params.description,
|
||||
dependencies: params.dependencies,
|
||||
column: "triage",
|
||||
source: provenance ? {
|
||||
sourceType: provenance.sourceType,
|
||||
sourceAgentId: provenance.sourceAgentId,
|
||||
sourceRunId: provenance.sourceRunId,
|
||||
} : undefined,
|
||||
}, options);
|
||||
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: `Created ${task.id}: ${params.description}${deps}`,
|
||||
}],
|
||||
details: { taskId: task.id },
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `ERROR: ${err.message}` }],
|
||||
details: {},
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1761,27 +1772,38 @@ export function createDelegateTaskTool(
|
||||
};
|
||||
}
|
||||
|
||||
// Create task assigned to the target agent
|
||||
const task = await createAgentTask(taskStore, {
|
||||
description: params.description,
|
||||
dependencies: params.dependencies,
|
||||
column: "todo",
|
||||
assignedAgentId: params.agent_id,
|
||||
source: {
|
||||
sourceType: "api",
|
||||
...(override ? { sourceMetadata: { executorRoleOverride: true } } : {}),
|
||||
},
|
||||
}, options);
|
||||
try {
|
||||
// Create task assigned to the target agent
|
||||
const task = await createAgentTask(taskStore, {
|
||||
description: params.description,
|
||||
dependencies: params.dependencies,
|
||||
column: "todo",
|
||||
assignedAgentId: params.agent_id,
|
||||
source: {
|
||||
sourceType: "api",
|
||||
...(override ? { sourceMetadata: { executorRoleOverride: true } } : {}),
|
||||
},
|
||||
}, options);
|
||||
|
||||
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. ` +
|
||||
`The task will be picked up by ${agent.name} on their next heartbeat cycle.`,
|
||||
}],
|
||||
details: { taskId: task.id, agentId: agent.id, agentName: agent.name },
|
||||
};
|
||||
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
||||
return {
|
||||
content: [{
|
||||
type: "text" as const,
|
||||
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. ` +
|
||||
`The task will be picked up by ${agent.name} on their next heartbeat cycle.`,
|
||||
}],
|
||||
details: { taskId: task.id, agentId: agent.id, agentName: agent.name },
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `ERROR: ${err.message}` }],
|
||||
details: {},
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user