feat(FN-3105): add planning depth selection to planning flows

- Add planning depth controls and styling in PlanningModeModal with updated modal reentry behavior
- Thread selected depth through legacy frontend API calls and planning route handlers
- Extend planning prompt builder/runtime wiring to honor depth values for subtask planning
- Add route, planning, and modal tests covering depth selector behavior and validation

Fusion-Task-Id: FN-3105
This commit is contained in:
Fusion
2026-05-02 11:09:10 -07:00
committed by gsxdsm
parent 31022b2ec1
commit ffb71b7912
9 changed files with 371 additions and 17 deletions

View File

@@ -393,12 +393,28 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
*/
router.post("/planning/start", async (req, res) => {
try {
const { initialPlan } = req.body;
const { initialPlan, planningDepth, customQuestionCount } = req.body;
if (!initialPlan || typeof initialPlan !== "string") {
throw badRequest("initialPlan is required and must be a string");
}
if (
planningDepth !== undefined
&& planningDepth !== "small"
&& planningDepth !== "medium"
&& planningDepth !== "large"
) {
throw badRequest('planningDepth must be one of "small", "medium", or "large" when provided');
}
if (
customQuestionCount !== undefined
&& (!Number.isInteger(customQuestionCount) || customQuestionCount < 1 || customQuestionCount > 20)
) {
throw badRequest("customQuestionCount must be an integer between 1 and 20 when provided");
}
const { store: scopedStore } = await getProjectContext(req);
const settings = await scopedStore.getSettings();
const ip = req.ip || req.socket.remoteAddress || "unknown";
@@ -411,6 +427,8 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
scopedStore,
rootDir,
settings.promptOverrides,
planningDepth,
customQuestionCount,
);
res.status(201).json(result);
} catch (err: unknown) {
@@ -438,7 +456,13 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
*/
router.post("/planning/start-streaming", async (req, res) => {
try {
const { initialPlan, planningModelProvider, planningModelId } = req.body;
const {
initialPlan,
planningModelProvider,
planningModelId,
planningDepth,
customQuestionCount,
} = req.body;
if (!initialPlan || typeof initialPlan !== "string") {
throw badRequest("initialPlan is required and must be a string");
@@ -452,6 +476,22 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
throw badRequest("planningModelId must be a string when provided");
}
if (
planningDepth !== undefined
&& planningDepth !== "small"
&& planningDepth !== "medium"
&& planningDepth !== "large"
) {
throw badRequest('planningDepth must be one of "small", "medium", or "large" when provided');
}
if (
customQuestionCount !== undefined
&& (!Number.isInteger(customQuestionCount) || customQuestionCount < 1 || customQuestionCount > 20)
) {
throw badRequest("customQuestionCount must be an integer between 1 and 20 when provided");
}
const { store: scopedStore, projectId } = await getProjectContext(req);
const settings = await scopedStore.getSettings();
const ip = req.ip || req.socket.remoteAddress || "unknown";
@@ -487,6 +527,8 @@ export function registerPlanningSubtaskRoutes(ctx: ApiRoutesContext, deps: Plann
dashboardHost: settings.ntfyDashboardHost,
events: settings.ntfyEvents,
},
planningDepth,
customQuestionCount,
},
);
res.status(201).json({ sessionId });