feat(FN-3580): restore canonical agent lifecycle and remove terminated agen

This merge restores the canonical agent lifecycle with termination scoped at the run level (FN-3580, 4 steps), adds sender-side wake recipient override for messages, and introduces test isolation CI enforcement with a stuck-requeue race fix. UI changes remove terminated-agent indicators from AgentDe

Fusion-Task-Id: FN-3580
This commit is contained in:
Fusion
2026-05-06 10:18:27 -07:00
committed by gsxdsm
parent 7f90308485
commit a31c4323a8
24 changed files with 216 additions and 286 deletions

View File

@@ -297,19 +297,34 @@ describe("executeHeartbeat", () => {
expect(store.updateAgentState).not.toHaveBeenCalledWith("agent-001", "active");
});
it("completes with invalid_state when agent state is terminated", async () => {
const store = createStoreWithAgentForExec({ state: "terminated" });
it("completes with invalid_state when agent state is error", async () => {
const store = createStoreWithAgentForExec({ state: "error" });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const result = await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" });
expect(result).toBeDefined();
expect(result.status).toBe("completed");
expect(result.resultJson).toEqual({ reason: "invalid_state", state: "terminated" });
expect(result.resultJson).toEqual({ reason: "invalid_state", state: "error" });
expect(mockedCreateFnAgent).not.toHaveBeenCalled();
expect(store.updateAgentState).not.toHaveBeenCalledWith("agent-001", "active");
});
it("keeps terminated as a run status while pausing the agent", async () => {
const store = createStoreWithAgentForExec({ state: "running" });
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
const run = await monitor.startRun("agent-001", { source: "on_demand" });
await monitor.completeRun("agent-001", run.id, {
status: "terminated",
stderrExcerpt: "Run stopped by user",
});
expect(store.updateAgentState).toHaveBeenCalledWith("agent-001", "running");
expect(store.updateAgentState).toHaveBeenCalledWith("agent-001", "paused");
expect(store.endHeartbeatRun).toHaveBeenCalledWith(run.id, "terminated");
});
it("completes as failed when agent not found in store", async () => {
const store = createStoreWithAgentForExec();
(store.getAgent as ReturnType<typeof vi.fn>).mockResolvedValue(null);