feat: add AI-driven mission interview system (Factory.ai-style planning)

Add a conversational AI interview flow to the missions screen that
interviews users to break down missions into milestones, slices, and
features with verification criteria at every level.

Backend: Replace stubbed interview logic with real AI agent integration
using createKbAgent, SSE streaming, and robust JSON parsing. Wire up
all 5 placeholder mission interview route endpoints (start, respond,
cancel, stream, create-mission).

Frontend: New MissionInterviewModal component with conversational UI,
thinking output display, question renderers, and hierarchical plan
review/edit. Integrated into MissionManager with "Plan with AI" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-04 12:50:12 -07:00
parent 1b3eb7bf10
commit cf4fe0277d
6 changed files with 1950 additions and 186 deletions

View File

@@ -606,7 +606,7 @@ describe("Mission API", () => {
});
describe("Interview endpoints", () => {
it("should return 501 for unimplemented interview endpoints", async () => {
it("should return 400 when missionTitle is missing on interview start", async () => {
const { app } = buildApp();
const res = await request(
app,
@@ -615,7 +615,47 @@ describe("Mission API", () => {
JSON.stringify({}),
{ "content-type": "application/json" }
);
expect(res.status).toBe(501);
expect(res.status).toBe(400);
expect(res.body.error).toContain("missionTitle");
});
it("should return 400 when sessionId is missing on interview respond", async () => {
const { app } = buildApp();
const res = await request(
app,
"POST",
"/api/missions/interview/respond",
JSON.stringify({}),
{ "content-type": "application/json" }
);
expect(res.status).toBe(400);
expect(res.body.error).toContain("sessionId");
});
it("should return 400 when sessionId is missing on interview cancel", async () => {
const { app } = buildApp();
const res = await request(
app,
"POST",
"/api/missions/interview/cancel",
JSON.stringify({}),
{ "content-type": "application/json" }
);
expect(res.status).toBe(400);
expect(res.body.error).toContain("sessionId");
});
it("should return 400 when sessionId is missing on create-mission", async () => {
const { app } = buildApp();
const res = await request(
app,
"POST",
"/api/missions/interview/create-mission",
JSON.stringify({}),
{ "content-type": "application/json" }
);
expect(res.status).toBe(400);
expect(res.body.error).toContain("sessionId");
});
});