fix(FN-2149): harden agent reflection error handling

- Update reflection trigger API typing to allow null responses from manual generation
- Handle null results in AgentReflectionsTab with a clear insufficient-history toast
- Normalize reflection trigger errors to show specific UX for deleted agents and insufficient history
- Return a clear 500 error when manual reflection generation yields no reflection payload
- Expand dashboard reflection route/UI tests to lock in null and not-found regression behavior
This commit is contained in:
Fusion
2026-04-19 12:14:14 -07:00
committed by gsxdsm
parent 2b3f971fd5
commit 9ec7ce9fd7
5 changed files with 124 additions and 25 deletions

View File

@@ -206,6 +206,62 @@ describe("AgentReflectionsTab", () => {
expect(mockedFetchAgentPerformance).toHaveBeenCalledTimes(2); // Initial + refresh
});
it("shows not-enough-history toast when Reflect Now returns null", async () => {
mockedTriggerAgentReflection.mockResolvedValue(null);
render(
<AgentReflectionsTab agentId="agent-001" projectId="test-project" addToast={addToast} />
);
await waitFor(() => {
expect(screen.getByText("Reflect Now")).toBeInTheDocument();
});
fireEvent.click(screen.getByText("Reflect Now"));
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith("Not enough history to generate a reflection yet", "error");
});
});
it("shows agent-deleted toast when Reflect Now returns agent-not-found", async () => {
mockedTriggerAgentReflection.mockRejectedValue(new Error("Agent not found"));
render(
<AgentReflectionsTab agentId="agent-001" projectId="test-project" addToast={addToast} />
);
await waitFor(() => {
expect(screen.getByText("Reflect Now")).toBeInTheDocument();
});
fireEvent.click(screen.getByText("Reflect Now"));
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith("This agent is no longer available. It may have been deleted.", "error");
});
});
it("shows not-enough-history toast when Reflect Now fails with insufficient-history error", async () => {
mockedTriggerAgentReflection.mockRejectedValue(
new Error("Unable to generate reflection — insufficient history or AI unavailable")
);
render(
<AgentReflectionsTab agentId="agent-001" projectId="test-project" addToast={addToast} />
);
await waitFor(() => {
expect(screen.getByText("Reflect Now")).toBeInTheDocument();
});
fireEvent.click(screen.getByText("Reflect Now"));
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith("Not enough history to generate a reflection yet", "error");
});
});
it("shows error toast when Reflect Now fails", async () => {
mockedTriggerAgentReflection.mockRejectedValue(new Error("Service unavailable"));

View File

@@ -72,6 +72,13 @@ function getTriggerLabel(trigger: string): string {
}
}
function getErrorMessage(err: unknown): string {
if (err instanceof Error && err.message) {
return err.message;
}
return String(err);
}
export function AgentReflectionsTab({ agentId, projectId, addToast }: AgentReflectionsTabProps) {
const [reflections, setReflections] = useState<AgentReflection[]>([]);
const [performance, setPerformance] = useState<AgentPerformanceSummary | null>(null);
@@ -103,12 +110,26 @@ export function AgentReflectionsTab({ agentId, projectId, addToast }: AgentRefle
const handleReflectNow = async () => {
setIsReflecting(true);
try {
await triggerAgentReflection(agentId, projectId);
const reflection = await triggerAgentReflection(agentId, projectId);
if (!reflection) {
addToast("Not enough history to generate a reflection yet", "error");
return;
}
addToast("Reflection generated successfully", "success");
setIsLoading(true);
await loadData();
} catch (err: any) {
addToast(`Failed to generate reflection: ${err.message}`, "error");
} catch (err: unknown) {
const message = getErrorMessage(err);
const normalizedMessage = message.toLowerCase();
if (normalizedMessage.includes("agent not found") || normalizedMessage.includes("not found")) {
addToast("This agent is no longer available. It may have been deleted.", "error");
} else if (normalizedMessage.includes("insufficient history")) {
addToast("Not enough history to generate a reflection yet", "error");
} else {
addToast(`Failed to generate reflection: ${message}`, "error");
}
} finally {
setIsReflecting(false);
}