feat(FN-1254): add inbox-lite task selection for heartbeat agents

- Add InboxTask typing and TaskStore.selectNextTaskForAgent() with priority ordering, dependency checks, paused filtering, and FIFO selection
- Wire heartbeat execution to auto-select and assign inbox work when no task is set, with optional checkout attempts and graceful conflict fallback
- Add POST /api/agents/:id/inbox to expose next-task selection details (task, priority, reason) and return task:null when no work is available
- Expand core, engine, and dashboard tests to cover selection priorities, heartbeat precedence/metadata, checkout-conflict handling, and route behavior with type-safe mocks
This commit is contained in:
gsxdsm
2026-04-08 17:57:16 -07:00
parent edf91528b0
commit cb4df9f476
8 changed files with 532 additions and 7 deletions

View File

@@ -8689,6 +8689,45 @@ Output ONLY the prompt text (no markdown, no explanations).`;
}
});
/**
* POST /api/agents/:id/inbox
* Select the next inbox-lite task candidate for an agent.
*
* Returns `{ task, priority, reason }` when work is available,
* or `{ task: null }` when no matching work is found.
*/
router.post("/agents/:id/inbox", async (req, res) => {
try {
const scopedStore = await getScopedStore(req);
const { AgentStore } = await import("@fusion/core");
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });
await agentStore.init();
const agentId = req.params.id;
const agent = await agentStore.getAgent(agentId);
if (!agent) {
throw notFound("Agent not found");
}
const selection = await scopedStore.selectNextTaskForAgent(agentId);
if (!selection) {
res.json({ task: null });
return;
}
res.json({
task: selection.task,
priority: selection.priority,
reason: selection.reason,
});
} catch (err: any) {
if (err instanceof ApiError) {
throw err;
}
rethrowAsApiError(err);
}
});
/**
* POST /api/agents/:id/heartbeat
* Record a heartbeat for an agent.