feat(FN-1799): merge fusion/fn-1799

This commit is contained in:
gsxdsm
2026-04-14 12:30:05 -07:00
parent 6ccbeb3e29
commit dfcd533630

View File

@@ -30,12 +30,11 @@ import type {
SliceStatus, SliceStatus,
FeatureStatus, FeatureStatus,
InterviewState, InterviewState,
MissionContractAssertion, MissionAssertionStatus,
FeatureLoopState,
ContractAssertionCreateInput, ContractAssertionCreateInput,
ContractAssertionUpdateInput, ContractAssertionUpdateInput,
MilestoneValidationRollup,
} from "@fusion/core"; } from "@fusion/core";
import type { MissionSummary } from "@fusion/core";
import { import {
MISSION_STATUSES, MISSION_STATUSES,
MILESTONE_STATUSES, MILESTONE_STATUSES,
@@ -58,10 +57,6 @@ import type { AiSessionStore } from "./ai-session-store.js";
// ── Validation Utilities ──────────────────────────────────────────────────── // ── Validation Utilities ────────────────────────────────────────────────────
function validateUuid(id: string): boolean {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
}
function validateMissionId(id: string): boolean { function validateMissionId(id: string): boolean {
// Accept generated format: M-{base36timestamp}-{random} (e.g. M-LZ7DN0-A2B5) // Accept generated format: M-{base36timestamp}-{random} (e.g. M-LZ7DN0-A2B5)
// and legacy numeric format: M-{digits} (e.g. M-001) // and legacy numeric format: M-{digits} (e.g. M-001)
@@ -413,10 +408,7 @@ export function createMissionRouter(
const rootDir = scopedStore.getRootDir(); const rootDir = scopedStore.getRootDir();
const settings = await scopedStore.getSettings(); const settings = await scopedStore.getSettings();
const { const { createMissionInterviewSession } = await import("./mission-interview.js");
createMissionInterviewSession,
RateLimitError,
} = await import("./mission-interview.js");
const sessionId = await createMissionInterviewSession( const sessionId = await createMissionInterviewSession(
ip, ip,
@@ -427,11 +419,12 @@ export function createMissionRouter(
modelId, modelId,
); );
res.status(201).json({ sessionId }); res.status(201).json({ sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "RateLimitError") { const errMsg = err instanceof Error ? err.message : String(err);
throw rateLimited(err.message); if (err instanceof Error && err.name === "RateLimitError") {
throw rateLimited(errMsg);
} else { } else {
throw internalError(err.message || "Failed to start interview session"); throw internalError(errMsg || "Failed to start interview session");
} }
} }
}) })
@@ -473,11 +466,7 @@ export function createMissionRouter(
const rootDir = scopedStore.getRootDir(); const rootDir = scopedStore.getRootDir();
const settings = await scopedStore.getSettings(); const settings = await scopedStore.getSettings();
const { const { submitMissionInterviewResponse } = await import("./mission-interview.js");
submitMissionInterviewResponse,
SessionNotFoundError,
InvalidSessionStateError,
} = await import("./mission-interview.js");
const result = await submitMissionInterviewResponse( const result = await submitMissionInterviewResponse(
sessionId, sessionId,
@@ -486,13 +475,15 @@ export function createMissionRouter(
settings.promptOverrides, settings.promptOverrides,
); );
res.json(result); res.json(result);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "SessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "InvalidSessionStateError") { if (errName === "SessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "InvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to process response"); throw internalError(errMsg || "Failed to process response");
} }
} }
}) })
@@ -531,21 +522,19 @@ export function createMissionRouter(
const rootDir = scopedStore.getRootDir(); const rootDir = scopedStore.getRootDir();
const settings = await scopedStore.getSettings(); const settings = await scopedStore.getSettings();
const { const { retryMissionInterviewSession } = await import("./mission-interview.js");
retryMissionInterviewSession,
SessionNotFoundError,
InvalidSessionStateError,
} = await import("./mission-interview.js");
await retryMissionInterviewSession(sessionId, rootDir, settings.promptOverrides); await retryMissionInterviewSession(sessionId, rootDir, settings.promptOverrides);
res.json({ success: true, sessionId }); res.json({ success: true, sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "SessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "InvalidSessionStateError") { if (errName === "SessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "InvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to retry interview session"); throw internalError(errMsg || "Failed to retry interview session");
} }
} }
}) })
@@ -576,18 +565,17 @@ export function createMissionRouter(
} }
try { try {
const { const { cancelMissionInterviewSession } = await import("./mission-interview.js");
cancelMissionInterviewSession,
SessionNotFoundError,
} = await import("./mission-interview.js");
await cancelMissionInterviewSession(sessionId); await cancelMissionInterviewSession(sessionId);
res.json({ success: true }); res.json({ success: true });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "SessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "SessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to cancel session"); throw internalError(errMsg || "Failed to cancel session");
} }
} }
}) })
@@ -696,8 +684,9 @@ export function createMissionRouter(
req.on("close", () => { req.on("close", () => {
clearInterval(heartbeat); clearInterval(heartbeat);
}); });
} catch (err: any) { } catch (err: unknown) {
writeSSEEvent(res, "error", JSON.stringify({ message: err.message || "Stream error" })); const errMsg = err instanceof Error ? err.message : String(err);
writeSSEEvent(res, "error", JSON.stringify({ message: errMsg || "Stream error" }));
res.end(); res.end();
} }
}) })
@@ -723,7 +712,6 @@ export function createMissionRouter(
getMissionInterviewSession, getMissionInterviewSession,
getMissionInterviewSummary, getMissionInterviewSummary,
cleanupMissionInterviewSession, cleanupMissionInterviewSession,
SessionNotFoundError,
} = await import("./mission-interview.js"); } = await import("./mission-interview.js");
const session = getMissionInterviewSession(sessionId); const session = getMissionInterviewSession(sessionId);
@@ -795,15 +783,17 @@ export function createMissionRouter(
// Return the full hierarchy // Return the full hierarchy
const result = missionStore.getMissionWithHierarchy(mission.id); const result = missionStore.getMissionWithHierarchy(mission.id);
res.status(201).json(result); res.status(201).json(result);
} catch (err: any) { } catch (err: unknown) {
// Re-throw ApiError subclasses without wrapping // Re-throw ApiError subclasses without wrapping
if (err instanceof ApiError) { if (err instanceof ApiError) {
throw err; throw err;
} }
if (err.name === "SessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "SessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to create mission"); throw internalError(errMsg || "Failed to create mission");
} }
} }
}) })
@@ -870,8 +860,9 @@ export function createMissionRouter(
try { try {
const mission = missionStore.updateMission(missionId, updates); const mission = missionStore.updateMission(missionId, updates);
res.json(mission); res.json(mission);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Mission not found"); throw notFound("Mission not found");
} }
throw err; throw err;
@@ -1040,8 +1031,9 @@ export function createMissionRouter(
try { try {
const mission = missionStore.updateMissionInterviewState(missionId, validatedState); const mission = missionStore.updateMissionInterviewState(missionId, validatedState);
res.json(mission); res.json(mission);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Mission not found"); throw notFound("Mission not found");
} }
throw err; throw err;
@@ -1206,8 +1198,9 @@ export function createMissionRouter(
try { try {
const milestone = missionStore.updateMilestone(milestoneId, updates); const milestone = missionStore.updateMilestone(milestoneId, updates);
res.json(milestone); res.json(milestone);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Milestone not found"); throw notFound("Milestone not found");
} }
throw err; throw err;
@@ -1281,8 +1274,9 @@ export function createMissionRouter(
try { try {
const milestone = missionStore.updateMilestoneInterviewState(milestoneId, validatedState); const milestone = missionStore.updateMilestoneInterviewState(milestoneId, validatedState);
res.json(milestone); res.json(milestone);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Milestone not found"); throw notFound("Milestone not found");
} }
throw err; throw err;
@@ -1442,8 +1436,9 @@ export function createMissionRouter(
try { try {
const slice = missionStore.updateSlice(sliceId, updates); const slice = missionStore.updateSlice(sliceId, updates);
res.json(slice); res.json(slice);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Slice not found"); throw notFound("Slice not found");
} }
throw err; throw err;
@@ -1490,8 +1485,9 @@ export function createMissionRouter(
try { try {
const slice = await missionStore.activateSlice(sliceId); const slice = await missionStore.activateSlice(sliceId);
res.json(slice); res.json(slice);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Slice not found"); throw notFound("Slice not found");
} }
throw err; throw err;
@@ -1558,7 +1554,7 @@ export function createMissionRouter(
// Validate status if provided // Validate status if provided
if (status !== undefined) { if (status !== undefined) {
if (typeof status !== "string" || !MISSION_ASSERTION_STATUSES.includes(status as any)) { if (typeof status !== "string" || !MISSION_ASSERTION_STATUSES.includes(status as MissionAssertionStatus)) {
throw badRequest(`Invalid status. Must be one of: ${MISSION_ASSERTION_STATUSES.join(", ")}`); throw badRequest(`Invalid status. Must be one of: ${MISSION_ASSERTION_STATUSES.join(", ")}`);
} }
} }
@@ -1566,7 +1562,7 @@ export function createMissionRouter(
const input: ContractAssertionCreateInput = { const input: ContractAssertionCreateInput = {
title: title.trim(), title: title.trim(),
assertion: assertionText.trim(), assertion: assertionText.trim(),
status: status as any, status: status as MissionAssertionStatus,
}; };
const created = missionStore.addContractAssertion(milestoneId, input); const created = missionStore.addContractAssertion(milestoneId, input);
@@ -1679,10 +1675,10 @@ export function createMissionRouter(
} }
if (status !== undefined) { if (status !== undefined) {
if (typeof status !== "string" || !MISSION_ASSERTION_STATUSES.includes(status as any)) { if (typeof status !== "string" || !MISSION_ASSERTION_STATUSES.includes(status as MissionAssertionStatus)) {
throw badRequest(`Invalid status. Must be one of: ${MISSION_ASSERTION_STATUSES.join(", ")}`); throw badRequest(`Invalid status. Must be one of: ${MISSION_ASSERTION_STATUSES.join(", ")}`);
} }
updates.status = status as any; updates.status = status as MissionAssertionStatus;
} }
const updated = missionStore.updateContractAssertion(assertionId, updates); const updated = missionStore.updateContractAssertion(assertionId, updates);
@@ -1733,14 +1729,15 @@ export function createMissionRouter(
try { try {
missionStore.linkFeatureToAssertion(featureId, assertionId); missionStore.linkFeatureToAssertion(featureId, assertionId);
res.json({ success: true }); res.json({ success: true });
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (err.message?.includes(`Feature ${featureId}`)) { if (errMsg.includes("not found")) {
if (errMsg.includes(`Feature ${featureId}`)) {
throw notFound("Feature not found"); throw notFound("Feature not found");
} }
throw notFound("Assertion not found"); throw notFound("Assertion not found");
} }
if (err.message?.includes("already linked")) { if (errMsg.includes("already linked")) {
throw conflict(`Feature ${featureId} is already linked to assertion ${assertionId}`); throw conflict(`Feature ${featureId} is already linked to assertion ${assertionId}`);
} }
throw err; throw err;
@@ -1768,14 +1765,15 @@ export function createMissionRouter(
try { try {
missionStore.unlinkFeatureFromAssertion(featureId, assertionId); missionStore.unlinkFeatureFromAssertion(featureId, assertionId);
res.json({ success: true }); res.json({ success: true });
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (err.message?.includes(`Feature ${featureId}`)) { if (errMsg.includes("not found")) {
if (errMsg.includes(`Feature ${featureId}`)) {
throw notFound("Feature not found"); throw notFound("Feature not found");
} }
throw notFound("Assertion not found"); throw notFound("Assertion not found");
} }
if (err.message?.includes("not linked")) { if (errMsg.includes("not linked")) {
throw badRequest(`Feature ${featureId} is not linked to assertion ${assertionId}`); throw badRequest(`Feature ${featureId} is not linked to assertion ${assertionId}`);
} }
throw err; throw err;
@@ -1883,7 +1881,7 @@ export function createMissionRouter(
// Transition feature to validating state // Transition feature to validating state
missionStore.updateFeature(featureId, { missionStore.updateFeature(featureId, {
loopState: "validating" as any, loopState: "validating" as FeatureLoopState,
}); });
// Start a validator run // Start a validator run
@@ -2021,8 +2019,9 @@ export function createMissionRouter(
recoveredCount: result.recoveredCount, recoveredCount: result.recoveredCount,
message: `Recovered ${result.recoveredCount} features`, message: `Recovered ${result.recoveredCount} features`,
}); });
} catch (err: any) { } catch (err: unknown) {
throw internalError(`Recovery failed: ${err.message}`); const errMsg = err instanceof Error ? err.message : String(err);
throw internalError(`Recovery failed: ${errMsg}`);
} }
}) })
); );
@@ -2165,8 +2164,9 @@ export function createMissionRouter(
try { try {
const feature = missionStore.updateFeature(featureId, updates); const feature = missionStore.updateFeature(featureId, updates);
res.json(feature); res.json(feature);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("not found")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("not found")) {
throw notFound("Feature not found"); throw notFound("Feature not found");
} }
throw err; throw err;
@@ -2223,9 +2223,10 @@ export function createMissionRouter(
try { try {
const feature = missionStore.linkFeatureToTask(featureId, taskId); const feature = missionStore.linkFeatureToTask(featureId, taskId);
res.json(feature); res.json(feature);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("already linked")) { const errMsg = err instanceof Error ? err.message : String(err);
throw conflict(err.message); if (errMsg.includes("already linked")) {
throw conflict(errMsg);
} }
throw err; throw err;
} }
@@ -2288,11 +2289,12 @@ export function createMissionRouter(
taskDescription || undefined, taskDescription || undefined,
); );
res.json(feature); res.json(feature);
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("already")) { const errMsg = err instanceof Error ? err.message : String(err);
throw badRequest(err.message); if (errMsg.includes("already")) {
throw badRequest(errMsg);
} }
if (err.message?.includes("TaskStore")) { if (errMsg.includes("TaskStore")) {
throw new ApiError(503, "TaskStore not available for triage operations"); throw new ApiError(503, "TaskStore not available for triage operations");
} }
throw err; throw err;
@@ -2322,8 +2324,9 @@ export function createMissionRouter(
try { try {
const triaged = await missionStore.triageSlice(sliceId); const triaged = await missionStore.triageSlice(sliceId);
res.json({ triaged, count: triaged.length }); res.json({ triaged, count: triaged.length });
} catch (err: any) { } catch (err: unknown) {
if (err.message?.includes("TaskStore")) { const errMsg = err instanceof Error ? err.message : String(err);
if (errMsg.includes("TaskStore")) {
throw new ApiError(503, "TaskStore not available for triage operations"); throw new ApiError(503, "TaskStore not available for triage operations");
} }
throw err; throw err;
@@ -2432,7 +2435,7 @@ export function createMissionRouter(
try { try {
await store.pauseTask(feature.taskId, true); await store.pauseTask(feature.taskId, true);
pausedTaskIds.push(feature.taskId); pausedTaskIds.push(feature.taskId);
} catch (err: any) { } catch (_err) {
// Log but don't fail — task may already be paused or not found // Log but don't fail — task may already be paused or not found
} }
} }
@@ -2696,10 +2699,7 @@ export function createMissionRouter(
? `Mission: "${mission.title}". ${mission.description || ""}` ? `Mission: "${mission.title}". ${mission.description || ""}`
: undefined; : undefined;
const { const { createTargetInterviewSession } = await import("./milestone-slice-interview.js");
createTargetInterviewSession,
RateLimitError,
} = await import("./milestone-slice-interview.js");
const sessionId = await createTargetInterviewSession( const sessionId = await createTargetInterviewSession(
ip, ip,
@@ -2710,11 +2710,12 @@ export function createMissionRouter(
rootDir rootDir
); );
res.status(201).json({ sessionId }); res.status(201).json({ sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "RateLimitError") { const errMsg = err instanceof Error ? err.message : String(err);
throw rateLimited(err.message); if (err instanceof Error && err.name === "RateLimitError") {
throw rateLimited(errMsg);
} else { } else {
throw internalError(err.message || "Failed to start interview session"); throw internalError(errMsg || "Failed to start interview session");
} }
} }
}) })
@@ -2756,22 +2757,20 @@ export function createMissionRouter(
} }
try { try {
const { const { submitTargetInterviewResponse } = await import("./milestone-slice-interview.js");
submitTargetInterviewResponse,
TargetSessionNotFoundError,
TargetInvalidSessionStateError,
} = await import("./milestone-slice-interview.js");
const rootDir = await getRootDirForRequest(req); const rootDir = await getRootDirForRequest(req);
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir); const result = await submitTargetInterviewResponse(sessionId, responses, rootDir);
res.json(result); res.json(result);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "TargetInvalidSessionStateError") { if (errName === "TargetSessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "TargetInvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to process response"); throw internalError(errMsg || "Failed to process response");
} }
} }
}) })
@@ -2880,8 +2879,9 @@ export function createMissionRouter(
req.on("close", () => { req.on("close", () => {
clearInterval(heartbeat); clearInterval(heartbeat);
}); });
} catch (err: any) { } catch (err: unknown) {
writeSSEEvent(res, "error", JSON.stringify({ message: err.message || "Stream error" })); const errMsg = err instanceof Error ? err.message : String(err);
writeSSEEvent(res, "error", JSON.stringify({ message: errMsg || "Stream error" }));
res.end(); res.end();
} }
}) })
@@ -2920,22 +2920,20 @@ export function createMissionRouter(
} }
try { try {
const { const { retryTargetInterviewSession } = await import("./milestone-slice-interview.js");
retryTargetInterviewSession,
TargetSessionNotFoundError,
TargetInvalidSessionStateError,
} = await import("./milestone-slice-interview.js");
const rootDir = await getRootDirForRequest(req); const rootDir = await getRootDirForRequest(req);
await retryTargetInterviewSession(sessionId, rootDir); await retryTargetInterviewSession(sessionId, rootDir);
res.json({ success: true, sessionId }); res.json({ success: true, sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "TargetInvalidSessionStateError") { if (errName === "TargetSessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "TargetInvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to retry interview session"); throw internalError(errMsg || "Failed to retry interview session");
} }
} }
}) })
@@ -2960,18 +2958,17 @@ export function createMissionRouter(
} }
try { try {
const { const { applyTargetInterview } = await import("./milestone-slice-interview.js");
applyTargetInterview,
TargetSessionNotFoundError,
} = await import("./milestone-slice-interview.js");
const milestone = applyTargetInterview(sessionId, missionStore); const milestone = applyTargetInterview(sessionId, missionStore);
res.json(milestone); res.json(milestone);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "TargetSessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to apply interview"); throw internalError(errMsg || "Failed to apply interview");
} }
} }
}) })
@@ -2997,11 +2994,13 @@ export function createMissionRouter(
const milestone = skipTargetInterview("milestone", milestoneId, missionStore); const milestone = skipTargetInterview("milestone", milestoneId, missionStore);
res.json(milestone); res.json(milestone);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "TargetSessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to skip interview"); throw internalError(errMsg || "Failed to skip interview");
} }
} }
}) })
@@ -3044,10 +3043,7 @@ export function createMissionRouter(
? `Milestone: "${milestone.title}".` ? `Milestone: "${milestone.title}".`
: undefined; : undefined;
const { const { createTargetInterviewSession } = await import("./milestone-slice-interview.js");
createTargetInterviewSession,
RateLimitError,
} = await import("./milestone-slice-interview.js");
const sessionId = await createTargetInterviewSession( const sessionId = await createTargetInterviewSession(
ip, ip,
@@ -3058,11 +3054,12 @@ export function createMissionRouter(
rootDir rootDir
); );
res.status(201).json({ sessionId }); res.status(201).json({ sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "RateLimitError") { const errMsg = err instanceof Error ? err.message : String(err);
throw rateLimited(err.message); if (err instanceof Error && err.name === "RateLimitError") {
throw rateLimited(errMsg);
} else { } else {
throw internalError(err.message || "Failed to start interview session"); throw internalError(errMsg || "Failed to start interview session");
} }
} }
}) })
@@ -3104,22 +3101,20 @@ export function createMissionRouter(
} }
try { try {
const { const { submitTargetInterviewResponse } = await import("./milestone-slice-interview.js");
submitTargetInterviewResponse,
TargetSessionNotFoundError,
TargetInvalidSessionStateError,
} = await import("./milestone-slice-interview.js");
const rootDir = await getRootDirForRequest(req); const rootDir = await getRootDirForRequest(req);
const result = await submitTargetInterviewResponse(sessionId, responses, rootDir); const result = await submitTargetInterviewResponse(sessionId, responses, rootDir);
res.json(result); res.json(result);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "TargetInvalidSessionStateError") { if (errName === "TargetSessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "TargetInvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to process response"); throw internalError(errMsg || "Failed to process response");
} }
} }
}) })
@@ -3228,8 +3223,9 @@ export function createMissionRouter(
req.on("close", () => { req.on("close", () => {
clearInterval(heartbeat); clearInterval(heartbeat);
}); });
} catch (err: any) { } catch (err: unknown) {
writeSSEEvent(res, "error", JSON.stringify({ message: err.message || "Stream error" })); const errMsg = err instanceof Error ? err.message : String(err);
writeSSEEvent(res, "error", JSON.stringify({ message: errMsg || "Stream error" }));
res.end(); res.end();
} }
}) })
@@ -3268,22 +3264,20 @@ export function createMissionRouter(
} }
try { try {
const { const { retryTargetInterviewSession } = await import("./milestone-slice-interview.js");
retryTargetInterviewSession,
TargetSessionNotFoundError,
TargetInvalidSessionStateError,
} = await import("./milestone-slice-interview.js");
const rootDir = await getRootDirForRequest(req); const rootDir = await getRootDirForRequest(req);
await retryTargetInterviewSession(sessionId, rootDir); await retryTargetInterviewSession(sessionId, rootDir);
res.json({ success: true, sessionId }); res.json({ success: true, sessionId });
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
} else if (err.name === "TargetInvalidSessionStateError") { if (errName === "TargetSessionNotFoundError") {
throw badRequest(err.message); throw notFound(errMsg);
} else if (errName === "TargetInvalidSessionStateError") {
throw badRequest(errMsg);
} else { } else {
throw internalError(err.message || "Failed to retry interview session"); throw internalError(errMsg || "Failed to retry interview session");
} }
} }
}) })
@@ -3308,18 +3302,17 @@ export function createMissionRouter(
} }
try { try {
const { const { applyTargetInterview } = await import("./milestone-slice-interview.js");
applyTargetInterview,
TargetSessionNotFoundError,
} = await import("./milestone-slice-interview.js");
const slice = applyTargetInterview(sessionId, missionStore); const slice = applyTargetInterview(sessionId, missionStore);
res.json(slice); res.json(slice);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "TargetSessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to apply interview"); throw internalError(errMsg || "Failed to apply interview");
} }
} }
}) })
@@ -3345,11 +3338,13 @@ export function createMissionRouter(
const slice = skipTargetInterview("slice", sliceId, missionStore); const slice = skipTargetInterview("slice", sliceId, missionStore);
res.json(slice); res.json(slice);
} catch (err: any) { } catch (err: unknown) {
if (err.name === "TargetSessionNotFoundError") { const errName = err instanceof Error ? err.name : "";
throw notFound(err.message); const errMsg = err instanceof Error ? err.message : String(err);
if (errName === "TargetSessionNotFoundError") {
throw notFound(errMsg);
} else { } else {
throw internalError(err.message || "Failed to skip interview"); throw internalError(errMsg || "Failed to skip interview");
} }
} }
}) })