feat(FN-4199): complete Step 2 — extend agents stats capacity counts
Fusion-Task-Id: FN-4199 Fusion-Task-Lineage: fd530f3d-ef2e-4684-ab05-5aaab6856f15
This commit is contained in:
@@ -4250,6 +4250,47 @@ describe("Agent stale task-link sanitization", () => {
|
||||
expect(res.body.assignedTaskCount).toBe(0);
|
||||
});
|
||||
|
||||
it("GET /api/agents/stats returns idle non-ephemeral and todo counts", async () => {
|
||||
const store = createMockStore({
|
||||
getFusionDir: vi.fn().mockReturnValue(fusionDir),
|
||||
listTasks: vi.fn().mockResolvedValue([
|
||||
{ id: "FN-1", column: "todo" },
|
||||
{ id: "FN-2", column: "todo" },
|
||||
{ id: "FN-3", column: "triage" },
|
||||
{ id: "FN-4", column: "in-progress" },
|
||||
{ id: "FN-5", column: "in-review" },
|
||||
{ id: "FN-6", column: "done" },
|
||||
]),
|
||||
} as any);
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use("/api", createApiRoutes(store));
|
||||
|
||||
const { AgentStore } = await import("@fusion/core");
|
||||
const agentStore = new AgentStore({ rootDir: fusionDir });
|
||||
await agentStore.init();
|
||||
|
||||
const idleNonEphemeral = await agentStore.createAgent({ name: "Idle Exec", role: "executor" });
|
||||
await agentStore.updateAgentState(idleNonEphemeral.id, "idle");
|
||||
|
||||
const idleEphemeral = await agentStore.createAgent({
|
||||
name: "executor-ephemeral",
|
||||
role: "executor",
|
||||
metadata: { type: "spawned" },
|
||||
});
|
||||
await agentStore.updateAgentState(idleEphemeral.id, "idle");
|
||||
|
||||
const active = await agentStore.createAgent({ name: "Active Exec", role: "executor" });
|
||||
await agentStore.updateAgentState(active.id, "active");
|
||||
|
||||
const res = await GET(app, "/api/agents/stats");
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.idleNonEphemeralCount).toBe(1);
|
||||
expect(res.body.todoTaskCount).toBe(2);
|
||||
});
|
||||
|
||||
it("GET /api/agents handles task lookup failure gracefully", async () => {
|
||||
const taskId = "FN-LOOKUP-FAIL";
|
||||
const store = createMockStore({
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ApprovalRequestStore,
|
||||
getDefaultHeartbeatProcedurePath,
|
||||
isAgentPermissionPolicyPresetId,
|
||||
isEphemeralAgent,
|
||||
normalizeAgentPermissionPolicyFromPreset,
|
||||
} from "@fusion/core";
|
||||
import { ApiError, badRequest, notFound } from "../api-error.js";
|
||||
@@ -268,6 +269,7 @@ export function registerAgentCoreRoutes(ctx: ApiRoutesContext, deps: AgentCoreRo
|
||||
* Return aggregate stats across all agents.
|
||||
* Must be registered before /agents/:id to avoid "stats" matching :id.
|
||||
* Note: assignedTaskCount excludes agents whose linked task is in a terminal state.
|
||||
* Includes idleNonEphemeralCount and todoTaskCount to back capacity-risk signaling.
|
||||
*/
|
||||
router.get("/agents/stats", async (req, res) => {
|
||||
try {
|
||||
@@ -278,6 +280,7 @@ export function registerAgentCoreRoutes(ctx: ApiRoutesContext, deps: AgentCoreRo
|
||||
|
||||
const agents = await agentStore.listAgents();
|
||||
const activeCount = agents.filter((a) => a.state === "active" || a.state === "running").length;
|
||||
const idleNonEphemeralCount = agents.filter((a) => a.state === "idle" && !isEphemeralAgent(a)).length;
|
||||
|
||||
// Count only agents with non-terminal linked tasks
|
||||
const sanitizedAgents = await sanitizeAgentTaskLinks(agents, scopedStore);
|
||||
@@ -293,7 +296,17 @@ export function registerAgentCoreRoutes(ctx: ApiRoutesContext, deps: AgentCoreRo
|
||||
|
||||
const total = completedRuns + failedRuns;
|
||||
const successRate = total > 0 ? completedRuns / total : 0;
|
||||
res.json({ activeCount, assignedTaskCount, completedRuns, failedRuns, successRate });
|
||||
const tasks = await scopedStore.listTasks({ slim: true, includeArchived: false });
|
||||
const todoTaskCount = tasks.filter((task) => task.column === "todo").length;
|
||||
res.json({
|
||||
activeCount,
|
||||
assignedTaskCount,
|
||||
completedRuns,
|
||||
failedRuns,
|
||||
successRate,
|
||||
idleNonEphemeralCount,
|
||||
todoTaskCount,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof ApiError) {
|
||||
throw err;
|
||||
|
||||
Reference in New Issue
Block a user