-
Notifications
You must be signed in to change notification settings - Fork 487
feat: implement cursor model migration and enhance auto mode function… #590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ality This commit introduces significant updates to the cursor model handling and auto mode features. The cursor model IDs have been standardized to a canonical format, ensuring backward compatibility while migrating legacy IDs. New endpoints for starting and stopping the auto mode loop have been added, allowing for better control over project-specific auto mode operations. Key changes: - Updated cursor model IDs to use the 'cursor-' prefix for consistency. - Added new API endpoints: `/start` and `/stop` for managing auto mode. - Enhanced the status endpoint to provide detailed project-specific auto mode information. - Improved error handling and logging throughout the auto mode service. - Migrated legacy model IDs to their canonical counterparts in various components. This update aims to streamline the user experience and ensure a smooth transition for existing users while providing new functionalities.
📝 WalkthroughWalkthroughThis PR refactors the model ID system across the codebase to use canonical, prefixed identifiers (e.g., Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @webdevcody, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refines the application's model handling and auto mode capabilities. The primary goal is to establish a robust and consistent model identification system across all providers, particularly for Cursor models, while also upgrading the auto mode to support multiple projects concurrently. These changes aim to provide a more stable and scalable foundation for automated workflows, ensuring a smoother user experience and better control over project-specific operations. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a significant and well-executed refactoring to standardize cursor model IDs and enhance the auto mode functionality to be project-specific. The migration to canonical model IDs is handled robustly across the backend and frontend, ensuring backward compatibility. The introduction of per-project auto mode loops with dedicated /start and /stop endpoints is a great enhancement for multi-project workflows.
My review focuses on a couple of minor maintainability improvements. I've suggested refactoring a repeated magic number into a constant and pointed out an area where some filtering logic could be simplified for better consistency and readability.
Overall, these are excellent changes that improve the architecture and user experience.
| await autoModeService.startAutoLoopForProject(projectPath, maxConcurrency ?? 3); | ||
|
|
||
| logger.info( | ||
| `Started auto loop for project: ${projectPath} with maxConcurrency: ${maxConcurrency ?? 3}` | ||
| ); | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| message: `Auto mode started with max ${maxConcurrency ?? 3} concurrent features`, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To improve maintainability and avoid magic numbers, the default value for maxConcurrency (which is 3) should be extracted into a constant. This value is repeated three times in this block.
You can define a constant at the top of the file:
const DEFAULT_MAX_CONCURRENCY = 3;And then use it like this:
const effectiveMaxConcurrency = maxConcurrency ?? 3;
// Start the auto loop for this project
await autoModeService.startAutoLoopForProject(projectPath, effectiveMaxConcurrency);
logger.info(
`Started auto loop for project: ${projectPath} with maxConcurrency: ${effectiveMaxConcurrency}`
);
res.json({
success: true,
message: `Auto mode started with max ${effectiveMaxConcurrency} concurrent features`,
});| const filteredCursorModels = CURSOR_MODELS.filter((model) => { | ||
| // Compare model.id directly since both model.id and enabledCursorModels use full IDs with prefix | ||
| return enabledCursorModels.includes(model.id as any); | ||
| // enabledCursorModels stores CursorModelIds which may or may not have "cursor-" prefix | ||
| // (e.g., 'auto', 'sonnet-4.5' without prefix, but 'cursor-gpt-5.2' with prefix) | ||
| // CURSOR_MODELS always has the "cursor-" prefix added in model-constants.ts | ||
| // Check both the full ID (for GPT models) and the unprefixed version (for non-GPT models) | ||
| const unprefixedId = model.id.startsWith('cursor-') ? model.id.slice(7) : model.id; | ||
| return ( | ||
| enabledCursorModels.includes(model.id as any) || | ||
| enabledCursorModels.includes(unprefixedId as any) | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This filtering logic seems unnecessarily complex and is inconsistent with the implementation in PhaseModelSelector. The settings migration hooks (use-settings-migration.ts and use-settings-sync.ts) ensure that enabledCursorModels in the store contains the complete list of canonical, prefixed model IDs.
Given this, a simple enabledCursorModels.includes(model.id) should be sufficient and would make the code more maintainable and consistent.
const filteredCursorModels = CURSOR_MODELS.filter((model) => {
// enabledCursorModels from the store should contain all canonical model IDs.
return enabledCursorModels.includes(model.id as any);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/ui/src/components/views/settings-view/model-defaults/phase-model-selector.tsx (1)
14-14: Remove unused importstripProviderPrefixon line 14.The search confirms
stripProviderPrefixis imported but never used in the file. This is dead code that should be removed from the import statement.apps/server/src/services/auto-mode-service.ts (1)
238-466: Per-project failure tracking is defined but not used in execution flow.The new per-project helpers won’t take effect unless execution paths call them. Currently, success/failure handling still uses legacy globals, so per-project loops won’t pause as intended and failure counts can bleed across projects. Wire
trackFailureAndCheckPauseForProject/signalShouldPauseForProject/recordSuccessForProjectintoexecuteFeatureandfollowUpFeature(the helper already falls back to legacy tracking).💡 Add legacy fallback for success tracking
private recordSuccessForProject(projectPath: string): void { const projectState = this.autoLoopsByProject.get(projectPath); if (projectState) { projectState.consecutiveFailures = []; + return; } + this.recordSuccess(); }Example call-site update (outside this range):
const shouldPause = this.trackFailureAndCheckPauseForProject(projectPath, { type: errorInfo.type, message: errorInfo.message, }); if (shouldPause) { this.signalShouldPauseForProject(projectPath, { type: errorInfo.type, message: errorInfo.message, }); } // On success: this.recordSuccessForProject(projectPath);
🤖 Fix all issues with AI agents
In `@apps/server/src/services/auto-mode-service.ts`:
- Around line 669-698: The per-project execution-state is not kept current
because executeFeature still calls the legacy saveExecutionState gated by
autoLoopRunning; update the executeFeature flow to detect the project's
auto-loop config (use getAutoLoopConfigForProject(projectPath)) and when
isAutoMode is true call saveExecutionStateForProject(projectPath,
config.maxConcurrency) for that project (after feature start and after feature
finish) and only fall back to saveExecutionState(projectPath) when isAutoMode is
true but no per-project config exists (or when autoLoopRunning is true for
legacy behavior); ensure you update both the feature-start and
feature-completion code paths to use this logic so runningFeatureIds and
maxConcurrency stay in sync for recovery.
- Around line 529-619: runningFeatures is keyed only by featureId which allows
collisions across projects; update the data model and checks to include project
scope: change the runningFeatures Map key usage to a composite key (e.g.
`${projectPath}:${featureId}`) everywhere it's read/written (references:
runningFeatures Map, runAutoLoopForProject's pendingFeatures.find check,
executeFeature where a feature is added/removed) and update
getRunningCountForProject to either iterate keys parsing projectPath or maintain
a per-project map. Ensure all places that call this.runningFeatures.has, .get,
.set, and .delete use the composite key (or switch to a Map<string, Map<string,
RunningFeature>> keyed by projectPath then featureId) so features from different
projects do not collide.
In `@apps/ui/src/hooks/use-auto-mode.ts`:
- Around line 97-124: The effect currently depends on isAutoModeRunning and also
calls setAutoModeRunning inside syncWithBackend which can cause redundant
re-runs and stale comparisons; remove isAutoModeRunning from the useEffect
dependency array and instead read the latest UI value via a ref inside
syncWithBackend: create a ref (e.g. isAutoModeRunningRef) and update it whenever
isAutoModeRunning changes, then inside syncWithBackend compare backendIsRunning
to isAutoModeRunningRef.current before calling setAutoModeRunning and
setAutoModeSessionForProjectPath; keep currentProject and setAutoModeRunning in
the dependency array so the effect only runs on project changes or stable
setters.
In `@apps/ui/src/hooks/use-settings-migration.ts`:
- Around line 657-658: The enhancementModel and validationModel assignments
should normalize legacy aliases using the migrateModelId utility: replace direct
fallbacks (settings.enhancementModel ?? 'claude-sonnet' and
settings.validationModel ?? 'claude-opus') with calls to
migrateModelId(settings.enhancementModel ?? 'claude-sonnet') and
migrateModelId(settings.validationModel ?? 'claude-opus') respectively; also
apply the same migrateModelId normalization where enhancementModel and
validationModel are passed through in refreshSettingsFromServer so stored legacy
values like "sonnet" or "opus" are normalized to canonical IDs (use the
migrateModelId function from `@automaker/types` to locate and import it if not
already).
In `@libs/model-resolver/src/resolver.ts`:
- Around line 100-105: The CLAUDE_MODEL_MAP lookup using canonicalKey is dead
because migrateModelId() already converts legacy aliases and
CLAUDE_CANONICAL_MAP returns before this block; either remove the entire
CLAUDE_MODEL_MAP check (the console.log and return) or change it to consult the
pre-migration input (store the raw/original model id before calling
migrateModelId() and use that to lookup CLAUDE_MODEL_MAP) so the legacy alias
check can actually match; update references to CLAUDE_MODEL_MAP, canonicalKey,
migrateModelId(), and CLAUDE_CANONICAL_MAP accordingly.
In `@libs/types/src/model-migration.ts`:
- Around line 49-52: migrateModelId currently casts a falsy legacyId to string,
which hides null/undefined; update migrateModelId to either (A) return a
canonical default string when legacyId is null/undefined (e.g., a constant
DEFAULT_MODEL_ID) so callers always get a valid string, or (B) change the
signature to return string | null | undefined and propagate that nullable type
through callers; locate the function migrateModelId in model-migration.ts and
implement one of these fixes and update any callers/tests to handle the chosen
return shape.
In `@libs/types/src/provider-utils.ts`:
- Around line 234-276: The current normalizeModelString implementation returns a
reconstructed string `claude-${model}` for legacy Claude aliases which drops the
full versioned identifier; change the Claude branch in normalizeModelString to
return the canonical value from CLAUDE_MODEL_MAP (e.g., CLAUDE_MODEL_MAP[model])
or call resolveModelString(model) from `@automaker/model-resolver` so the full
versioned model (like claude-sonnet-4-5-20250929) is preserved; locate the
Claude branch inside normalizeModelString and replace the `claude-${model}`
return with CLAUDE_MODEL_MAP[model] or resolveModelString(model).
🧹 Nitpick comments (10)
apps/server/src/routes/backlog-plan/common.ts (1)
103-147: Well-structured error mapping with clear categorization.The function provides good user-friendly translations for common SDK/CLI errors. A few observations:
The status code checks (lines 123, 140) using
includes('429')andincludes('401')could match unintended substrings (e.g., port numbers, error codes like4290). Consider using word boundaries or checking for common status code patterns like"status 429"or" 429 ".Good use of case-insensitive matching via
toLowerCase()for generic terms while keeping exact matches for specific error strings.🔧 Optional: More precise status code matching
// Rate limiting - if (rawMessage.toLowerCase().includes('rate limit') || rawMessage.includes('429')) { + if (rawMessage.toLowerCase().includes('rate limit') || /\b429\b/.test(rawMessage)) { return 'Rate limited. Please wait a moment and try again.'; } ... // Authentication errors if ( rawMessage.toLowerCase().includes('not authenticated') || rawMessage.toLowerCase().includes('unauthorized') || - rawMessage.includes('401') + /\b401\b/.test(rawMessage) ) {apps/ui/src/lib/electron.ts (1)
486-505: Keep mock auto‑mode status aligned with new fields.The interface now exposes
isAutoLoopRunningandmaxConcurrency, but the mockstatus()response doesn’t return them, so the web preview can surfaceundefined. Consider tracking and returning them for parity.♻️ Suggested mock parity update
let mockAutoModeRunning = false; +let mockMaxConcurrency = 3; let mockRunningFeatures = new Set<string>(); // Track multiple concurrent feature verifications function createMockAutoModeAPI(): AutoModeAPI { return { start: async (projectPath: string, maxConcurrency?: number) => { if (mockAutoModeRunning) { return { success: false, error: 'Auto mode is already running' }; } mockAutoModeRunning = true; - console.log(`[Mock] Auto mode started with maxConcurrency: ${maxConcurrency || 3}`); + mockMaxConcurrency = maxConcurrency ?? 3; + console.log(`[Mock] Auto mode started with maxConcurrency: ${mockMaxConcurrency}`); const featureId = 'auto-mode-0'; mockRunningFeatures.add(featureId); // Simulate auto mode with Plan-Act-Verify phases simulateAutoModeLoop(projectPath, featureId); return { success: true }; }, stop: async (_projectPath: string) => { mockAutoModeRunning = false; + mockMaxConcurrency = 3; const runningCount = mockRunningFeatures.size; mockRunningFeatures.clear(); // Clear all timeouts mockAutoModeTimeouts.forEach((timeout) => clearTimeout(timeout)); mockAutoModeTimeouts.clear(); return { success: true, runningFeatures: runningCount }; }, status: async (_projectPath?: string) => { return { success: true, isRunning: mockAutoModeRunning, + isAutoLoopRunning: mockAutoModeRunning, currentFeatureId: mockAutoModeRunning ? 'feature-0' : null, runningFeatures: Array.from(mockRunningFeatures), runningCount: mockRunningFeatures.size, + maxConcurrency: mockMaxConcurrency, }; },apps/server/src/providers/cursor-config-manager.ts (1)
79-97: Normalize legacy Cursor IDs when reading config.Older
cursor-config.jsonfiles may still store legacy IDs (e.g.,auto), and the getters currently return them as‑is. Consider normalizing on read so callers always see canonical IDs.♻️ Suggested normalization in getters
-import { getAllCursorModelIds, type CursorCliConfig, type CursorModelId } from '@automaker/types'; +import { + getAllCursorModelIds, + migrateModelId, + type CursorCliConfig, + type CursorModelId, +} from '@automaker/types'; getDefaultModel(): CursorModelId { - return this.config.defaultModel || 'cursor-auto'; + return (migrateModelId(this.config.defaultModel) as CursorModelId) || 'cursor-auto'; } getEnabledModels(): CursorModelId[] { - return this.config.models || ['cursor-auto']; + return ( + this.config.models?.map((model) => migrateModelId(model) as CursorModelId) ?? + ['cursor-auto'] + ); }libs/types/src/model-migration.ts (2)
94-118: Fallback to prefixed unknown IDs could create invalid model IDs.Lines 110-116: When an ID is not recognized as legacy or canonical, the function adds the
cursor-prefix unconditionally. This could create invalid model IDs (e.g.,cursor-some-typo) that won't be recognized by the Cursor CLI.Consider logging a warning or returning the input unchanged for truly unknown IDs to aid debugging:
♻️ Suggested improvement
// Unknown - assume it might be a valid cursor model with prefix if (id.startsWith('cursor-')) { return id as CursorModelId; } - // Add prefix if not present - return `cursor-${id}` as CursorModelId; + // Unknown ID - pass through unchanged to avoid creating invalid prefixed IDs + // The caller should validate the result + console.warn(`Unknown Cursor model ID: ${id}, passing through unchanged`); + return id as CursorModelId; }); }
126-154: Same fallback concern for OpenCode model IDs.Lines 147-152: Similar to the Cursor migration, adding the
opencode-prefix to unknown IDs could create invalid model identifiers.♻️ Suggested improvement
// Convert slash to dash format for unknown models if (id.startsWith('opencode/')) { return id.replace('opencode/', 'opencode-') as OpencodeModelId; } - // Add prefix if not present - if (!id.startsWith('opencode-')) { - return `opencode-${id}` as OpencodeModelId; - } + // Unknown ID - pass through unchanged + console.warn(`Unknown OpenCode model ID: ${id}, passing through unchanged`); return id as OpencodeModelId; });apps/ui/src/components/views/board-view/shared/model-selector.tsx (2)
72-81: Backward-compatible filtering for cursor models looks good.The dual-check approach (checking both the full
model.idand the unprefixed version) ensures compatibility during the migration period whenenabledCursorModelsmay contain either format.However, the
as anycasts on lines 79-80 bypass type safety. Consider using proper type assertions or updating the type definitions to accept both formats explicitly.♻️ Optional: Improve type safety
- return ( - enabledCursorModels.includes(model.id as any) || - enabledCursorModels.includes(unprefixedId as any) - ); + return ( + enabledCursorModels.includes(model.id as CursorModelId) || + enabledCursorModels.includes(unprefixedId as CursorModelId) + );This requires importing
CursorModelIdfrom@automaker/types.
94-96: Hardcoded Claude default model.The default Claude model is hardcoded as
'claude-sonnet'. Consider defining this as a constant (e.g.,CLAUDE_DEFAULT_MODEL) to maintain consistency if this value is used elsewhere or needs to change.apps/server/src/routes/auto-mode/routes/start.ts (1)
39-47: Consider extracting default maxConcurrency to avoid repetition.The default value
3is repeated three times (lines 39, 42, 47). Extracting to a constant improves maintainability.♻️ Optional: Extract default constant
+const DEFAULT_MAX_CONCURRENCY = 3; + export function createStartHandler(autoModeService: AutoModeService) { return async (req: Request, res: Response): Promise<void> => { try { const { projectPath, maxConcurrency } = req.body as { projectPath: string; maxConcurrency?: number; }; // ... + const effectiveConcurrency = maxConcurrency ?? DEFAULT_MAX_CONCURRENCY; // Start the auto loop for this project - await autoModeService.startAutoLoopForProject(projectPath, maxConcurrency ?? 3); + await autoModeService.startAutoLoopForProject(projectPath, effectiveConcurrency); logger.info( - `Started auto loop for project: ${projectPath} with maxConcurrency: ${maxConcurrency ?? 3}` + `Started auto loop for project: ${projectPath} with maxConcurrency: ${effectiveConcurrency}` ); res.json({ success: true, - message: `Auto mode started with max ${maxConcurrency ?? 3} concurrent features`, + message: `Auto mode started with max ${effectiveConcurrency} concurrent features`, });apps/ui/src/hooks/use-auto-mode.ts (1)
154-168: Redundant null checks for eventProjectId.The checks
if (eventProjectId)on lines 157 and 165 are unnecessary since lines 148-151 already return early ifeventProjectIdis falsy.♻️ Remove redundant checks
case 'auto_mode_started': // Backend started auto loop - update UI state logger.info('[AutoMode] Backend started auto loop for project'); - if (eventProjectId) { - setAutoModeRunning(eventProjectId, true); - } + setAutoModeRunning(eventProjectId, true); break; case 'auto_mode_stopped': // Backend stopped auto loop - update UI state logger.info('[AutoMode] Backend stopped auto loop for project'); - if (eventProjectId) { - setAutoModeRunning(eventProjectId, false); - } + setAutoModeRunning(eventProjectId, false); break;apps/ui/src/components/views/board-view.tsx (1)
1134-1140: Surface start/stop failures to users (not just logs).Right now a failed start/stop only logs, which can leave the toggle state ambiguous for users. Consider a toast (and optionally a state refresh) on failure.
💡 Suggested tweak
autoMode.start().catch((error) => { logger.error('[AutoMode] Failed to start:', error); + toast.error('Failed to start auto mode', { + description: error instanceof Error ? error.message : 'An unexpected error occurred', + }); }); } else { autoMode.stop().catch((error) => { logger.error('[AutoMode] Failed to stop:', error); + toast.error('Failed to stop auto mode', { + description: error instanceof Error ? error.message : 'An unexpected error occurred', + }); }); }
| /** | ||
| * Run the auto loop for a specific project | ||
| */ | ||
| private async runAutoLoopForProject(projectPath: string): Promise<void> { | ||
| const projectState = this.autoLoopsByProject.get(projectPath); | ||
| if (!projectState) { | ||
| logger.warn(`No project state found for ${projectPath}, stopping loop`); | ||
| return; | ||
| } | ||
|
|
||
| logger.info( | ||
| `[AutoLoop] Starting loop for ${projectPath}, maxConcurrency: ${projectState.config.maxConcurrency}` | ||
| ); | ||
| let iterationCount = 0; | ||
|
|
||
| while (projectState.isRunning && !projectState.abortController.signal.aborted) { | ||
| iterationCount++; | ||
| try { | ||
| // Count running features for THIS project only | ||
| const projectRunningCount = this.getRunningCountForProject(projectPath); | ||
|
|
||
| // Check if we have capacity for this project | ||
| if (projectRunningCount >= projectState.config.maxConcurrency) { | ||
| logger.debug( | ||
| `[AutoLoop] At capacity (${projectRunningCount}/${projectState.config.maxConcurrency}), waiting...` | ||
| ); | ||
| await this.sleep(5000); | ||
| continue; | ||
| } | ||
|
|
||
| // Load pending features for this project | ||
| const pendingFeatures = await this.loadPendingFeatures(projectPath); | ||
|
|
||
| logger.debug( | ||
| `[AutoLoop] Iteration ${iterationCount}: Found ${pendingFeatures.length} pending features, ${projectRunningCount} running` | ||
| ); | ||
|
|
||
| if (pendingFeatures.length === 0) { | ||
| this.emitAutoModeEvent('auto_mode_idle', { | ||
| message: 'No pending features - auto mode idle', | ||
| projectPath, | ||
| }); | ||
| logger.info(`[AutoLoop] No pending features, sleeping for 10s...`); | ||
| await this.sleep(10000); | ||
| continue; | ||
| } | ||
|
|
||
| // Find a feature not currently running | ||
| const nextFeature = pendingFeatures.find((f) => !this.runningFeatures.has(f.id)); | ||
|
|
||
| if (nextFeature) { | ||
| logger.info(`[AutoLoop] Starting feature ${nextFeature.id}: ${nextFeature.title}`); | ||
| // Start feature execution in background | ||
| this.executeFeature( | ||
| projectPath, | ||
| nextFeature.id, | ||
| projectState.config.useWorktrees, | ||
| true | ||
| ).catch((error) => { | ||
| logger.error(`Feature ${nextFeature.id} error:`, error); | ||
| }); | ||
| } else { | ||
| logger.debug(`[AutoLoop] All pending features are already running`); | ||
| } | ||
|
|
||
| await this.sleep(2000); | ||
| } catch (error) { | ||
| logger.error(`[AutoLoop] Loop iteration error for ${projectPath}:`, error); | ||
| await this.sleep(5000); | ||
| } | ||
| } | ||
|
|
||
| // Mark as not running when loop exits | ||
| projectState.isRunning = false; | ||
| logger.info( | ||
| `[AutoLoop] Loop stopped for project: ${projectPath} after ${iterationCount} iterations` | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get count of running features for a specific project | ||
| */ | ||
| private getRunningCountForProject(projectPath: string): number { | ||
| let count = 0; | ||
| for (const [, feature] of this.runningFeatures) { | ||
| if (feature.projectPath === projectPath) { | ||
| count++; | ||
| } | ||
| } | ||
| return count; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ID generation patterns mentioned in the review
rg -n "createFeature|featureId|uuid|ulid|nanoid" --type=ts --type=tsx --type=js --type=jsx -C3 | head -100Repository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
#!/bin/bash
# Search for ID generation patterns without type restrictions
rg -n "createFeature|featureId|uuid|ulid|nanoid" -C3 | head -150Repository: AutoMaker-Org/automaker
Length of output: 9231
🏁 Script executed:
#!/bin/bash
# Find the Feature type definition
rg -n "interface Feature|type Feature|class Feature" -A5 | head -80Repository: AutoMaker-Org/automaker
Length of output: 4480
🏁 Script executed:
#!/bin/bash
# Get the full Feature interface definition
cat -n libs/types/src/feature.ts | head -100Repository: AutoMaker-Org/automaker
Length of output: 2705
🏁 Script executed:
#!/bin/bash
# Search for where Feature objects are created
rg -n "new Feature|createFeature|feature\s*=\s*{|\.id\s*=" --max-count=50 | head -100Repository: AutoMaker-Org/automaker
Length of output: 240
🏁 Script executed:
#!/bin/bash
# Simpler search for Feature creation
rg -n "createFeature" -A5 | head -80Repository: AutoMaker-Org/automaker
Length of output: 6264
🏁 Script executed:
#!/bin/bash
# Search for runningFeatures in auto-mode-service.ts
rg -n "runningFeatures" apps/server/src/services/auto-mode-service.ts -B2 -A2 | head -100Repository: AutoMaker-Org/automaker
Length of output: 3505
🏁 Script executed:
#!/bin/bash
# Search for where Feature objects are created/loaded in actual production code
rg -n "loadPendingFeatures\|loadFeatures\|getFeatures" apps/server/src/services/ -A10 | head -150Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Find FeatureLoader class definition
rg -n "class FeatureLoader" -A20 | head -80Repository: AutoMaker-Org/automaker
Length of output: 1608
🏁 Script executed:
#!/bin/bash
# Find feature loading methods in FeatureLoader
rg -n "loadPendingFeatures\|loadFeatures\|readFeatures\|getFeatures" apps/server/src/services/feature-loader.ts -A15 | head -200Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Find loadPendingFeatures in auto-mode-service.ts
rg -n "loadPendingFeatures" apps/server/src/services/auto-mode-service.ts -A20 | head -150Repository: AutoMaker-Org/automaker
Length of output: 4532
🏁 Script executed:
#!/bin/bash
# Find where feature.json files are created/written
rg -n "feature\.json" apps/server/src/services/ -B5 -A5 | head -200Repository: AutoMaker-Org/automaker
Length of output: 9523
🏁 Script executed:
#!/bin/bash
# Search for where features are created/directories are created
rg -n "createFeature\|mkdir\|makeDir\|createDir" apps/server/src/services/feature-loader.ts -B5 -A10 | head -200Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Search for feature creation in UI or API routes
fd -e ts -e js | xargs rg -l "feature.*create\|createFeature\|new Feature" apps/ | head -20Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Search for API routes or endpoints that handle feature creation
rg -n "POST\|PUT\|patch.*feature\|/feature" apps/server/src/routes/ -A5 | head -150Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# List the route files in the server
find apps/server/src -name "*route*" -o -name "*api*" | head -20Repository: AutoMaker-Org/automaker
Length of output: 831
Verify how feature IDs are scoped across projects to confirm runningFeatures collision risk.
The concern is valid: runningFeatures is a global Map<string, RunningFeature> keyed only by featureId (line 278), but features are stored per-project in separate directories. If two projects have features with the same ID, they will collide when executing. The check at line 577 (!this.runningFeatures.has(f.id)) does not account for projectPath, and feature IDs are loaded from per-project feature.json files with no global uniqueness constraint.
Keying by ${projectPath}:${featureId} or restructuring to per-project maps is necessary for correct multi-project operation.
🤖 Prompt for AI Agents
In `@apps/server/src/services/auto-mode-service.ts` around lines 529 - 619,
runningFeatures is keyed only by featureId which allows collisions across
projects; update the data model and checks to include project scope: change the
runningFeatures Map key usage to a composite key (e.g.
`${projectPath}:${featureId}`) everywhere it's read/written (references:
runningFeatures Map, runAutoLoopForProject's pendingFeatures.find check,
executeFeature where a feature is added/removed) and update
getRunningCountForProject to either iterate keys parsing projectPath or maintain
a per-project map. Ensure all places that call this.runningFeatures.has, .get,
.set, and .delete use the composite key (or switch to a Map<string, Map<string,
RunningFeature>> keyed by projectPath then featureId) so features from different
projects do not collide.
| /** | ||
| * Save execution state for a specific project | ||
| */ | ||
| private async saveExecutionStateForProject( | ||
| projectPath: string, | ||
| maxConcurrency: number | ||
| ): Promise<void> { | ||
| try { | ||
| await ensureAutomakerDir(projectPath); | ||
| const statePath = getExecutionStatePath(projectPath); | ||
| const runningFeatureIds = Array.from(this.runningFeatures.entries()) | ||
| .filter(([, f]) => f.projectPath === projectPath) | ||
| .map(([id]) => id); | ||
|
|
||
| const state: ExecutionState = { | ||
| version: 1, | ||
| autoLoopWasRunning: true, | ||
| maxConcurrency, | ||
| projectPath, | ||
| runningFeatureIds, | ||
| savedAt: new Date().toISOString(), | ||
| }; | ||
| await secureFs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf-8'); | ||
| logger.info( | ||
| `Saved execution state for ${projectPath}: ${runningFeatureIds.length} running features` | ||
| ); | ||
| } catch (error) { | ||
| logger.error(`Failed to save execution state for ${projectPath}:`, error); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per-project execution-state.json won’t stay in sync after feature start/finish.
saveExecutionStateForProject is only called at loop start; executeFeature still uses the legacy saveExecutionState guarded by autoLoopRunning, which won’t be true for per-project loops. That means execution-state.json can be stale (missing runningFeatureIds/maxConcurrency), breaking recovery after restart. Suggest updating the execution flow to save per-project state when that project’s loop is running, with legacy fallback.
Example (outside this range):
const config = this.getAutoLoopConfigForProject(projectPath);
if (isAutoMode && config) {
await this.saveExecutionStateForProject(projectPath, config.maxConcurrency);
} else if (isAutoMode && this.autoLoopRunning) {
await this.saveExecutionState(projectPath);
}🤖 Prompt for AI Agents
In `@apps/server/src/services/auto-mode-service.ts` around lines 669 - 698, The
per-project execution-state is not kept current because executeFeature still
calls the legacy saveExecutionState gated by autoLoopRunning; update the
executeFeature flow to detect the project's auto-loop config (use
getAutoLoopConfigForProject(projectPath)) and when isAutoMode is true call
saveExecutionStateForProject(projectPath, config.maxConcurrency) for that
project (after feature start and after feature finish) and only fall back to
saveExecutionState(projectPath) when isAutoMode is true but no per-project
config exists (or when autoLoopRunning is true for legacy behavior); ensure you
update both the feature-start and feature-completion code paths to use this
logic so runningFeatureIds and maxConcurrency stay in sync for recovery.
| // On mount, query backend for current auto loop status and sync UI state. | ||
| // This handles cases where the backend is still running after a page refresh. | ||
| useEffect(() => { | ||
| if (!currentProject) return; | ||
|
|
||
| const session = readAutoModeSession(); | ||
| const desired = session[currentProject.path]; | ||
| if (typeof desired !== 'boolean') return; | ||
| const syncWithBackend = async () => { | ||
| try { | ||
| const api = getElectronAPI(); | ||
| if (!api?.autoMode?.status) return; | ||
|
|
||
| const result = await api.autoMode.status(currentProject.path); | ||
| if (result.success && result.isAutoLoopRunning !== undefined) { | ||
| const backendIsRunning = result.isAutoLoopRunning; | ||
| if (backendIsRunning !== isAutoModeRunning) { | ||
| logger.info( | ||
| `[AutoMode] Syncing UI state with backend for ${currentProject.path}: ${backendIsRunning ? 'ON' : 'OFF'}` | ||
| ); | ||
| setAutoModeRunning(currentProject.id, backendIsRunning); | ||
| setAutoModeSessionForProjectPath(currentProject.path, backendIsRunning); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| logger.error('Error syncing auto mode state with backend:', error); | ||
| } | ||
| }; | ||
|
|
||
| if (desired !== isAutoModeRunning) { | ||
| logger.info( | ||
| `[AutoMode] Restoring session state for ${currentProject.path}: ${desired ? 'ON' : 'OFF'}` | ||
| ); | ||
| setAutoModeRunning(currentProject.id, desired); | ||
| } | ||
| syncWithBackend(); | ||
| }, [currentProject, isAutoModeRunning, setAutoModeRunning]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential infinite loop or redundant API calls in sync effect.
The isAutoModeRunning is included in the dependency array (line 124), but the effect also calls setAutoModeRunning (line 114) which changes this value. This could cause:
- Unnecessary re-syncs when the UI state changes from user actions
- The comparison on line 110 uses a potentially stale value from the previous render
Consider removing isAutoModeRunning from dependencies and using a ref or comparing against the fresh store value inside the async callback.
🔧 Suggested fix
useEffect(() => {
if (!currentProject) return;
const syncWithBackend = async () => {
try {
const api = getElectronAPI();
if (!api?.autoMode?.status) return;
const result = await api.autoMode.status(currentProject.path);
if (result.success && result.isAutoLoopRunning !== undefined) {
const backendIsRunning = result.isAutoLoopRunning;
- if (backendIsRunning !== isAutoModeRunning) {
+ // Get fresh state from store to avoid stale closure
+ const currentState = useAppStore.getState().autoModeByProject[currentProject.id];
+ const uiIsRunning = currentState?.isRunning ?? false;
+ if (backendIsRunning !== uiIsRunning) {
logger.info(
`[AutoMode] Syncing UI state with backend for ${currentProject.path}: ${backendIsRunning ? 'ON' : 'OFF'}`
);
setAutoModeRunning(currentProject.id, backendIsRunning);
setAutoModeSessionForProjectPath(currentProject.path, backendIsRunning);
}
}
} catch (error) {
logger.error('Error syncing auto mode state with backend:', error);
}
};
syncWithBackend();
- }, [currentProject, isAutoModeRunning, setAutoModeRunning]);
+ }, [currentProject, setAutoModeRunning]);🤖 Prompt for AI Agents
In `@apps/ui/src/hooks/use-auto-mode.ts` around lines 97 - 124, The effect
currently depends on isAutoModeRunning and also calls setAutoModeRunning inside
syncWithBackend which can cause redundant re-runs and stale comparisons; remove
isAutoModeRunning from the useEffect dependency array and instead read the
latest UI value via a ref inside syncWithBackend: create a ref (e.g.
isAutoModeRunningRef) and update it whenever isAutoModeRunning changes, then
inside syncWithBackend compare backendIsRunning to isAutoModeRunningRef.current
before calling setAutoModeRunning and setAutoModeSessionForProjectPath; keep
currentProject and setAutoModeRunning in the dependency array so the effect only
runs on project changes or stable setters.
| enhancementModel: settings.enhancementModel ?? 'claude-sonnet', | ||
| validationModel: settings.validationModel ?? 'claude-opus', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and understand its context
fd "use-settings-migration.ts" -x wc -l {}Repository: AutoMaker-Org/automaker
Length of output: 116
🏁 Script executed:
# Search for migratePhaseModelEntry function
rg "migratePhaseModelEntry" --type ts --type tsx -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
# Search for resolveModelString function mentioned in learnings
rg "resolveModelString" --type ts --type tsx -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
# Check for model-resolver imports in the project
rg "from.*model-resolver|import.*model-resolver" --type ts --type tsxRepository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
# Examine the use-settings-migration.ts file around lines 657-658
sed -n '640,680p' apps/ui/src/hooks/use-settings-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 2244
🏁 Script executed:
# Search without type restrictions
rg "migratePhaseModelEntry" -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 6373
🏁 Script executed:
# Search for resolveModelString
rg "resolveModelString" -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 48915
🏁 Script executed:
# Check the imports at the top of the file
head -50 apps/ui/src/hooks/use-settings-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 2054
🏁 Script executed:
# Find where migratePhaseModelEntry is defined
rg "export.*function.*migratePhaseModelEntry|const migratePhaseModelEntry" -B 2 -A 10Repository: AutoMaker-Org/automaker
Length of output: 900
🏁 Script executed:
# Find migrateModelId function
rg "export.*function.*migrateModelId|const migrateModelId" -B 2 -A 10Repository: AutoMaker-Org/automaker
Length of output: 890
🏁 Script executed:
# Check what migratePhaseModelEntry returns and how it's used elsewhere
sed -n '1,100p' libs/types/src/model-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 3269
🏁 Script executed:
# Examine the GlobalSettings type to understand enhancementModel and validationModel structure
rg "type GlobalSettings|interface GlobalSettings" -A 30Repository: AutoMaker-Org/automaker
Length of output: 9046
🏁 Script executed:
# Get full GlobalSettings type definition to see enhancementModel and validationModel structure
sed -n '/export interface GlobalSettings/,/^}/p' libs/types/src/settings.ts | head -150Repository: AutoMaker-Org/automaker
Length of output: 5769
🏁 Script executed:
# Check the exact structure in use-settings-migration.ts around lines 650-670
sed -n '640,670p' apps/ui/src/hooks/use-settings-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 1839
🏁 Script executed:
# See how use-settings-sync.ts handles these fields in refreshSettingsFromServer
sed -n '250,350p' apps/ui/src/hooks/use-settings-sync.tsRepository: AutoMaker-Org/automaker
Length of output: 4103
🏁 Script executed:
# Check if migrateModelId is exported from types
rg "export.*migrateModelId" libs/types/src/Repository: AutoMaker-Org/automaker
Length of output: 178
🏁 Script executed:
# Look at use-settings-sync.ts to see how refreshSettingsFromServer handles enhancementModel/validationModel
sed -n '1,150p' apps/ui/src/hooks/use-settings-sync.ts | grep -A 100 "refreshSettingsFromServer"Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Search for enhancementModel/validationModel usage in use-settings-sync
rg "enhancementModel|validationModel" apps/ui/src/hooks/use-settings-sync.ts -B 2 -A 2Repository: AutoMaker-Org/automaker
Length of output: 1165
🏁 Script executed:
# Check if migrateModelId is exported from `@automaker/types`
rg "export.*from.*model-migration" libs/types/src/index.tsRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check the exact behavior of migrateModelId with legacy aliases
rg -A 25 "Legacy Claude alias \(short name\)" libs/types/src/model-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 730
🏁 Script executed:
# Look at full migratePhaseModelEntry to understand its return type better
sed -n '60,120p' libs/types/src/model-migration.tsRepository: AutoMaker-Org/automaker
Length of output: 1625
🏁 Script executed:
# Check if migrateModelId is exported from `@automaker/types`
rg "migrateModelId" libs/types/src/index.tsRepository: AutoMaker-Org/automaker
Length of output: 84
🏁 Script executed:
# Verify the complete LEGACY_CLAUDE_ALIAS_MAP to understand what gets normalized
rg -B 5 "LEGACY_CLAUDE_ALIAS_MAP" libs/types/src/model.ts | head -30Repository: AutoMaker-Org/automaker
Length of output: 207
🏁 Script executed:
# Check what happens with legacy 'sonnet' and 'opus' values
rg "sonnet|opus" libs/types/src/model.ts -B 3 -A 1Repository: AutoMaker-Org/automaker
Length of output: 1063
Use migrateModelId() to normalize legacy model aliases.
Legacy string values like 'sonnet' and 'opus' can slip through from stored settings and no longer match canonical lists (which expect 'claude-sonnet', 'claude-opus'). Normalize here using the same migration utilities already available in @automaker/types.
🛠️ Suggested fix
+ enhancementModel: migrateModelId(settings.enhancementModel ?? 'sonnet'),
+ validationModel: migrateModelId(settings.validationModel ?? 'opus'),
- enhancementModel: settings.enhancementModel ?? 'claude-sonnet',
- validationModel: settings.validationModel ?? 'claude-opus',Also apply the same normalization in refreshSettingsFromServer where these fields are currently passed through unchanged.
🤖 Prompt for AI Agents
In `@apps/ui/src/hooks/use-settings-migration.ts` around lines 657 - 658, The
enhancementModel and validationModel assignments should normalize legacy aliases
using the migrateModelId utility: replace direct fallbacks
(settings.enhancementModel ?? 'claude-sonnet' and settings.validationModel ??
'claude-opus') with calls to migrateModelId(settings.enhancementModel ??
'claude-sonnet') and migrateModelId(settings.validationModel ?? 'claude-opus')
respectively; also apply the same migrateModelId normalization where
enhancementModel and validationModel are passed through in
refreshSettingsFromServer so stored legacy values like "sonnet" or "opus" are
normalized to canonical IDs (use the migrateModelId function from
`@automaker/types` to locate and import it if not already).
| // Legacy Claude model alias (sonnet, opus, haiku) - support for backward compatibility | ||
| const resolved = CLAUDE_MODEL_MAP[canonicalKey]; | ||
| if (resolved) { | ||
| console.log(`[ModelResolver] Resolved Claude model alias: "${modelKey}" -> "${resolved}"`); | ||
| console.log(`[ModelResolver] Resolved Claude legacy alias: "${canonicalKey}" -> "${resolved}"`); | ||
| return resolved; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine LEGACY_CLAUDE_ALIAS_MAP and related migration logic
echo "=== Checking for LEGACY_CLAUDE_ALIAS_MAP in codebase ==="
rg -n "LEGACY_CLAUDE_ALIAS_MAP|CLAUDE_MODEL_MAP" --type ts -B 2 -A 8 libs/
echo ""
echo "=== Examining resolver.ts around lines 60-105 ==="
head -n 110 libs/model-resolver/src/resolver.ts | tail -n 60Repository: AutoMaker-Org/automaker
Length of output: 28751
Lines 100-105 are unreachable dead code for legacy Claude aliases.
After migrateModelId() converts legacy aliases ("sonnet", "opus", "haiku") to canonical IDs ("claude-sonnet", etc.) at line 63, the code checks CLAUDE_CANONICAL_MAP at line 88 and matches, returning before ever reaching the CLAUDE_MODEL_MAP lookup at line 101. The CLAUDE_MODEL_MAP check expects keys like "sonnet", but canonicalKey is "claude-sonnet" after migration. Remove these lines or document if they're intentional as a safety fallback.
🤖 Prompt for AI Agents
In `@libs/model-resolver/src/resolver.ts` around lines 100 - 105, The
CLAUDE_MODEL_MAP lookup using canonicalKey is dead because migrateModelId()
already converts legacy aliases and CLAUDE_CANONICAL_MAP returns before this
block; either remove the entire CLAUDE_MODEL_MAP check (the console.log and
return) or change it to consult the pre-migration input (store the raw/original
model id before calling migrateModelId() and use that to lookup
CLAUDE_MODEL_MAP) so the legacy alias check can actually match; update
references to CLAUDE_MODEL_MAP, canonicalKey, migrateModelId(), and
CLAUDE_CANONICAL_MAP accordingly.
| export function migrateModelId(legacyId: string | undefined | null): string { | ||
| if (!legacyId) { | ||
| return legacyId as string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return type inconsistency when input is null/undefined.
When legacyId is null or undefined, returning legacyId as string casts a falsy value to string type, which is misleading. The return type suggests a string is always returned, but callers may receive null or undefined.
Consider explicitly returning a default canonical ID or adjusting the return type:
🔧 Option 1: Return a sensible default
export function migrateModelId(legacyId: string | undefined | null): string {
if (!legacyId) {
- return legacyId as string;
+ return 'claude-sonnet'; // Default canonical ID
}🔧 Option 2: Adjust return type to reflect reality
-export function migrateModelId(legacyId: string | undefined | null): string {
+export function migrateModelId(legacyId: string | undefined | null): string | undefined | null {
if (!legacyId) {
- return legacyId as string;
+ return legacyId;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function migrateModelId(legacyId: string | undefined | null): string { | |
| if (!legacyId) { | |
| return legacyId as string; | |
| } | |
| export function migrateModelId(legacyId: string | undefined | null): string | undefined | null { | |
| if (!legacyId) { | |
| return legacyId; | |
| } |
🤖 Prompt for AI Agents
In `@libs/types/src/model-migration.ts` around lines 49 - 52, migrateModelId
currently casts a falsy legacyId to string, which hides null/undefined; update
migrateModelId to either (A) return a canonical default string when legacyId is
null/undefined (e.g., a constant DEFAULT_MODEL_ID) so callers always get a valid
string, or (B) change the signature to return string | null | undefined and
propagate that nullable type through callers; locate the function migrateModelId
in model-migration.ts and implement one of these fixes and update any
callers/tests to handle the chosen return shape.
| * Normalize a model string to its canonical form | ||
| * - For Cursor: adds cursor- prefix if missing | ||
| * - For Codex: can add codex- prefix (but bare gpt-* is also valid) | ||
| * - For Claude: returns as-is | ||
| * | ||
| * With the new canonical format: | ||
| * - Cursor models: always have cursor- prefix | ||
| * - OpenCode models: always have opencode- prefix (static) or provider/model format (dynamic) | ||
| * - Claude models: can use legacy aliases or claude- prefix | ||
| * - Codex models: always have codex- prefix | ||
| * | ||
| * @param model - Model string to normalize | ||
| * @returns Normalized model string | ||
| */ | ||
| export function normalizeModelString(model: string | undefined | null): string { | ||
| if (!model || typeof model !== 'string') return 'sonnet'; // Default | ||
| if (!model || typeof model !== 'string') return 'claude-sonnet'; // Default to canonical | ||
|
|
||
| // Already has a canonical prefix - return as-is | ||
| if ( | ||
| model.startsWith(PROVIDER_PREFIXES.cursor) || | ||
| model.startsWith(PROVIDER_PREFIXES.codex) || | ||
| model.startsWith(PROVIDER_PREFIXES.opencode) || | ||
| model.startsWith('claude-') | ||
| ) { | ||
| return model; | ||
| } | ||
|
|
||
| // If it's a Cursor model without prefix, add the prefix | ||
| if (model in CURSOR_MODEL_MAP && !model.startsWith(PROVIDER_PREFIXES.cursor)) { | ||
| return `${PROVIDER_PREFIXES.cursor}${model}`; | ||
| // Check if it's a legacy Cursor model ID | ||
| if (model in LEGACY_CURSOR_MODEL_MAP) { | ||
| return LEGACY_CURSOR_MODEL_MAP[model as keyof typeof LEGACY_CURSOR_MODEL_MAP]; | ||
| } | ||
|
|
||
| // For Codex, bare gpt-* and o-series models are valid canonical forms | ||
| // Check if it's in the CODEX_MODEL_MAP | ||
| if (model in CODEX_MODEL_MAP) { | ||
| // If it already starts with gpt- or o, it's canonical | ||
| if (model.startsWith('gpt-') || /^o\d/.test(model)) { | ||
| return model; | ||
| } | ||
| // Otherwise, it might need a prefix (though this is unlikely) | ||
| if (!model.startsWith(PROVIDER_PREFIXES.codex)) { | ||
| return `${PROVIDER_PREFIXES.codex}${model}`; | ||
| } | ||
| // Check if it's a legacy OpenCode model ID | ||
| if (model in LEGACY_OPENCODE_MODEL_MAP) { | ||
| return LEGACY_OPENCODE_MODEL_MAP[model as keyof typeof LEGACY_OPENCODE_MODEL_MAP]; | ||
| } | ||
|
|
||
| // Legacy Claude aliases | ||
| if (model in CLAUDE_MODEL_MAP) { | ||
| return `claude-${model}`; | ||
| } | ||
|
|
||
| // For Codex, bare gpt-* and o-series models need codex- prefix | ||
| if (model.startsWith('gpt-') || /^o\d/.test(model)) { | ||
| return `${PROVIDER_PREFIXES.codex}${model}`; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and inspect CLAUDE_MODEL_MAP definition
rg -n "CLAUDE_MODEL_MAP\s*=" libs/types/src/ -A 10Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Locate resolveModelString in the codebase
fd "model-resolver" --type fRepository: AutoMaker-Org/automaker
Length of output: 116
🏁 Script executed:
#!/bin/bash
# Check current imports in provider-utils.ts
head -30 libs/types/src/provider-utils.tsRepository: AutoMaker-Org/automaker
Length of output: 1137
🏁 Script executed:
#!/bin/bash
# Find CLAUDE_MODEL_MAP definition in all files
rg "CLAUDE_MODEL_MAP\s*=" --type ts --type js -A 15Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Locate model.js in libs/types/src/
ls -la libs/types/src/ | grep modelRepository: AutoMaker-Org/automaker
Length of output: 463
🏁 Script executed:
#!/bin/bash
# Search for resolveModelString in the entire codebase
rg "resolveModelString" --type ts --type js -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 40371
🏁 Script executed:
#!/bin/bash
# Read CLAUDE_MODEL_MAP definition in model.ts
cat -n libs/types/src/model.ts | head -80Repository: AutoMaker-Org/automaker
Length of output: 3293
🏁 Script executed:
#!/bin/bash
# Read the resolver.ts implementation to understand resolveModelString logic
cat -n libs/model-resolver/src/resolver.ts | head -150Repository: AutoMaker-Org/automaker
Length of output: 6226
🏁 Script executed:
#!/bin/bash
# Read the normalizeModelString function in provider-utils.ts
sed -n '234,276p' libs/types/src/provider-utils.tsRepository: AutoMaker-Org/automaker
Length of output: 1559
🏁 Script executed:
#!/bin/bash
# Check if resolveModelString is imported in provider-utils.ts
grep -n "import.*resolveModelString" libs/types/src/provider-utils.tsRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
#!/bin/bash
# Check what package model-resolver is and if it's accessible from provider-utils
fd "package.json" libs/types libs/model-resolver | xargs grep -l "@automaker/model-resolver" 2>/dev/null | head -5Repository: AutoMaker-Org/automaker
Length of output: 99
🏁 Script executed:
#!/bin/bash
# Confirm what CLAUDE_MODEL_MAP contains in model.ts - the actual values
rg "CLAUDE_MODEL_MAP:" libs/types/src/model.ts -A 5Repository: AutoMaker-Org/automaker
Length of output: 251
🏁 Script executed:
#!/bin/bash
# Check if there's a migrateModelId function mentioned in the resolver
rg "migrateModelId" libs/types/src/ -B 2 -A 5Repository: AutoMaker-Org/automaker
Length of output: 1745
Use CLAUDE_MODEL_MAP[model] or switch to resolveModelString() to preserve full model versions.
The code at line 269 returns claude-${model} for aliases, which discards the full versioned string stored in CLAUDE_MODEL_MAP. For example, input 'sonnet' produces 'claude-sonnet' instead of the correct 'claude-sonnet-4-5-20250929'. Either access the map value directly (CLAUDE_MODEL_MAP[model]) or delegate to resolveModelString() from @automaker/model-resolver, which already handles this correctly and is used throughout the codebase.
🤖 Prompt for AI Agents
In `@libs/types/src/provider-utils.ts` around lines 234 - 276, The current
normalizeModelString implementation returns a reconstructed string
`claude-${model}` for legacy Claude aliases which drops the full versioned
identifier; change the Claude branch in normalizeModelString to return the
canonical value from CLAUDE_MODEL_MAP (e.g., CLAUDE_MODEL_MAP[model]) or call
resolveModelString(model) from `@automaker/model-resolver` so the full versioned
model (like claude-sonnet-4-5-20250929) is preserved; locate the Claude branch
inside normalizeModelString and replace the `claude-${model}` return with
CLAUDE_MODEL_MAP[model] or resolveModelString(model).
…ality
This commit introduces significant updates to the cursor model handling and auto mode features. The cursor model IDs have been standardized to a canonical format, ensuring backward compatibility while migrating legacy IDs. New endpoints for starting and stopping the auto mode loop have been added, allowing for better control over project-specific auto mode operations.
Key changes:
/startand/stopfor managing auto mode.This update aims to streamline the user experience and ensure a smooth transition for existing users while providing new functionalities.
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.