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:
@@ -1307,6 +1307,33 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
[projectId, sessionTabId, view]
|
||||
);
|
||||
|
||||
const handleRefineFurther = useCallback(async () => {
|
||||
if (view.type !== "summary") {
|
||||
return;
|
||||
}
|
||||
|
||||
const { session, summary } = view;
|
||||
const sessionId = session.sessionId;
|
||||
currentSessionIdRef.current = sessionId;
|
||||
setLockSessionId(sessionId);
|
||||
|
||||
setError(null);
|
||||
setIsRetrying(false);
|
||||
setStreamingOutput("");
|
||||
setView({ type: "loading" });
|
||||
|
||||
connectToPlanningStream(sessionId);
|
||||
|
||||
try {
|
||||
await respondToPlanning(sessionId, { refine: true }, projectId, sessionTabId);
|
||||
} catch (err) {
|
||||
streamConnectionRef.current?.close();
|
||||
streamConnectionRef.current = null;
|
||||
setError(getErrorMessage(err) || "Failed to refine plan");
|
||||
setView({ type: "summary", session, summary: editedSummary ?? summary });
|
||||
}
|
||||
}, [connectToPlanningStream, editedSummary, projectId, sessionTabId, view]);
|
||||
|
||||
const handleStopGeneration = useCallback(async () => {
|
||||
const sessionId = currentSessionIdRef.current;
|
||||
if (!sessionId) {
|
||||
@@ -1935,8 +1962,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
onCreateTask={handleCreateTask}
|
||||
onBreakIntoTasks={handleStartBreakdown}
|
||||
onRefine={() => {
|
||||
// Reset to question mode for more refinement
|
||||
setView({ type: "question", session: view.session });
|
||||
void handleRefineFurther();
|
||||
}}
|
||||
isLoading={false}
|
||||
/>
|
||||
|
||||
@@ -809,6 +809,84 @@ describe("PlanningModeModal", () => {
|
||||
expect(mockCreateTaskFromPlanning).toHaveBeenCalledWith("session-complete-2", resumedSummary, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("refines a resumed complete session without blank question view", async () => {
|
||||
const resumedSummary: PlanningSummary = {
|
||||
title: "Resume-and-refine",
|
||||
description: "Recovered summary for refine",
|
||||
suggestedSize: "M",
|
||||
suggestedDependencies: [],
|
||||
keyDeliverables: ["Implement", "Verify"],
|
||||
};
|
||||
const refinedQuestion: PlanningQuestion = {
|
||||
id: "q-refine",
|
||||
type: "text",
|
||||
question: "Which part should we refine?",
|
||||
description: "Refine follow-up",
|
||||
};
|
||||
|
||||
mockFetchAiSession.mockResolvedValueOnce({
|
||||
id: "session-complete-refine",
|
||||
type: "planning",
|
||||
status: "complete",
|
||||
title: "Resume-and-refine",
|
||||
inputPayload: JSON.stringify({ initialPlan: "Recover and refine" }),
|
||||
conversationHistory: "[]",
|
||||
currentQuestion: null,
|
||||
result: JSON.stringify(resumedSummary),
|
||||
thinkingOutput: "",
|
||||
error: null,
|
||||
projectId: null,
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
});
|
||||
|
||||
let streamHandlers: any;
|
||||
mockConnectPlanningStream.mockImplementationOnce((_sessionId: string, _projectId: string | undefined, handlers: any) => {
|
||||
streamHandlers = handlers;
|
||||
return {
|
||||
close: vi.fn(),
|
||||
isConnected: vi.fn().mockReturnValue(true),
|
||||
};
|
||||
});
|
||||
mockRespondToPlanning.mockImplementationOnce(async () => {
|
||||
setTimeout(() => {
|
||||
streamHandlers?.onQuestion?.(refinedQuestion);
|
||||
}, 10);
|
||||
return { type: "question", data: refinedQuestion };
|
||||
});
|
||||
|
||||
render(
|
||||
<PlanningModeModal
|
||||
isOpen={true}
|
||||
onClose={mockOnClose}
|
||||
onTaskCreated={mockOnTaskCreated}
|
||||
onTasksCreated={vi.fn()}
|
||||
tasks={mockTasks}
|
||||
resumeSessionId="session-complete-refine"
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: "Refine Further" })).toBeDefined();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Refine Further" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRespondToPlanning).toHaveBeenCalledWith(
|
||||
"session-complete-refine",
|
||||
{ refine: true },
|
||||
undefined,
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Which part should we refine?")).toBeDefined();
|
||||
});
|
||||
expect(screen.queryByText("No active question in session")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Conversation history", () => {
|
||||
|
||||
Reference in New Issue
Block a user