feat(#233): Connect agent dashboard to real orchestrator API
- Add GET /agents endpoint to orchestrator controller - Update AgentStatusWidget to fetch from real API instead of mock data - Add comprehensive tests for listAgents endpoint - Auto-refresh agent list every 30 seconds - Display agent status with proper icons and formatting - Show error states when API is unavailable Fixes #233 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ describe("AgentsController", () => {
|
||||
};
|
||||
let spawnerService: {
|
||||
spawnAgent: ReturnType<typeof vi.fn>;
|
||||
listAgentSessions: ReturnType<typeof vi.fn>;
|
||||
getAgentSession: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let lifecycleService: {
|
||||
getAgentLifecycleState: ReturnType<typeof vi.fn>;
|
||||
@@ -30,6 +32,8 @@ describe("AgentsController", () => {
|
||||
|
||||
spawnerService = {
|
||||
spawnAgent: vi.fn(),
|
||||
listAgentSessions: vi.fn(),
|
||||
getAgentSession: vi.fn(),
|
||||
};
|
||||
|
||||
lifecycleService = {
|
||||
@@ -58,6 +62,109 @@ describe("AgentsController", () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe("listAgents", () => {
|
||||
it("should return empty array when no agents exist", () => {
|
||||
// Arrange
|
||||
spawnerService.listAgentSessions.mockReturnValue([]);
|
||||
|
||||
// Act
|
||||
const result = controller.listAgents();
|
||||
|
||||
// Assert
|
||||
expect(spawnerService.listAgentSessions).toHaveBeenCalled();
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return all agent sessions with mapped status", () => {
|
||||
// Arrange
|
||||
const sessions = [
|
||||
{
|
||||
agentId: "agent-1",
|
||||
taskId: "task-1",
|
||||
agentType: "worker" as const,
|
||||
state: "running" as const,
|
||||
context: {
|
||||
repository: "repo",
|
||||
branch: "main",
|
||||
workItems: [],
|
||||
},
|
||||
spawnedAt: new Date("2026-02-05T12:00:00Z"),
|
||||
},
|
||||
{
|
||||
agentId: "agent-2",
|
||||
taskId: "task-2",
|
||||
agentType: "reviewer" as const,
|
||||
state: "completed" as const,
|
||||
context: {
|
||||
repository: "repo",
|
||||
branch: "main",
|
||||
workItems: [],
|
||||
},
|
||||
spawnedAt: new Date("2026-02-05T11:00:00Z"),
|
||||
completedAt: new Date("2026-02-05T11:30:00Z"),
|
||||
},
|
||||
{
|
||||
agentId: "agent-3",
|
||||
taskId: "task-3",
|
||||
agentType: "tester" as const,
|
||||
state: "failed" as const,
|
||||
context: {
|
||||
repository: "repo",
|
||||
branch: "main",
|
||||
workItems: [],
|
||||
},
|
||||
spawnedAt: new Date("2026-02-05T10:00:00Z"),
|
||||
error: "Test execution failed",
|
||||
},
|
||||
];
|
||||
spawnerService.listAgentSessions.mockReturnValue(sessions);
|
||||
|
||||
// Act
|
||||
const result = controller.listAgents();
|
||||
|
||||
// Assert
|
||||
expect(spawnerService.listAgentSessions).toHaveBeenCalled();
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toEqual({
|
||||
agentId: "agent-1",
|
||||
taskId: "task-1",
|
||||
status: "running",
|
||||
agentType: "worker",
|
||||
spawnedAt: "2026-02-05T12:00:00.000Z",
|
||||
completedAt: undefined,
|
||||
error: undefined,
|
||||
});
|
||||
expect(result[1]).toEqual({
|
||||
agentId: "agent-2",
|
||||
taskId: "task-2",
|
||||
status: "completed",
|
||||
agentType: "reviewer",
|
||||
spawnedAt: "2026-02-05T11:00:00.000Z",
|
||||
completedAt: "2026-02-05T11:30:00.000Z",
|
||||
error: undefined,
|
||||
});
|
||||
expect(result[2]).toEqual({
|
||||
agentId: "agent-3",
|
||||
taskId: "task-3",
|
||||
status: "failed",
|
||||
agentType: "tester",
|
||||
spawnedAt: "2026-02-05T10:00:00.000Z",
|
||||
completedAt: undefined,
|
||||
error: "Test execution failed",
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle errors gracefully", () => {
|
||||
// Arrange
|
||||
spawnerService.listAgentSessions.mockImplementation(() => {
|
||||
throw new Error("Service unavailable");
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
expect(() => controller.listAgents()).toThrow("Failed to list agents: Service unavailable");
|
||||
});
|
||||
});
|
||||
|
||||
describe("spawn", () => {
|
||||
const validRequest = {
|
||||
taskId: "task-123",
|
||||
|
||||
@@ -70,6 +70,47 @@ export class AgentsController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all agents
|
||||
* @returns Array of all agent sessions with their status
|
||||
*/
|
||||
@Get()
|
||||
listAgents(): {
|
||||
agentId: string;
|
||||
taskId: string;
|
||||
status: string;
|
||||
agentType: string;
|
||||
spawnedAt: string;
|
||||
completedAt?: string;
|
||||
error?: string;
|
||||
}[] {
|
||||
this.logger.log("Received request to list all agents");
|
||||
|
||||
try {
|
||||
// Get all sessions from spawner service
|
||||
const sessions = this.spawnerService.listAgentSessions();
|
||||
|
||||
// Map to response format
|
||||
const agents = sessions.map((session) => ({
|
||||
agentId: session.agentId,
|
||||
taskId: session.taskId,
|
||||
status: session.state,
|
||||
agentType: session.agentType,
|
||||
spawnedAt: session.spawnedAt.toISOString(),
|
||||
completedAt: session.completedAt?.toISOString(),
|
||||
error: session.error,
|
||||
}));
|
||||
|
||||
this.logger.log(`Found ${agents.length.toString()} agents`);
|
||||
|
||||
return agents;
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(`Failed to list agents: ${errorMessage}`);
|
||||
throw new Error(`Failed to list agents: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent status
|
||||
* @param agentId Agent ID to query
|
||||
|
||||
Reference in New Issue
Block a user