feat(FN-1147): rehydrate AI sessions across server restarts

- Add recoverable-session querying in AiSessionStore and cover it with targeted store tests
- Persist resume context (ip, initial plan, mission metadata) and rebuild planning/subtask/mission sessions from SQLite rows
- Rehydrate recoverable sessions at server startup and resume planning/mission interviews by recreating agents with replayed conversation context
- Update API flows to pass project root context and clean in-memory sessions when persisted sessions are deleted, with comprehensive regression tests
This commit is contained in:
gsxdsm
2026-04-08 08:26:57 -07:00
parent 2a923ea1a0
commit 69621e78dc
11 changed files with 1287 additions and 129 deletions

View File

@@ -17,9 +17,18 @@ import { WebSocketManager, type BadgeSnapshot } from "./websocket.js";
import type { BadgePubSub } from "./badge-pubsub.js";
import { createBadgePubSub, type BadgePubSubMessage } from "./badge-pubsub.js";
import { AiSessionStore } from "./ai-session-store.js";
import { setAiSessionStore as setPlanningAiSessionStore } from "./planning.js";
import { setAiSessionStore as setSubtaskAiSessionStore } from "./subtask-breakdown.js";
import { setAiSessionStore as setMissionAiSessionStore } from "./mission-interview.js";
import {
setAiSessionStore as setPlanningAiSessionStore,
rehydrateFromStore as rehydratePlanningSessions,
} from "./planning.js";
import {
setAiSessionStore as setSubtaskAiSessionStore,
rehydrateFromStore as rehydrateSubtaskSessions,
} from "./subtask-breakdown.js";
import {
setAiSessionStore as setMissionAiSessionStore,
rehydrateFromStore as rehydrateMissionSessions,
} from "./mission-interview.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -322,6 +331,17 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
setSubtaskAiSessionStore(aiSessionStore);
setMissionAiSessionStore(aiSessionStore);
const planningRehydratedCount = rehydratePlanningSessions(aiSessionStore);
const subtaskRehydratedCount = rehydrateSubtaskSessions(aiSessionStore);
const missionRehydratedCount = rehydrateMissionSessions(aiSessionStore);
const totalRehydrated =
planningRehydratedCount + subtaskRehydratedCount + missionRehydratedCount;
if (totalRehydrated > 0) {
console.log(
`[server] Rehydrated ${planningRehydratedCount} planning, ${subtaskRehydratedCount} subtask, ${missionRehydratedCount} mission sessions from SQLite`,
);
}
const loadSettings = (store as { getSettings?: () => Promise<{ aiSessionTtlMs?: number; aiSessionCleanupIntervalMs?: number }> }).getSettings;
if (typeof loadSettings === "function") {
void loadSettings