feat(FN-3777): add create_agent and delete_agent tools

Adds agent creation and deletion tools to the pi extension, with engine-side implementation in `agent-tools.ts`, core store integration for agent lifecycle management, and corresponding tests and documentation updates across the workspace.

Fusion-Task-Id: FN-3777
This commit is contained in:
Fusion
2026-05-08 19:02:59 -07:00
committed by gsxdsm
parent e04af96d9c
commit 3a91534f98
18 changed files with 352 additions and 19 deletions

View File

@@ -1247,6 +1247,24 @@ describe("AgentStore", () => {
expect(handler).toHaveBeenCalledOnce();
expect(handler).toHaveBeenCalledWith(created.id);
});
it("blocks delete when checked-out assigned task exists unless force=true", async () => {
const taskStore = new TaskStore(rootDir, join(rootDir, ".fusion-global-settings"));
await taskStore.init();
const linkedStore = new AgentStore({ rootDir, inMemoryDb: true, taskStore });
await linkedStore.init();
const created = await linkedStore.createAgent({ name: "Checked Out", role: "executor" });
const task = await taskStore.createTask({ title: "T", description: "D", column: "todo", assignedAgentId: created.id });
await taskStore.updateTask(task.id, { checkedOutBy: created.id });
await expect(linkedStore.deleteAgent(created.id)).rejects.toThrow("holds checkout");
await linkedStore.deleteAgent(created.id, { force: true });
expect(await taskStore.getTask(task.id)).toEqual(expect.objectContaining({ assignedAgentId: undefined, checkedOutBy: undefined }));
linkedStore.close();
taskStore.close();
});
});
// ── listAgents ────────────────────────────────────────────────────

View File

@@ -1627,13 +1627,27 @@ export class AgentStore extends EventEmitter {
* @param agentId - The agent ID
* @throws Error if agent not found
*/
async deleteAgent(agentId: string): Promise<void> {
async deleteAgent(agentId: string, options?: { force?: boolean; reassignTo?: string }): Promise<void> {
await this.withLock(agentId, async () => {
const agent = await this.getAgent(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
if (this.taskStore && typeof (this.taskStore as { getTasksByAssignedAgent?: unknown }).getTasksByAssignedAgent === "function") {
const assignedTasks = await this.taskStore.getTasksByAssignedAgent(agentId);
for (const task of assignedTasks) {
if (task.checkedOutBy === agentId && options?.force !== true) {
throw new Error(`Agent ${agentId} holds checkout for task ${task.id}; pass force=true to delete`);
}
await this.taskStore.updateTask(task.id, {
assignedAgentId: options?.reassignTo ?? null,
checkedOutBy: task.checkedOutBy === agentId ? null : undefined,
});
}
}
this.db.prepare("DELETE FROM agents WHERE id = ?").run(agentId);
this.db.bumpLastModified();