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:
gsxdsm
2026-04-08 15:10:46 -07:00
parent 5f53949e2a
commit e80fb448d4
20 changed files with 1368 additions and 234 deletions

View File

@@ -145,6 +145,33 @@ export class AiSessionStore extends EventEmitter<AiSessionStoreEvents> {
return row ?? null;
}
/**
* Atomically update only status/error for an existing session.
* Returns false when the session does not exist.
*/
updateStatus(id: string, status: AiSessionStatus, error?: string): boolean {
const now = new Date().toISOString();
const result = this.db
.prepare(
`UPDATE ai_sessions
SET status = ?, error = ?, updatedAt = ?
WHERE id = ?`,
)
.run(status, error ?? null, now, id) as { changes?: number };
const changed = Number(result.changes ?? 0) > 0;
if (!changed) {
return false;
}
const row = this.get(id);
if (row) {
this.emit("ai_session:updated", toSummary(row, row.updatedAt));
}
return true;
}
/**
* Lightweight heartbeat for active sessions.
* Updates only `updatedAt` and intentionally does NOT emit
@@ -160,7 +187,7 @@ export class AiSessionStore extends EventEmitter<AiSessionStoreEvents> {
}
/**
* List active sessions (generating or awaiting_input).
* List active/retryable sessions (generating, awaiting_input, or error).
* Optionally filtered by projectId.
*/
listActive(projectId?: string): AiSessionSummary[] {
@@ -168,7 +195,7 @@ export class AiSessionStore extends EventEmitter<AiSessionStoreEvents> {
return this.db
.prepare(
`SELECT id, type, status, title, projectId, updatedAt FROM ai_sessions
WHERE status IN ('generating', 'awaiting_input') AND projectId = ?
WHERE status IN ('generating', 'awaiting_input', 'error') AND projectId = ?
ORDER BY updatedAt DESC`,
)
.all(projectId) as unknown as AiSessionSummary[];
@@ -176,7 +203,7 @@ export class AiSessionStore extends EventEmitter<AiSessionStoreEvents> {
return this.db
.prepare(
`SELECT id, type, status, title, projectId, updatedAt FROM ai_sessions
WHERE status IN ('generating', 'awaiting_input')
WHERE status IN ('generating', 'awaiting_input', 'error')
ORDER BY updatedAt DESC`,
)
.all() as unknown as AiSessionSummary[];