feat(FN-3891): add verificationFixRetries merge control setting

Added a new `verificationFixRetries` merge control to the Settings modal, documented it in the settings reference, and added corresponding tests; a changeset was also included for the `@runfusion/fusion` package.

Fusion-Task-Id: FN-3891
This commit is contained in:
Fusion
2026-05-11 08:14:30 -07:00
committed by gsxdsm
parent d1f4d5f579
commit ee4f7a580e
3 changed files with 85 additions and 5 deletions

View File

@@ -368,6 +368,16 @@ describe("MissionStore", () => {
expect(batchedResult.summary.completedFeatures).toBe(singleSummary.completedFeatures);
expect(batchedResult.summary.progressPercent).toBe(singleSummary.progressPercent);
});
it("preserves persisted interviewState when listing missions with summaries", () => {
const interviewMission = store.createMission({ title: "Interview mission" });
store.updateMissionInterviewState(interviewMission.id, "in_progress");
const listed = store.listMissionsWithSummaries().find((mission) => mission.id === interviewMission.id);
expect(listed).toBeDefined();
expect(listed?.interviewState).toBe("in_progress");
});
});
// ── Batched Health Tests ──────────────────────────────────────────────

View File

@@ -1777,14 +1777,66 @@ describe("MissionManager", () => {
expect(screen.queryByText("Should not render")).not.toBeInTheDocument();
expect(screen.queryByText(/interview sessions pending/i)).not.toBeInTheDocument();
});
const awaitingInterviewRow = screen.getByText("Payment workflow planning").closest(".mission-list__item");
expect(awaitingInterviewRow).toBeTruthy();
fireEvent.click(within(awaitingInterviewRow as HTMLElement).getByLabelText("Resume interview"));
it("keeps persisted interview missions distinct from transient interview sessions", async () => {
const missionsWithInterview = [
{
id: "M-PERSISTED-INTERVIEW",
title: "Persisted mission interview",
description: "Persisted mission row",
status: "planning",
interviewState: "in_progress",
milestones: [],
createdAt: "2026-01-06T00:00:00.000Z",
updatedAt: "2026-01-06T00:00:00.000Z",
},
...mockMissions,
];
await waitFor(() => {
expect(screen.getByText("Plan Mission with AI")).toBeInTheDocument();
mockFetchAiSessions.mockResolvedValueOnce([
{
id: "session-awaiting",
type: "mission_interview",
status: "awaiting_input",
title: "Transient interview session",
projectId: null,
lockedByTab: null,
updatedAt: "2026-01-03T00:00:00.000Z",
},
]);
globalThis.fetch = createFetchMockWithHealth(missionsWithInterview as Array<Record<string, unknown>>, {
...mockMissionHealthById,
"M-PERSISTED-INTERVIEW": {
missionId: "M-PERSISTED-INTERVIEW",
status: "planning",
tasksCompleted: 0,
tasksFailed: 0,
tasksInFlight: 0,
totalTasks: 0,
estimatedCompletionPercent: 0,
autopilotState: "inactive",
autopilotEnabled: false,
},
});
render(<MissionManager isOpen={true} onClose={vi.fn()} addToast={vi.fn()} />);
const persistedTitle = await screen.findByText("Persisted mission interview");
const transientTitle = await screen.findByText("Transient interview session");
const persistedRow = persistedTitle.closest(".mission-list__item");
const transientRow = transientTitle.closest(".mission-list__item");
expect(persistedRow).toBeTruthy();
expect(transientRow).toBeTruthy();
expect(persistedRow).not.toBe(transientRow);
expect(within(persistedRow as HTMLElement).getByText("Interview in progress")).toBeInTheDocument();
expect(within(transientRow as HTMLElement).getByText("Awaiting input")).toBeInTheDocument();
expect(screen.queryByText(/interview sessions pending/i)).not.toBeInTheDocument();
});
it("only shows in-progress interview sessions scoped to the active project", async () => {

View File

@@ -708,6 +708,24 @@ describe("Mission API", () => {
expect(res.body).toHaveLength(2);
});
it("returns persisted interview-stage missions from list endpoint", async () => {
const { app, missionStore } = buildApp();
const mission = missionStore.createMission({ title: "Interview draft" });
missionStore.updateMissionInterviewState(mission.id, "in_progress");
const res = await get(app, "/api/missions");
expect(res.status).toBe(200);
expect(res.body).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: mission.id,
interviewState: "in_progress",
}),
]),
);
});
it("should return empty array when no missions", async () => {
const { app } = buildApp();
const res = await get(app, "/api/missions");