feat(FN-1799): merge fusion/fn-1799
This commit is contained in:
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user