FN-5880: recover plan-ready mission interview drafts
Keep completed mission interview summaries resumable until they are approved or discarded. - include mission interview sessions in dashboard, CLI, and extension draft listings - relabel complete drafts as plan-ready review items and reopen them at the summary approval step - add regression coverage and docs/changeset updates for resumable complete drafts Files changed: .../fn-5880-mission-interview-complete-draft.md | 5 ++ docs/missions.md | 8 +-- packages/cli/src/__tests__/extension.test.ts | 13 +++- .../cli/src/commands/__tests__/mission.test.ts | 11 ++- packages/cli/src/commands/mission.ts | 17 ++++- packages/cli/src/extension.ts | 7 +- .../dashboard/app/components/MissionManager.tsx | 24 ++++--- .../components/__tests__/MissionManager.test.tsx | 70 ++++++++++++++++++ packages/dashboard/app/components/mission-types.ts | 2 +- .../mission-interview-drafts-routes.test.ts | 18 ++++ - .../src/__tests__/mission-interview.test.ts | 82 ++++++++++++++++++++++ packages/dashboard/src/mission-interview.ts | 36 +++++----- 12 files changed, 252 insertions(+), 41 deletions(-) Fusion-Task-Id: FN-5880 Fusion-Task-Lineage: 18a9c25c-3f59-4179-9e9c-63f0747acb7f
This commit is contained in:
@@ -1088,13 +1088,24 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
`INSERT INTO ai_sessions (id, type, status, title, inputPayload, conversationHistory, currentQuestion, result, thinkingOutput, error, projectId, createdAt, updatedAt, lockedByTab, lockedAt)
|
||||
VALUES (?, 'mission_interview', 'awaiting_input', ?, '{}', '[]', NULL, NULL, '', NULL, NULL, ?, ?, NULL, NULL)`,
|
||||
).run("draft-1", "Draft Mission", "2026-05-12T00:00:00.000Z", "2026-05-12T00:00:00.000Z");
|
||||
store.getDatabase().prepare(
|
||||
`INSERT INTO ai_sessions (id, type, status, title, inputPayload, conversationHistory, currentQuestion, result, thinkingOutput, error, projectId, createdAt, updatedAt, lockedByTab, lockedAt)
|
||||
VALUES (?, 'mission_interview', 'complete', ?, '{}', '[]', NULL, '{}', '', NULL, NULL, ?, ?, NULL, NULL)`,
|
||||
).run("draft-2", "Ready Mission", "2026-05-12T00:01:00.000Z", "2026-05-12T00:01:00.000Z");
|
||||
|
||||
const listTool = api.tools.get("fn_mission_list")!;
|
||||
const result = await listTool.execute("call-1", {}, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
expect(result.content[0].text).toContain("Drafts (1)");
|
||||
expect(result.content[0].text).toContain("Drafts (2)");
|
||||
expect(result.content[0].text).toContain("draft-1: Draft Mission (draft · interview awaiting_input)");
|
||||
expect(result.content[0].text).toContain("draft-2: Ready Mission (draft · interview plan ready)");
|
||||
expect(result.details.drafts).toEqual([
|
||||
{
|
||||
id: "draft-2",
|
||||
title: "Ready Mission",
|
||||
status: "complete",
|
||||
updatedAt: "2026-05-12T00:01:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "draft-1",
|
||||
title: "Draft Mission",
|
||||
|
||||
@@ -377,6 +377,12 @@ describe("mission commands", () => {
|
||||
status: "awaiting_input",
|
||||
updatedAt: "2026-05-12T00:00:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "draft-2",
|
||||
title: "Ready draft",
|
||||
status: "complete",
|
||||
updatedAt: "2026-05-12T00:01:00.000Z",
|
||||
},
|
||||
]),
|
||||
});
|
||||
|
||||
@@ -394,9 +400,10 @@ describe("mission commands", () => {
|
||||
}
|
||||
|
||||
const joined = consoleCapture.logs.join("\n");
|
||||
expect(joined).toContain("◌ Drafts (1)");
|
||||
expect(joined).toContain("◌ Drafts (2)");
|
||||
expect(joined).toContain("draft-1 Draft mission — (draft · interview awaiting_input)");
|
||||
expect(joined.indexOf("◌ Drafts (1)")).toBeLessThan(joined.indexOf("● Active (1)"));
|
||||
expect(joined).toContain("draft-2 Ready draft — (draft · interview plan ready)");
|
||||
expect(joined.indexOf("◌ Drafts (2)")).toBeLessThan(joined.indexOf("● Active (1)"));
|
||||
|
||||
mockExit.mockRestore();
|
||||
} finally {
|
||||
|
||||
@@ -103,6 +103,17 @@ interface RunMissionListOptions {
|
||||
includeDrafts?: boolean;
|
||||
}
|
||||
|
||||
function formatMissionInterviewDraftStatus(
|
||||
status: "generating" | "awaiting_input" | "error" | "complete",
|
||||
): string {
|
||||
switch (status) {
|
||||
case "complete":
|
||||
return "plan ready";
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all missions with status summary.
|
||||
*/
|
||||
@@ -118,11 +129,11 @@ export async function runMissionList(projectName?: string, options: RunMissionLi
|
||||
`SELECT id, title, status, updatedAt
|
||||
FROM ai_sessions
|
||||
WHERE type = 'mission_interview'
|
||||
AND status IN ('generating', 'awaiting_input', 'error')
|
||||
AND status IN ('generating', 'awaiting_input', 'error', 'complete')
|
||||
AND COALESCE(archived, 0) = 0
|
||||
ORDER BY updatedAt DESC`,
|
||||
)
|
||||
.all() as Array<{ id: string; title: string; status: "generating" | "awaiting_input" | "error"; updatedAt: string }>)
|
||||
.all() as Array<{ id: string; title: string; status: "generating" | "awaiting_input" | "error" | "complete"; updatedAt: string }>)
|
||||
: [];
|
||||
|
||||
if (missions.length === 0 && drafts.length === 0) {
|
||||
@@ -135,7 +146,7 @@ export async function runMissionList(projectName?: string, options: RunMissionLi
|
||||
if (drafts.length > 0) {
|
||||
console.log(` ◌ Drafts (${drafts.length})`);
|
||||
for (const draft of drafts) {
|
||||
console.log(` ◌ ${draft.id} ${draft.title} — (draft · interview ${draft.status})`);
|
||||
console.log(` ◌ ${draft.id} ${draft.title} — (draft · interview ${formatMissionInterviewDraftStatus(draft.status)})`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
@@ -2325,11 +2325,11 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
`SELECT id, title, status, updatedAt
|
||||
FROM ai_sessions
|
||||
WHERE type = 'mission_interview'
|
||||
AND status IN ('generating', 'awaiting_input', 'error')
|
||||
AND status IN ('generating', 'awaiting_input', 'error', 'complete')
|
||||
AND COALESCE(archived, 0) = 0
|
||||
ORDER BY updatedAt DESC`,
|
||||
)
|
||||
.all() as Array<{ id: string; title: string; status: "generating" | "awaiting_input" | "error"; updatedAt: string }>)
|
||||
.all() as Array<{ id: string; title: string; status: "generating" | "awaiting_input" | "error" | "complete"; updatedAt: string }>)
|
||||
: [];
|
||||
|
||||
if (missions.length === 0 && drafts.length === 0) {
|
||||
@@ -2357,7 +2357,8 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
if (drafts.length > 0) {
|
||||
lines.push(`Drafts (${drafts.length})`);
|
||||
for (const draft of drafts) {
|
||||
lines.push(` ◌ ${draft.id}: ${draft.title} (draft · interview ${draft.status})`);
|
||||
const draftStatus = draft.status === "complete" ? "plan ready" : draft.status;
|
||||
lines.push(` ◌ ${draft.id}: ${draft.title} (draft · interview ${draftStatus})`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user