/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { useState, useCallback, useMemo } from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../../colors.js'; import { useKeypress } from '../../hooks/useKeypress.js'; import { RadioButtonSelect } from '../shared/RadioButtonSelect.js'; import { CreateStep, type CreateWizardState } from './types.js'; import { getBorderStyle } from '../../contexts/UnicodeRenderingContext.js'; interface SubagentCreationWizardProps { profiles: string[]; activeProfileName?: string | null; onSave: ( name: string, systemPrompt: string, profile: string, mode: 'auto' | 'manual', ) => Promise; onCancel: () => void; isFocused?: boolean; } type FieldName = 'name' | 'mode' | 'systemPrompt' | 'profile'; function ModeSelectModal({ currentMode, onSelect, }: { currentMode: 'auto' | 'manual'; onSelect: (mode: 'auto' | 'manual') => void; }) { const modeItems = [ { label: 'Manual (enter full system prompt)', value: 'manual' as const, key: 'manual', }, { label: 'Auto (LLM expands your description)', value: 'auto' as const, key: 'auto', }, ]; return ( Select Mode ────────────────────────────────────────────────────────── items={modeItems} onSelect={onSelect} isFocused={true} initialIndex={currentMode === 'auto' ? 1 : 0} maxItemsToShow={10} /> [ESC] Cancel ); } function ProfileSelectModal({ profiles, onSelect, }: { profiles: string[]; onSelect: (profile: string) => void; }) { const profileItems = profiles.map((p) => ({ label: p, value: p, key: p })); return ( Select Profile ────────────────────────────────────────────────────────── items={profileItems} onSelect={onSelect} isFocused={true} maxItemsToShow={10} /> [ESC] Cancel ); } function SelectionFieldDisplay({ value, isFieldFocused, }: { value: string; isFieldFocused: boolean; }) { return ( Current: {value || '(none)'} {isFieldFocused && [Enter] Select} ); } function EditingFieldDisplay({ value, multiline, }: { value: string; multiline: boolean; }) { return ( {multiline ? ( <> {value.split('\n').map((line, idx) => ( {line || ' '} ))} | ) : ( {value} | )} {multiline ? '[ESC] Done Editing' : '[Enter] Next field'} ); } function formatFieldValue(value: string, multiline: boolean): string { if (!value) return '(empty)'; if (!multiline) return value; if (value.length > 40) return `[${value.slice(0, 40)}...]`; return value; } function InactiveFieldDisplay({ value, multiline, isFieldFocused, }: { value: string; multiline: boolean; isFieldFocused: boolean; }) { return ( {formatFieldValue(value, multiline)} {isFieldFocused && [Enter] Edit} ); } function FieldDisplay({ field, focusedField, isEditing, value, multiline, }: { field: FieldName; focusedField: FieldName; isEditing: boolean; value: string; multiline: boolean; }) { const isFieldFocused = focusedField === field; if (field === 'profile' || field === 'mode') return ( ); if (isFieldFocused && isEditing) return ; return ( ); } function FormStepField({ field, label, step, focusedField, isEditing, value, multiline, }: { field: FieldName; label: string; step: number; focusedField: FieldName; isEditing: boolean; value: string; multiline?: boolean; }) { const isFieldFocused = focusedField === field; return ( {isFieldFocused ? '→ ' : ' '} {step} {label} ); } function FormFields({ state, focusedField, isEditing, }: { state: CreateWizardState; focusedField: FieldName; isEditing: boolean; }) { return ( <> Step 1: Name Step 2: Mode Step 3: {state.mode === 'auto' ? 'Description' : 'System Prompt'} Step 4: Profile Assignment ); } function CreationWizardForm({ state, focusedField, isEditing, isSaving, error, }: { state: CreateWizardState; focusedField: FieldName; isEditing: boolean; isSaving: boolean; error: string | null; }) { return ( Create New Subagent ────────────────────────────────────────────────────────── {error && ( {error} )} Controls: ↑↓ Navigate fields [Enter] Edit/Select [s] Save [ESC] Cancel {' '} Current field: {focusedField} {isEditing ? ' (editing)' : ''} {isSaving && ( Creating subagent... )} ); } function useModalEscape( isFocused: boolean, showModeSelect: boolean, showProfileSelect: boolean, setShowModeSelect: (v: boolean) => void, setShowProfileSelect: (v: boolean) => void, ) { useKeypress( (key) => { if (showModeSelect && key.name === 'escape') { setShowModeSelect(false); return; } if (showProfileSelect && key.name === 'escape') { setShowProfileSelect(false); } }, { isActive: isFocused && (showModeSelect || showProfileSelect) }, ); } function useEditingKeys(opts: { isFocused: boolean; isModalOpen: boolean; isEditing: boolean; focusedField: FieldName; handleBackspace: () => void; handleInput: (c: string) => void; setIsEditing: (v: boolean) => void; moveField: (d: number) => void; }) { useKeypress( (key) => { if (!opts.isFocused || opts.isModalOpen || !opts.isEditing) return; if (key.name === 'escape') { opts.setIsEditing(false); return; } if (key.name === 'backspace' || key.name === 'delete') { opts.handleBackspace(); return; } if (key.name === 'return' && opts.focusedField === 'systemPrompt') { opts.handleInput('\n'); return; } if (key.name === 'return' && opts.focusedField === 'name') { opts.setIsEditing(false); opts.moveField(1); return; } const input = key.sequence; if (input && !key.ctrl && !key.meta) { opts.handleInput(input); } }, { isActive: opts.isFocused && !opts.isModalOpen && opts.isEditing }, ); } function useNavKeys(opts: { isSaving: boolean; isModalOpen: boolean; isEditing: boolean; focusedField: FieldName; moveField: (d: number) => void; setShowModeSelect: (v: boolean) => void; setShowProfileSelect: (v: boolean) => void; setIsEditing: (v: boolean) => void; handleSave: () => Promise; onCancel: () => void; isFocused: boolean; }) { useKeypress( (key) => { if (opts.isSaving || opts.isModalOpen || opts.isEditing) return; if (key.name === 'escape') { opts.onCancel(); return; } if (key.name === 'up') { opts.moveField(-1); return; } if (key.name === 'down') { opts.moveField(1); return; } if (key.name === 'return') { if (opts.focusedField === 'mode') opts.setShowModeSelect(true); else if (opts.focusedField === 'profile') opts.setShowProfileSelect(true); else opts.setIsEditing(true); return; } if (key.sequence === 's') { void opts.handleSave(); } }, { isActive: opts.isFocused && !opts.isSaving && !opts.isModalOpen }, ); } function useWizardState(profiles: string[], activeProfileName?: string | null) { return useState(() => ({ currentStep: CreateStep.FORM, name: '', mode: 'auto', systemPrompt: '', selectedProfile: (activeProfileName && profiles.includes(activeProfileName) ? activeProfileName : profiles[0]) || '', validationErrors: {}, })); } function useInputHandlers( focusedField: FieldName, setState: React.Dispatch>, ) { const handleInput = useCallback( (char: string) => { setState((prev) => { if (focusedField === 'profile' || focusedField === 'mode') return prev; if (focusedField === 'name' && char === ' ') return prev; const field = focusedField; if (char === '\n' && field === 'systemPrompt') return { ...prev, [field]: prev[field] + '\n' }; return { ...prev, [field]: prev[field] + char }; }); }, [focusedField, setState], ); const handleBackspace = useCallback(() => { setState((prev) => { if (focusedField === 'profile' || focusedField === 'mode') return prev; return { ...prev, [focusedField]: prev[focusedField].slice(0, -1) }; }); }, [focusedField, setState]); return { handleInput, handleBackspace }; } function useSaveHandler( state: CreateWizardState, onSave: SubagentCreationWizardProps['onSave'], setError: React.Dispatch>, setIsSaving: React.Dispatch>, ) { const validateName = useCallback((name: string): string | null => { if (!name.trim()) return 'Name is required'; if (!/^[a-zA-Z0-9_-]+$/.test(name)) return 'Only letters, numbers, hyphens, and underscores allowed'; return null; }, []); return useCallback(async () => { const nameError = validateName(state.name); if (nameError) { setError(nameError); return; } if (!state.systemPrompt.trim()) { setError( state.mode === 'auto' ? 'Description is required' : 'System prompt is required', ); return; } if (!state.selectedProfile) { setError('Profile is required'); return; } setIsSaving(true); setError(null); try { await onSave( state.name, state.systemPrompt, state.selectedProfile, state.mode, ); setIsSaving(false); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create'); setIsSaving(false); } }, [state, validateName, onSave, setError, setIsSaving]); } function useSelectHandlers( setState: React.Dispatch>, setFocusedField: React.Dispatch>, setShowModeSelect: React.Dispatch>, setShowProfileSelect: React.Dispatch>, ) { const handleModeSelect = useCallback( (mode: 'auto' | 'manual') => { setState((prev) => ({ ...prev, mode })); setShowModeSelect(false); setFocusedField('mode'); }, [setState, setFocusedField, setShowModeSelect], ); const handleProfileSelect = useCallback( (profile: string) => { setState((prev) => ({ ...prev, selectedProfile: profile })); setShowProfileSelect(false); setFocusedField('profile'); }, [setState, setFocusedField, setShowProfileSelect], ); return { handleModeSelect, handleProfileSelect }; } function useFieldMover( focusedField: FieldName, fields: FieldName[], setFocusedField: React.Dispatch>, ) { return useCallback( (delta: number) => { const currentIndex = fields.indexOf(focusedField); let newIndex = currentIndex + delta; if (newIndex < 0) newIndex = 0; if (newIndex >= fields.length) newIndex = fields.length - 1; setFocusedField(fields[newIndex]); }, [focusedField, fields, setFocusedField], ); } export const SubagentCreationWizard: React.FC = ({ profiles, activeProfileName, onSave, onCancel, isFocused = true, }) => { const [state, setState] = useWizardState(profiles, activeProfileName); const [focusedField, setFocusedField] = useState('name'); const [isEditing, setIsEditing] = useState(false); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const [showModeSelect, setShowModeSelect] = useState(false); const [showProfileSelect, setShowProfileSelect] = useState(false); const fields = useMemo( () => ['name', 'mode', 'systemPrompt', 'profile'], [], ); const { handleInput, handleBackspace } = useInputHandlers( focusedField, setState, ); const handleSave = useSaveHandler(state, onSave, setError, setIsSaving); const { handleModeSelect, handleProfileSelect } = useSelectHandlers( setState, setFocusedField, setShowModeSelect, setShowProfileSelect, ); const moveField = useFieldMover(focusedField, fields, setFocusedField); const isModalOpen = showModeSelect || showProfileSelect; useModalEscape( isFocused, showModeSelect, showProfileSelect, setShowModeSelect, setShowProfileSelect, ); useEditingKeys({ isFocused, isModalOpen, isEditing, focusedField, handleBackspace, handleInput, setIsEditing, moveField, }); useNavKeys({ isSaving, isModalOpen, isEditing, focusedField, moveField, setShowModeSelect, setShowProfileSelect, setIsEditing, handleSave, onCancel, isFocused, }); if (showModeSelect) return ( ); if (showProfileSelect) return ( ); return ( ); };