diff --git a/src/renderer/src/components/OrchestratorLaunchModal.tsx b/src/renderer/src/components/OrchestratorLaunchModal.tsx index e7a74900bf4..ed1695eaef0 100644 --- a/src/renderer/src/components/OrchestratorLaunchModal.tsx +++ b/src/renderer/src/components/OrchestratorLaunchModal.tsx @@ -17,7 +17,13 @@ import { buildNewWorkspaceCreateTargetOptions } from '@/lib/new-workspace-projec import { getComposerEligibleRepos } from '@/lib/new-workspace-composer-repo' import { getAgentCatalog } from '@/lib/agent-catalog' import { filterEnabledTuiAgents } from '../../../shared/tui-agent-selection' -import { launchOrchestratorForProject } from '@/lib/orchestrator-launch' +import { DirectorTypePicker } from '@/components/director/DirectorTypePicker' +import { + LlmDirectorBackend, + RecipeDirectorBackend, + type DirectorKind +} from '@/lib/director-backend' +import { getRecipes } from '@/lib/recipe-director-recipes' import { translate } from '@/i18n/i18n' import type { TuiAgent } from '../../../shared/types' @@ -49,6 +55,11 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { const detectedAgentIds = useAppStore((s) => s.detectedAgentIds) const disabledTuiAgents = useAppStore((s) => s.settings?.disabledTuiAgents) const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null) + // Why: #11 owns the gate — the Recipe director option only appears under the + // experimental flag; with it off the modal behaves exactly as it did before. + const experimentalOrchestrators = useAppStore( + (s) => s.settings?.experimentalOrchestrators ?? false + ) const nameId = useId() const promptId = useId() @@ -90,10 +101,13 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { : null, [projectOptions, prefillProjectId] ) + const recipes = useMemo(() => getRecipes(), []) const [selectedOptionId, setSelectedOptionId] = useState(null) const [name, setName] = useState('') const [agent, setAgent] = useState(null) const [prompt, setPrompt] = useState('') + const [directorKind, setDirectorKind] = useState('llm') + const [recipeName, setRecipeName] = useState(null) useEffect(() => { if (visible) { @@ -101,6 +115,8 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { setAgent(defaultTuiAgent && defaultTuiAgent !== 'blank' ? defaultTuiAgent : null) setName(prefillName) setPrompt(prefillPrompt) + setDirectorKind('llm') + setRecipeName(recipes[0]?.name ?? null) } }, [ visible, @@ -108,7 +124,8 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { prefilledProjectOptionId, defaultTuiAgent, prefillName, - prefillPrompt + prefillPrompt, + recipes ]) if (!visible) { @@ -121,15 +138,28 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { ? (projects.find((p) => p.id === selectedOption.projectId) ?? null) : null + // The Recipe director is gated; if the flag is off, only the Smart path is live. + const isRecipe = directorKind === 'recipe' && experimentalOrchestrators + const handleLaunch = (): void => { if (!project) { return } - void launchOrchestratorForProject(project, { - name: name.trim() || undefined, - agent: agent ?? undefined, - prompt: prompt.trim() || undefined - }) + // Dispatch through the DirectorBackend abstraction (#8) instead of branching + // on the kind at the call site. + if (isRecipe) { + const recipe = recipes.find((entry) => entry.name === recipeName) + if (!recipe) { + return + } + void new RecipeDirectorBackend(recipe).launch(project, { name: name.trim() || undefined }) + } else { + void new LlmDirectorBackend().launch(project, { + name: name.trim() || undefined, + agent: agent ?? undefined, + prompt: prompt.trim() || undefined + }) + } closeModal() } @@ -184,6 +214,16 @@ export default function OrchestratorLaunchModal(): React.JSX.Element | null { )} /> + {experimentalOrchestrators && ( + + )}
-
- - -
-
- -