feat(FN-1152): add retry flows for failed AI planning sessions
- Keep errored AI sessions retryable in the session store and add backend retry handlers for planning, subtask breakdown, and mission interview flows - Add retry API routes and dashboard API client helpers for planning, subtask, and mission interview session retries - Update PlanningModeModal, SubtaskBreakdownModal, MissionInterviewModal, and background session handling to show error states with retry/cancel UX - Expand unit and integration tests across store, services, routes, and modal components to cover retry success and failure paths
This commit is contained in:
@@ -1400,6 +1400,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
"POST /planning/start",
|
||||
"POST /planning/start-streaming",
|
||||
"POST /planning/respond",
|
||||
"POST /planning/:sessionId/retry",
|
||||
"POST /planning/cancel",
|
||||
"POST /planning/create-task",
|
||||
"POST /planning/start-breakdown",
|
||||
@@ -5862,6 +5863,31 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/subtasks/:sessionId/retry", async (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
if (!sessionId || typeof sessionId !== "string") {
|
||||
throw badRequest("sessionId is required");
|
||||
}
|
||||
|
||||
const scopedStore = await getScopedStore(req);
|
||||
const { retrySubtaskSession } = await import("./subtask-breakdown.js");
|
||||
await retrySubtaskSession(sessionId, scopedStore.getRootDir());
|
||||
res.json({ success: true, sessionId });
|
||||
} catch (err: any) {
|
||||
if (err instanceof ApiError) {
|
||||
throw err;
|
||||
}
|
||||
if (err.name === "SessionNotFoundError") {
|
||||
throw notFound(err.message);
|
||||
} else if (err.name === "InvalidSessionStateError") {
|
||||
throw badRequest(err.message);
|
||||
} else {
|
||||
rethrowAsApiError(err, "Failed to retry subtask session");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/planning/start
|
||||
* Start a new planning session.
|
||||
@@ -5988,6 +6014,31 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/planning/:sessionId/retry", async (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
if (!sessionId || typeof sessionId !== "string") {
|
||||
throw badRequest("sessionId is required");
|
||||
}
|
||||
|
||||
const scopedStore = await getScopedStore(req);
|
||||
const { retrySession } = await import("./planning.js");
|
||||
await retrySession(sessionId, scopedStore.getRootDir());
|
||||
res.json({ success: true, sessionId });
|
||||
} catch (err: any) {
|
||||
if (err instanceof ApiError) {
|
||||
throw err;
|
||||
}
|
||||
if (err.name === "SessionNotFoundError") {
|
||||
throw notFound(err.message);
|
||||
} else if (err.name === "InvalidSessionStateError") {
|
||||
throw badRequest(err.message);
|
||||
} else {
|
||||
rethrowAsApiError(err, "Failed to retry planning session");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/planning/cancel
|
||||
* Cancel and cleanup a planning session.
|
||||
|
||||
Reference in New Issue
Block a user