diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index bcdb92a8..9a72fc04 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -316,6 +316,8 @@ interface RunningFeature { abortController: AbortController; isAutoMode: boolean; startTime: number; + model?: string; + provider?: 'claude' | 'cursor'; } interface AutoLoopState { @@ -604,9 +606,16 @@ export class AutoModeService { typeof img === 'string' ? img : img.path ); - // Get model from feature + // Get model from feature and determine provider const model = resolveModelString(feature.model, DEFAULT_MODELS.claude); - console.log(`[AutoMode] Executing feature ${featureId} with model: ${model} in ${workDir}`); + const provider = ProviderFactory.getProviderNameForModel(model); + console.log( + `[AutoMode] Executing feature ${featureId} with model: ${model}, provider: ${provider} in ${workDir}` + ); + + // Store model and provider in running feature for tracking + tempRunningFeature.model = model; + tempRunningFeature.provider = provider; // Run the agent with the feature's model and images // Context files are passed as system prompt for higher priority @@ -640,6 +649,8 @@ export class AutoModeService { (Date.now() - tempRunningFeature.startTime) / 1000 )}s${finalStatus === 'verified' ? ' - auto-verified' : ''}`, projectPath, + model: tempRunningFeature.model, + provider: tempRunningFeature.provider, }); } catch (error) { const errorInfo = classifyError(error); @@ -805,6 +816,13 @@ ${prompt} ## Task Address the follow-up instructions above. Review the previous work and make the requested changes or fixes.`; + // Get model from feature and determine provider early for tracking + const model = resolveModelString(feature?.model, DEFAULT_MODELS.claude); + const provider = ProviderFactory.getProviderNameForModel(model); + console.log( + `[AutoMode] Follow-up for feature ${featureId} using model: ${model}, provider: ${provider}` + ); + this.runningFeatures.set(featureId, { featureId, projectPath, @@ -813,6 +831,8 @@ Address the follow-up instructions above. Review the previous work and make the abortController, isAutoMode: false, startTime: Date.now(), + model, + provider, }); this.emitAutoModeEvent('auto_mode_feature_start', { @@ -823,13 +843,11 @@ Address the follow-up instructions above. Review the previous work and make the title: 'Follow-up', description: prompt.substring(0, 100), }, + model, + provider, }); try { - // Get model from feature (already loaded above) - const model = resolveModelString(feature?.model, DEFAULT_MODELS.claude); - console.log(`[AutoMode] Follow-up for feature ${featureId} using model: ${model}`); - // Update feature status to in_progress await this.updateFeatureStatus(projectPath, featureId, 'in_progress'); @@ -925,6 +943,8 @@ Address the follow-up instructions above. Review the previous work and make the passes: true, message: `Follow-up completed successfully${finalStatus === 'verified' ? ' - auto-verified' : ''}`, projectPath, + model, + provider, }); } catch (error) { const errorInfo = classifyError(error); @@ -1217,12 +1237,16 @@ Format your response as a structured markdown document.`; projectPath: string; projectName: string; isAutoMode: boolean; + model?: string; + provider?: 'claude' | 'cursor'; }> { return Array.from(this.runningFeatures.values()).map((rf) => ({ featureId: rf.featureId, projectPath: rf.projectPath, projectName: path.basename(rf.projectPath), isAutoMode: rf.isAutoMode, + model: rf.model, + provider: rf.provider, })); } diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index a5eea2c5..77e866b1 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -312,11 +312,18 @@ export function AddFeatureDialog({ } }; - const handleModelSelect = (model: AgentModel) => { + const handleModelSelect = (model: string) => { + // For Cursor models, thinking is handled by the model itself + // For Claude models, check if it supports extended thinking + const isCursorModel = model.startsWith('cursor-'); setNewFeature({ ...newFeature, - model, - thinkingLevel: modelSupportsThinking(model) ? newFeature.thinkingLevel : 'none', + model: model as AgentModel, + thinkingLevel: isCursorModel + ? 'none' + : modelSupportsThinking(model) + ? newFeature.thinkingLevel + : 'none', }); }; @@ -328,7 +335,9 @@ export function AddFeatureDialog({ }); }; - const newModelAllowsThinking = modelSupportsThinking(newFeature.model); + // Cursor models handle thinking internally, so only show thinking selector for Claude models + const isCursorModel = newFeature.model.startsWith('cursor-'); + const newModelAllowsThinking = !isCursorModel && modelSupportsThinking(newFeature.model); return ( diff --git a/apps/ui/src/components/views/board-view/shared/model-constants.ts b/apps/ui/src/components/views/board-view/shared/model-constants.ts index d08a9d21..53615143 100644 --- a/apps/ui/src/components/views/board-view/shared/model-constants.ts +++ b/apps/ui/src/components/views/board-view/shared/model-constants.ts @@ -1,12 +1,16 @@ import type { AgentModel, ThinkingLevel } from '@/store/app-store'; +import type { ModelProvider } from '@automaker/types'; +import { CURSOR_MODEL_MAP } from '@automaker/types'; import { Brain, Zap, Scale, Cpu, Rocket, Sparkles } from 'lucide-react'; export type ModelOption = { - id: AgentModel; + id: string; // Claude models use AgentModel, Cursor models use "cursor-{id}" label: string; description: string; badge?: string; - provider: 'claude'; + provider: ModelProvider; + hasThinking?: boolean; + tier?: 'free' | 'pro'; }; export const CLAUDE_MODELS: ModelOption[] = [ @@ -33,6 +37,26 @@ export const CLAUDE_MODELS: ModelOption[] = [ }, ]; +/** + * Cursor models derived from CURSOR_MODEL_MAP + * ID is prefixed with "cursor-" for ProviderFactory routing + */ +export const CURSOR_MODELS: ModelOption[] = Object.entries(CURSOR_MODEL_MAP).map( + ([id, config]) => ({ + id: `cursor-${id}`, + label: config.label, + description: config.description, + provider: 'cursor' as ModelProvider, + hasThinking: config.hasThinking, + tier: config.tier, + }) +); + +/** + * All available models (Claude + Cursor) + */ +export const ALL_MODELS: ModelOption[] = [...CLAUDE_MODELS, ...CURSOR_MODELS]; + export const THINKING_LEVELS: ThinkingLevel[] = ['none', 'low', 'medium', 'high', 'ultrathink']; export const THINKING_LEVEL_LABELS: Record = { diff --git a/apps/ui/src/components/views/board-view/shared/model-selector.tsx b/apps/ui/src/components/views/board-view/shared/model-selector.tsx index 55a0fe83..764aae33 100644 --- a/apps/ui/src/components/views/board-view/shared/model-selector.tsx +++ b/apps/ui/src/components/views/board-view/shared/model-selector.tsx @@ -1,54 +1,178 @@ import { Label } from '@/components/ui/label'; -import { Brain } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; +import { Brain, Bot, Terminal } from 'lucide-react'; import { cn } from '@/lib/utils'; -import { AgentModel } from '@/store/app-store'; -import { CLAUDE_MODELS, ModelOption } from './model-constants'; +import type { AgentModel } from '@/store/app-store'; +import type { ModelProvider } from '@automaker/types'; +import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants'; interface ModelSelectorProps { - selectedModel: AgentModel; - onModelSelect: (model: AgentModel) => void; + selectedModel: string; // Can be AgentModel or "cursor-{id}" + onModelSelect: (model: string) => void; testIdPrefix?: string; } +/** + * Get the provider from a model string + */ +function getProviderFromModelString(model: string): ModelProvider { + if (model.startsWith('cursor-')) { + return 'cursor'; + } + return 'claude'; +} + export function ModelSelector({ selectedModel, onModelSelect, testIdPrefix = 'model-select', }: ModelSelectorProps) { + const selectedProvider = getProviderFromModelString(selectedModel); + + const handleProviderChange = (provider: ModelProvider) => { + if (provider === 'cursor' && selectedProvider !== 'cursor') { + // Switch to Cursor's default model + onModelSelect('cursor-auto'); + } else if (provider === 'claude' && selectedProvider !== 'claude') { + // Switch to Claude's default model + onModelSelect('sonnet'); + } + }; + return ( -
-
- - - Native - -
-
- {CLAUDE_MODELS.map((option) => { - const isSelected = selectedModel === option.id; - const shortName = option.label.replace('Claude ', ''); - return ( - - ); - })} +
+ {/* Provider Selection */} +
+ +
+ + +
+ + {/* Claude Models */} + {selectedProvider === 'claude' && ( +
+
+ + + Native SDK + +
+
+ {CLAUDE_MODELS.map((option) => { + const isSelected = selectedModel === option.id; + const shortName = option.label.replace('Claude ', ''); + return ( + + ); + })} +
+
+ )} + + {/* Cursor Models */} + {selectedProvider === 'cursor' && ( +
+
+ + + CLI + +
+
+ {CURSOR_MODELS.map((option) => { + const isSelected = selectedModel === option.id; + return ( + + ); + })} +
+
+ )}
); } diff --git a/plan/cursor-cli-integration/README.md b/plan/cursor-cli-integration/README.md index ebf2b51b..af814a20 100644 --- a/plan/cursor-cli-integration/README.md +++ b/plan/cursor-cli-integration/README.md @@ -15,7 +15,7 @@ | 6 | [UI Setup Wizard](phases/phase-6-setup-wizard.md) | `completed` | ✅ | | 7 | [Settings View Provider Tabs](phases/phase-7-settings.md) | `completed` | ✅ | | 8 | [AI Profiles Integration](phases/phase-8-profiles.md) | `completed` | ✅ | -| 9 | [Task Execution Integration](phases/phase-9-execution.md) | `pending` | - | +| 9 | [Task Execution Integration](phases/phase-9-execution.md) | `completed` | ✅ | | 10 | [Testing & Validation](phases/phase-10-testing.md) | `pending` | - | **Status Legend:** `pending` | `in_progress` | `completed` | `blocked` @@ -393,3 +393,4 @@ Cursor models use their own `CURSOR_MODEL_MAP` in `@automaker/types`. | 2025-12-27 | 1 | Added tasks 1.5-1.7: ModelOption, DEFAULT_MODELS, reuse InstallationStatus | | 2025-12-27 | 2 | Refactored to use `spawnJSONLProcess` and `isAbortError` from @automaker packages | | 2025-12-27 | - | Added design decisions 4-5: @automaker packages usage, model-resolver note | +| 2025-12-28 | 9 | Completed: ModelSelector with Cursor models, provider tracking in execution events | diff --git a/plan/cursor-cli-integration/phases/phase-9-execution.md b/plan/cursor-cli-integration/phases/phase-9-execution.md index a1dbfc2e..46fd364b 100644 --- a/plan/cursor-cli-integration/phases/phase-9-execution.md +++ b/plan/cursor-cli-integration/phases/phase-9-execution.md @@ -1,6 +1,6 @@ # Phase 9: Task Execution Integration -**Status:** `pending` +**Status:** `completed` **Dependencies:** Phase 3 (Factory), Phase 8 (Profiles) **Estimated Effort:** Medium (service updates)