feat(FN-1151): preserve and display planning conversation history

- Persist per-turn thinking output for planning and mission interview sessions, including history serialization and recovery-safe state fields
- Add shared conversation history parsing/API types and a reusable timeline component with formatted answers and expandable AI reasoning
- Restore and render conversation history across Planning Mode, Mission Interview, and Subtask Breakdown modals during resume and live question flow
- Harden response submission with nullable-question guards and expand unit/e2e coverage for history rendering and persistence behavior
This commit is contained in:
gsxdsm
2026-04-08 14:17:20 -07:00
parent d5063c6b26
commit 375d46d31b
15 changed files with 1023 additions and 10 deletions

View File

@@ -120,11 +120,17 @@ export type PlanningStreamEvent =
/** Callback function for streaming events */
export type PlanningStreamCallback = (event: PlanningStreamEvent, eventId?: number) => void;
interface PlanningHistoryEntry {
question: PlanningQuestion;
response: unknown;
thinkingOutput?: string;
}
interface Session {
id: string;
ip: string;
initialPlan: string;
history: Array<{ question: PlanningQuestion; response: unknown }>;
history: PlanningHistoryEntry[];
currentQuestion?: PlanningQuestion;
summary?: PlanningSummary;
/** AI agent session for real-time interaction */
@@ -133,6 +139,8 @@ interface Session {
streamCallback?: PlanningStreamCallback;
/** Accumulated thinking output for display */
thinkingOutput: string;
/** Thinking output generated while producing currentQuestion */
lastGeneratedThinking: string;
createdAt: Date;
updatedAt: Date;
}
@@ -260,7 +268,7 @@ function buildSessionFromRow(row: AiSessionRow): Session {
id: row.id,
ip: payload.ip ?? "",
initialPlan: payload.initialPlan ?? row.title,
history: safeParseJson<Array<{ question: PlanningQuestion; response: unknown }>>(
history: safeParseJson<PlanningHistoryEntry[]>(
row.conversationHistory,
[],
{ throwOnError: true, fieldName: "conversationHistory" },
@@ -278,6 +286,7 @@ function buildSessionFromRow(row: AiSessionRow): Session {
}) ?? undefined)
: undefined,
thinkingOutput: row.thinkingOutput,
lastGeneratedThinking: row.thinkingOutput || "",
createdAt,
updatedAt,
agent: undefined,
@@ -546,6 +555,7 @@ export async function createSession(
initialPlan,
history: [],
thinkingOutput: "",
lastGeneratedThinking: "",
createdAt: new Date(),
updatedAt: new Date(),
};
@@ -720,6 +730,7 @@ export async function createSessionWithAgent(
initialPlan,
history: [],
thinkingOutput: "",
lastGeneratedThinking: "",
createdAt: new Date(),
updatedAt: new Date(),
};
@@ -952,6 +963,7 @@ async function continueAgentConversation(session: Session, message: string): Pro
if (parsed.type === "question") {
session.currentQuestion = parsed.data;
session.lastGeneratedThinking = session.thinkingOutput;
session.updatedAt = new Date();
persistSession(session, "awaiting_input");
planningStreamManager.broadcast(session.id, {
@@ -1188,6 +1200,7 @@ export async function submitResponse(
session.history.push({
question: session.currentQuestion,
response: responses,
thinkingOutput: session.lastGeneratedThinking || "",
});
persistSession(session, "generating");