import { describe, it, expect, vi, beforeEach } from "vitest"; import type { Agent, AgentStore, TaskStore, Task } from "@fusion/core"; import { createListAgentsTool, createDelegateTaskTool } from "../agent-tools.js"; function createMockAgentStore(overrides: Partial = {}): AgentStore { return { listAgents: vi.fn().mockResolvedValue([]), getAgent: vi.fn().mockResolvedValue(null), ...overrides, } as unknown as AgentStore; } function createMockTaskStore(overrides: Partial = {}): TaskStore { return { getSettings: vi.fn().mockResolvedValue({ autoSummarizeTitles: false }), createTask: vi.fn().mockResolvedValue({ id: "FN-001", description: "", dependencies: [], column: "triage" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }), ...overrides, } as unknown as TaskStore; } function createAgent(overrides: Partial = {}): Agent { const now = new Date().toISOString(); return { id: "agent-001", name: "Test Agent", role: "executor", state: "idle", createdAt: now, updatedAt: now, metadata: {}, ...overrides, }; } describe("createListAgentsTool", () => { let agentStore: AgentStore; beforeEach(() => { agentStore = createMockAgentStore(); }); it("returns formatted list of agents with their details", async () => { const agents = [ createAgent({ id: "agent-001", name: "Alice", role: "executor", state: "idle", taskId: undefined }), createAgent({ id: "agent-002", name: "Bob", role: "reviewer", state: "running", taskId: "FN-100" }), ]; vi.mocked(agentStore.listAgents).mockResolvedValue(agents); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); expect(result.content[0]).toHaveProperty("text"); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Available agents:"); expect(text).toContain("ID: agent-001"); expect(text).toContain("Name: Alice"); expect(text).toContain("Role: executor"); expect(text).toContain("State: idle"); expect(text).toContain("ID: agent-002"); expect(text).toContain("Name: Bob"); expect(text).toContain("Role: reviewer"); expect(text).toContain("State: running"); expect(text).toContain("Current Task: FN-100"); }); it("includes soul truncated to 200 chars when present", async () => { const longSoul = "A".repeat(300); const agent = createAgent({ id: "agent-001", soul: longSoul }); vi.mocked(agentStore.listAgents).mockResolvedValue([agent]); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Soul: " + "A".repeat(200)); expect(text).not.toContain("Soul: " + "A".repeat(201)); }); it("includes title when present", async () => { const agent = createAgent({ id: "agent-001", title: "Senior Engineer" }); vi.mocked(agentStore.listAgents).mockResolvedValue([agent]); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Title: Senior Engineer"); }); it("includes instructionsText summary when present", async () => { const agent = createAgent({ id: "agent-001", instructionsText: "Be thorough and check edge cases." }); vi.mocked(agentStore.listAgents).mockResolvedValue([agent]); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Custom Instructions: Be thorough and check edge cases."); }); it("includes instructionsText truncated to 100 chars with ellipsis", async () => { const longInstructions = "X".repeat(150); const agent = createAgent({ id: "agent-001", instructionsText: longInstructions }); vi.mocked(agentStore.listAgents).mockResolvedValue([agent]); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Custom Instructions: " + "X".repeat(100) + "…"); expect(text).not.toContain("Custom Instructions: " + "X".repeat(101)); }); it("filters by role when provided", async () => { const tool = createListAgentsTool(agentStore); await tool.execute("session-1", { role: "executor" }, undefined as any, undefined as any, undefined as any); expect(agentStore.listAgents).toHaveBeenCalledWith({ role: "executor" }); }); it("filters by state when provided", async () => { const tool = createListAgentsTool(agentStore); await tool.execute("session-1", { state: "idle" }, undefined as any, undefined as any, undefined as any); expect(agentStore.listAgents).toHaveBeenCalledWith({ state: "idle" }); }); it("passes includeEphemeral when provided", async () => { const tool = createListAgentsTool(agentStore); await tool.execute("session-1", { includeEphemeral: true }, undefined as any, undefined as any, undefined as any); expect(agentStore.listAgents).toHaveBeenCalledWith({ includeEphemeral: true }); }); it("returns no-agents message when list is empty", async () => { vi.mocked(agentStore.listAgents).mockResolvedValue([]); const tool = createListAgentsTool(agentStore); const result = await tool.execute("session-1", {}, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("No agents found matching the specified filters."); }); }); describe("createDelegateTaskTool", () => { let agentStore: AgentStore; let taskStore: TaskStore; beforeEach(() => { agentStore = createMockAgentStore(); taskStore = createMockTaskStore(); }); it("creates task with correct assignedAgentId, column todo, and description", async () => { const agent = createAgent({ id: "agent-001", name: "Bob" }); vi.mocked(agentStore.getAgent).mockResolvedValue(agent); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-050", description: "Write tests", dependencies: [], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); 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(taskStore.createTask).toHaveBeenCalledWith({ description: "Write tests", dependencies: undefined, column: "todo", assignedAgentId: "agent-001", source: { sourceType: "api" }, }, expect.objectContaining({ settings: { autoSummarizeTitles: false } })); const text = (result.content[0] as { text: string }).text; expect(text).toContain("Delegated to Bob (agent-001)"); expect(text).toContain("Created FN-050"); expect(text).toContain("picked up by Bob on their next heartbeat cycle"); }); it("returns success message with task ID and agent name", async () => { const agent = createAgent({ id: "agent-001", name: "Bob" }); vi.mocked(agentStore.getAgent).mockResolvedValue(agent); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-051", description: "Write tests", dependencies: [], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); 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); const text = (result.content[0] as { text: string }).text; expect(text).toContain("FN-051"); expect(text).toContain("Bob"); expect(result.details).toEqual({ taskId: "FN-051", agentId: "agent-001", agentName: "Bob" }); }); it("returns error when target agent not found", async () => { vi.mocked(agentStore.getAgent).mockResolvedValue(null); const tool = createDelegateTaskTool(agentStore, taskStore); const result = await tool.execute("session-1", { agent_id: "nonexistent-agent", description: "Do something", }, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("ERROR: Agent nonexistent-agent not found"); expect(taskStore.createTask).not.toHaveBeenCalled(); }); it("returns error when target agent is ephemeral", async () => { const ephemeralAgent = createAgent({ id: "executor-FN-100", metadata: { agentKind: "task-worker" }, }); vi.mocked(agentStore.getAgent).mockResolvedValue(ephemeralAgent); const tool = createDelegateTaskTool(agentStore, taskStore); const result = await tool.execute("session-1", { agent_id: "executor-FN-100", description: "Do something", }, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("ERROR: Cannot delegate to ephemeral/runtime agent executor-FN-100"); 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); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-053", description: "Do something", dependencies: [], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); const tool = createDelegateTaskTool(agentStore, taskStore); await tool.execute("session-1", { agent_id: "agent-009", description: "Do something", }, undefined as any, undefined as any, undefined as any); expect(taskStore.createTask).toHaveBeenCalledWith(expect.objectContaining({ assignedAgentId: "agent-009", source: { sourceType: "api" }, }), expect.anything()); }); it("rejects reviewer target without override", async () => { const reviewer = createAgent({ id: "agent-002", name: "Rita", role: "reviewer" }); vi.mocked(agentStore.getAgent).mockResolvedValue(reviewer); const tool = createDelegateTaskTool(agentStore, taskStore); const result = await tool.execute("session-1", { agent_id: "agent-002", description: "Do something", }, undefined as any, undefined as any, undefined as any); const text = (result.content[0] as { text: string }).text; expect(text).toContain("ERROR: Agent agent-002 has role \"reviewer\""); expect(text).toContain("Pass override=true to bypass"); expect(taskStore.createTask).not.toHaveBeenCalled(); }); it("allows non-executor target with override", async () => { const reviewer = createAgent({ id: "agent-002", name: "Rita", role: "reviewer" }); vi.mocked(agentStore.getAgent).mockResolvedValue(reviewer); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-054", description: "Do something", dependencies: [], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); const tool = createDelegateTaskTool(agentStore, taskStore); await tool.execute("session-1", { agent_id: "agent-002", description: "Do something", override: true, }, undefined as any, undefined as any, undefined as any); expect(taskStore.createTask).toHaveBeenCalledWith(expect.objectContaining({ source: { sourceType: "api", sourceMetadata: { executorRoleOverride: true } }, }), expect.objectContaining({ settings: { autoSummarizeTitles: false } })); }); it("passes dependencies through to task creation", async () => { const agent = createAgent({ id: "agent-001", name: "Bob" }); vi.mocked(agentStore.getAgent).mockResolvedValue(agent); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-052", description: "Integration test", dependencies: ["FN-010"], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); const tool = createDelegateTaskTool(agentStore, taskStore); const result = await tool.execute("session-1", { agent_id: "agent-001", description: "Integration test", dependencies: ["FN-010"], }, undefined as any, undefined as any, undefined as any); expect(taskStore.createTask).toHaveBeenCalledWith({ description: "Integration test", dependencies: ["FN-010"], column: "todo", assignedAgentId: "agent-001", source: { sourceType: "api" }, }, expect.objectContaining({ settings: { autoSummarizeTitles: false } })); const text = (result.content[0] as { text: string }).text; expect(text).toContain("depends on: FN-010"); }); it("creates task without dependencies when none specified", async () => { const agent = createAgent({ id: "agent-001", name: "Bob" }); vi.mocked(agentStore.getAgent).mockResolvedValue(agent); vi.mocked(taskStore.createTask).mockResolvedValue({ id: "FN-053", description: "Simple task", dependencies: [], column: "todo" as const, steps: [], currentStep: 0, log: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }); const tool = createDelegateTaskTool(agentStore, taskStore); const result = await tool.execute("session-1", { agent_id: "agent-001", description: "Simple task", }, undefined as any, undefined as any, undefined as any); expect(taskStore.createTask).toHaveBeenCalledWith( expect.objectContaining({ dependencies: undefined }), expect.objectContaining({ settings: { autoSummarizeTitles: false } }), ); const text = (result.content[0] as { text: string }).text; expect(text).not.toContain("depends on:"); }); });