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:
@@ -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({
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user