feat(FN-3209): fix refine continuation flow in planning mode and add local

Merges fixes for the planning refine continuation flow (FN-3209) alongside a new local startup script for development environments. The changes include updates to `PlanningModeModal.tsx`, new and updated tests for the planning system, route handler improvements in `chat.ts` and `planning.ts`, and do

Fusion-Task-Id: FN-3209
This commit is contained in:
Fusion
2026-05-05 11:26:25 -07:00
committed by gsxdsm
parent 695f1e4fa3
commit 1a79fa7110
8 changed files with 282 additions and 22 deletions

View File

@@ -839,7 +839,7 @@ describe("planning module", () => {
await expect(submitResponse("invalid-session-id", {})).rejects.toThrow(SessionNotFoundError);
});
it("throws InvalidSessionStateError when no active question", async () => {
it("throws InvalidSessionStateError when no active question and not refining", async () => {
const mockIp = getUniqueIp();
const { sessionId } = await createSession(mockIp, initialPlan, undefined, TEST_ROOT_DIR);
@@ -852,6 +852,88 @@ describe("planning module", () => {
await expect(submitResponse(sessionId, {})).rejects.toThrow(InvalidSessionStateError);
});
it("continues from summary when refine is requested", async () => {
const mockIp = getUniqueIp();
setupMockAgent([
...STANDARD_QUESTION_RESPONSES,
JSON.stringify({
type: "question",
data: {
id: "q-refine",
type: "text",
question: "What should we tighten in this plan?",
description: "Refine follow-up",
},
}),
]);
const { sessionId } = await createSession(mockIp, initialPlan, undefined, TEST_ROOT_DIR);
await submitResponse(sessionId, { scope: "small" }, TEST_ROOT_DIR);
await submitResponse(sessionId, { requirements: "test" }, TEST_ROOT_DIR);
await submitResponse(sessionId, { confirm: true }, TEST_ROOT_DIR);
const response = await submitResponse(sessionId, { refine: true }, TEST_ROOT_DIR);
expect(response.type).toBe("question");
if (response.type === "question") {
expect(response.data.id).toBe("q-refine");
}
expect(getSummary(sessionId)).toBeUndefined();
});
it("rehydrates a completed persisted session and refines from summary", async () => {
const store = new MockAiSessionStore();
const summary = {
title: "Recovered summary",
description: "Recovered summary description",
suggestedSize: "M",
suggestedDependencies: [],
keyDeliverables: ["Deliverable"],
};
const row = buildPlanningRow({
id: "planning-complete-refine",
status: "complete",
conversationHistory: JSON.stringify([
{
question: {
id: "q-existing",
type: "text",
question: "What should we build?",
description: "baseline",
},
response: { "q-existing": "A useful feature" },
},
]),
currentQuestion: "null",
result: JSON.stringify(summary),
});
store.rows.set(row.id, row);
setAiSessionStore(store as any);
const resumedAgent = createMockAgent([
JSON.stringify({
type: "question",
data: {
id: "q-refine-rehydrated",
type: "text",
question: "Any additional constraints?",
description: "Refine resumed",
},
}),
]);
const createFnAgentSpy = vi.fn(async () => resumedAgent);
__setCreateFnAgent(createFnAgentSpy as any);
const response = await submitResponse(row.id, { refine: true }, TEST_ROOT_DIR);
expect(response.type).toBe("question");
if (response.type === "question") {
expect(response.data.id).toBe("q-refine-rehydrated");
}
expect(createFnAgentSpy).toHaveBeenCalledTimes(1);
expect(resumedAgent.session.prompt).toHaveBeenCalledTimes(2);
expect(resumedAgent.session.prompt.mock.calls[0]?.[0]).toContain("Previous conversation summary");
expect(resumedAgent.session.prompt.mock.calls[1]?.[0]).toContain("Refine Further");
});
it("reconstructs agent for a rehydrated session and continues conversation", async () => {
const store = new MockAiSessionStore();
const row = buildPlanningRow({

View File

@@ -991,6 +991,50 @@ describe("Planning Mode Routes", () => {
expect(finalRes.body.data.keyDeliverables).toBeInstanceOf(Array);
});
it("allows refine requests from completed sessions", async () => {
const startRes = await REQUEST(
buildApp(),
"POST",
"/api/planning/start",
JSON.stringify({ initialPlan: "Build a user auth system" }),
{ "Content-Type": "application/json" }
);
const sessionId = startRes.body.sessionId;
await REQUEST(
buildApp(),
"POST",
"/api/planning/respond",
JSON.stringify({ sessionId, responses: { scope: "medium" } }),
{ "Content-Type": "application/json" }
);
await REQUEST(
buildApp(),
"POST",
"/api/planning/respond",
JSON.stringify({ sessionId, responses: { requirements: "Must have login" } }),
{ "Content-Type": "application/json" }
);
await REQUEST(
buildApp(),
"POST",
"/api/planning/respond",
JSON.stringify({ sessionId, responses: { confirm: true } }),
{ "Content-Type": "application/json" }
);
const refineRes = await REQUEST(
buildApp(),
"POST",
"/api/planning/respond",
JSON.stringify({ sessionId, responses: { refine: true } }),
{ "Content-Type": "application/json" }
);
expect(refineRes.status).toBe(200);
expect(["question", "complete"]).toContain(refineRes.body.type);
});
it("returns 404 for invalid session ID", async () => {
const res = await REQUEST(
buildApp(),