feat: implement project setup dialog and refactor sidebar integration

- Added a new ProjectSetupDialog component to facilitate project specification generation, enhancing user experience by guiding users through project setup.
- Refactored the Sidebar component to integrate the new ProjectSetupDialog, replacing the previous inline dialog implementation for improved code organization and maintainability.
- Updated the sidebar to handle project overview and feature generation options, streamlining the project setup process.
- Removed the old dialog implementation from the Sidebar, reducing code duplication and improving clarity.
This commit is contained in:
Cody Seibert
2025-12-15 01:07:47 -05:00
parent 919e08689a
commit f25d62fe25
13 changed files with 724 additions and 332 deletions

View File

@@ -48,7 +48,9 @@ export function resolveModelString(
// Look up Claude model alias
const resolved = CLAUDE_MODEL_MAP[modelKey];
if (resolved) {
console.log(`[ModelResolver] Resolved model alias: "${modelKey}" -> "${resolved}"`);
console.log(
`[ModelResolver] Resolved model alias: "${modelKey}" -> "${resolved}"`
);
return resolved;
}
@@ -73,8 +75,5 @@ export function getEffectiveModel(
sessionModel?: string,
defaultModel?: string
): string {
return resolveModelString(
explicitModel || sessionModel,
defaultModel
);
return resolveModelString(explicitModel || sessionModel, defaultModel);
}

View File

@@ -0,0 +1,291 @@
/**
* SDK Options Factory - Centralized configuration for Claude Agent SDK
*
* Provides presets for common use cases:
* - Spec generation: Long-running analysis with read-only tools
* - Feature generation: Quick JSON generation from specs
* - Feature building: Autonomous feature implementation with full tool access
* - Suggestions: Analysis with read-only tools
* - Chat: Full tool access for interactive coding
*
* Uses model-resolver for consistent model handling across the application.
*/
import type { Options } from "@anthropic-ai/claude-agent-sdk";
import {
resolveModelString,
DEFAULT_MODELS,
CLAUDE_MODEL_MAP,
} from "./model-resolver.js";
/**
* Tool presets for different use cases
*/
export const TOOL_PRESETS = {
/** Read-only tools for analysis */
readOnly: ["Read", "Glob", "Grep"] as const,
/** Tools for spec generation that needs to read the codebase */
specGeneration: ["Read", "Glob", "Grep"] as const,
/** Full tool access for feature implementation */
fullAccess: [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash",
"WebSearch",
"WebFetch",
] as const,
/** Tools for chat/interactive mode */
chat: [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash",
"WebSearch",
"WebFetch",
] as const,
} as const;
/**
* Max turns presets for different use cases
*/
export const MAX_TURNS = {
/** Quick operations that shouldn't need many iterations */
quick: 5,
/** Standard operations */
standard: 20,
/** Long-running operations like full spec generation */
extended: 50,
/** Very long operations that may require extensive exploration */
maximum: 1000,
} as const;
/**
* Model presets for different use cases
*
* These can be overridden via environment variables:
* - AUTOMAKER_MODEL_SPEC: Model for spec generation
* - AUTOMAKER_MODEL_FEATURES: Model for feature generation
* - AUTOMAKER_MODEL_SUGGESTIONS: Model for suggestions
* - AUTOMAKER_MODEL_CHAT: Model for chat
* - AUTOMAKER_MODEL_DEFAULT: Fallback model for all operations
*/
export function getModelForUseCase(
useCase: "spec" | "features" | "suggestions" | "chat" | "auto" | "default",
explicitModel?: string
): string {
// Explicit model takes precedence
if (explicitModel) {
return resolveModelString(explicitModel);
}
// Check environment variable override for this use case
const envVarMap: Record<string, string | undefined> = {
spec: process.env.AUTOMAKER_MODEL_SPEC,
features: process.env.AUTOMAKER_MODEL_FEATURES,
suggestions: process.env.AUTOMAKER_MODEL_SUGGESTIONS,
chat: process.env.AUTOMAKER_MODEL_CHAT,
auto: process.env.AUTOMAKER_MODEL_AUTO,
default: process.env.AUTOMAKER_MODEL_DEFAULT,
};
const envModel = envVarMap[useCase] || envVarMap.default;
if (envModel) {
return resolveModelString(envModel);
}
const defaultModels: Record<string, string> = {
spec: CLAUDE_MODEL_MAP["haiku"], // used to generate app specs
features: CLAUDE_MODEL_MAP["haiku"], // used to generate features from app specs
suggestions: CLAUDE_MODEL_MAP["haiku"], // used for suggestions
chat: CLAUDE_MODEL_MAP["haiku"], // used for chat
auto: CLAUDE_MODEL_MAP["opus"], // used to implement kanban cards
default: CLAUDE_MODEL_MAP["opus"],
};
return resolveModelString(defaultModels[useCase] || DEFAULT_MODELS.claude);
}
/**
* Base options that apply to all SDK calls
*/
function getBaseOptions(): Partial<Options> {
return {
permissionMode: "acceptEdits",
};
}
/**
* Options configuration for creating SDK options
*/
export interface CreateSdkOptionsConfig {
/** Working directory for the agent */
cwd: string;
/** Optional explicit model override */
model?: string;
/** Optional session model (used as fallback if explicit model not provided) */
sessionModel?: string;
/** Optional system prompt */
systemPrompt?: string;
/** Optional abort controller for cancellation */
abortController?: AbortController;
}
/**
* Create SDK options for spec generation
*
* Configuration:
* - Uses read-only tools for codebase analysis
* - Extended turns for thorough exploration
* - Opus model by default (can be overridden)
*/
export function createSpecGenerationOptions(
config: CreateSdkOptionsConfig
): Options {
return {
...getBaseOptions(),
model: getModelForUseCase("spec", config.model),
maxTurns: MAX_TURNS.maximum,
cwd: config.cwd,
allowedTools: [...TOOL_PRESETS.specGeneration],
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}
/**
* Create SDK options for feature generation from specs
*
* Configuration:
* - Uses read-only tools (just needs to read the spec)
* - Quick turns since it's mostly JSON generation
* - Sonnet model by default for speed
*/
export function createFeatureGenerationOptions(
config: CreateSdkOptionsConfig
): Options {
return {
...getBaseOptions(),
model: getModelForUseCase("features", config.model),
maxTurns: MAX_TURNS.quick,
cwd: config.cwd,
allowedTools: [...TOOL_PRESETS.readOnly],
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}
/**
* Create SDK options for generating suggestions
*
* Configuration:
* - Uses read-only tools for analysis
* - Quick turns for focused suggestions
* - Opus model by default for thorough analysis
*/
export function createSuggestionsOptions(
config: CreateSdkOptionsConfig
): Options {
return {
...getBaseOptions(),
model: getModelForUseCase("suggestions", config.model),
maxTurns: MAX_TURNS.quick,
cwd: config.cwd,
allowedTools: [...TOOL_PRESETS.readOnly],
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}
/**
* Create SDK options for chat/interactive mode
*
* Configuration:
* - Full tool access for code modification
* - Standard turns for interactive sessions
* - Model priority: explicit model > session model > chat default
* - Sandbox enabled for bash safety
*/
export function createChatOptions(config: CreateSdkOptionsConfig): Options {
// Model priority: explicit model > session model > chat default
const effectiveModel = config.model || config.sessionModel;
return {
...getBaseOptions(),
model: getModelForUseCase("chat", effectiveModel),
maxTurns: MAX_TURNS.standard,
cwd: config.cwd,
allowedTools: [...TOOL_PRESETS.chat],
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}
/**
* Create SDK options for autonomous feature building/implementation
*
* Configuration:
* - Full tool access for code modification and implementation
* - Extended turns for thorough feature implementation
* - Uses default model (can be overridden)
* - Sandbox enabled for bash safety
*/
export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options {
return {
...getBaseOptions(),
model: getModelForUseCase("auto", config.model),
maxTurns: MAX_TURNS.maximum,
cwd: config.cwd,
allowedTools: [...TOOL_PRESETS.fullAccess],
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}
/**
* Create custom SDK options with explicit configuration
*
* Use this when the preset options don't fit your use case.
*/
export function createCustomOptions(
config: CreateSdkOptionsConfig & {
maxTurns?: number;
allowedTools?: readonly string[];
sandbox?: { enabled: boolean; autoAllowBashIfSandboxed?: boolean };
}
): Options {
return {
...getBaseOptions(),
model: getModelForUseCase("default", config.model),
maxTurns: config.maxTurns ?? MAX_TURNS.maximum,
cwd: config.cwd,
allowedTools: config.allowedTools
? [...config.allowedTools]
: [...TOOL_PRESETS.readOnly],
...(config.sandbox && { sandbox: config.sandbox }),
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
...(config.abortController && { abortController: config.abortController }),
};
}

View File

@@ -2,16 +2,19 @@
* Generate features from existing app_spec.txt
*/
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { query } from "@anthropic-ai/claude-agent-sdk";
import path from "path";
import fs from "fs/promises";
import type { EventEmitter } from "../../lib/events.js";
import { createLogger } from "../../lib/logger.js";
import { createFeatureGenerationOptions } from "../../lib/sdk-options.js";
import { logAuthStatus } from "./common.js";
import { parseAndCreateFeatures } from "./parse-and-create-features.js";
const logger = createLogger("SpecRegeneration");
const MAX_FEATURES = 100;
export async function generateFeaturesFromSpec(
projectPath: string,
events: EventEmitter,
@@ -29,6 +32,10 @@ export async function generateFeaturesFromSpec(
try {
spec = await fs.readFile(specPath, "utf-8");
logger.info(`Spec loaded successfully (${spec.length} chars)`);
logger.info(`Spec preview (first 500 chars): ${spec.substring(0, 500)}`);
logger.info(
`Spec preview (last 500 chars): ${spec.substring(spec.length - 500)}`
);
} catch (readError) {
logger.error("❌ Failed to read spec file:", readError);
events.emit("spec-regeneration:event", {
@@ -66,9 +73,16 @@ Format as JSON:
]
}
Generate 5-15 features that build on each other logically.`;
Generate ${MAX_FEATURES} features that build on each other logically.
logger.debug("Prompt length:", `${prompt.length} chars`);
IMPORTANT: Do not ask for clarification. The specification is provided above. Generate the JSON immediately.`;
logger.info("========== PROMPT BEING SENT ==========");
logger.info(`Prompt length: ${prompt.length} chars`);
logger.info(
`Prompt preview (first 1000 chars):\n${prompt.substring(0, 1000)}`
);
logger.info("========== END PROMPT PREVIEW ==========");
events.emit("spec-regeneration:event", {
type: "spec_regeneration_progress",
@@ -76,14 +90,10 @@ Generate 5-15 features that build on each other logically.`;
projectPath: projectPath,
});
const options: Options = {
model: "claude-sonnet-4-20250514",
maxTurns: 5,
const options = createFeatureGenerationOptions({
cwd: projectPath,
allowedTools: ["Read", "Glob"],
permissionMode: "acceptEdits",
abortController,
};
});
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
logger.info("Calling Claude Agent SDK query() for features...");
@@ -120,7 +130,7 @@ Generate 5-15 features that build on each other logically.`;
if (msg.type === "assistant" && msg.message.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
responseText = block.text;
responseText += block.text;
logger.debug(
`Feature text block received (${block.text.length} chars)`
);
@@ -147,6 +157,9 @@ Generate 5-15 features that build on each other logically.`;
logger.info(`Feature stream complete. Total messages: ${messageCount}`);
logger.info(`Feature response length: ${responseText.length} chars`);
logger.info("========== FULL RESPONSE TEXT ==========");
logger.info(responseText);
logger.info("========== END RESPONSE TEXT ==========");
await parseAndCreateFeatures(projectPath, responseText, events);

View File

@@ -2,12 +2,13 @@
* Generate app_spec.txt from project overview
*/
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { query } from "@anthropic-ai/claude-agent-sdk";
import path from "path";
import fs from "fs/promises";
import type { EventEmitter } from "../../lib/events.js";
import { getAppSpecFormatInstruction } from "../../lib/app-spec-format.js";
import { createLogger } from "../../lib/logger.js";
import { createSpecGenerationOptions } from "../../lib/sdk-options.js";
import { logAuthStatus } from "./common.js";
import { generateFeaturesFromSpec } from "./generate-features-from-spec.js";
@@ -21,11 +22,12 @@ export async function generateSpec(
generateFeatures?: boolean,
analyzeProject?: boolean
): Promise<void> {
logger.debug("========== generateSpec() started ==========");
logger.debug("projectPath:", projectPath);
logger.debug("projectOverview length:", `${projectOverview.length} chars`);
logger.debug("generateFeatures:", generateFeatures);
logger.debug("analyzeProject:", analyzeProject);
logger.info("========== generateSpec() started ==========");
logger.info("projectPath:", projectPath);
logger.info("projectOverview length:", `${projectOverview.length} chars`);
logger.info("projectOverview preview:", projectOverview.substring(0, 300));
logger.info("generateFeatures:", generateFeatures);
logger.info("analyzeProject:", analyzeProject);
// Build the prompt based on whether we should analyze the project
let analysisInstructions = "";
@@ -63,21 +65,20 @@ ${analysisInstructions}
${getAppSpecFormatInstruction()}`;
logger.debug("Prompt length:", `${prompt.length} chars`);
logger.info("========== PROMPT BEING SENT ==========");
logger.info(`Prompt length: ${prompt.length} chars`);
logger.info(`Prompt preview (first 500 chars):\n${prompt.substring(0, 500)}`);
logger.info("========== END PROMPT PREVIEW ==========");
events.emit("spec-regeneration:event", {
type: "spec_progress",
content: "Starting spec generation...\n",
});
const options: Options = {
model: "claude-opus-4-5-20251101",
maxTurns: 10,
const options = createSpecGenerationOptions({
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "acceptEdits",
abortController,
};
});
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
logger.info("Calling Claude Agent SDK query()...");
@@ -98,45 +99,96 @@ ${getAppSpecFormatInstruction()}`;
let responseText = "";
let messageCount = 0;
logger.debug("Starting to iterate over stream...");
logger.info("Starting to iterate over stream...");
try {
for await (const msg of stream) {
messageCount++;
logger.debug(
`Stream message #${messageCount}:`,
JSON.stringify(
{ type: msg.type, subtype: (msg as any).subtype },
null,
2
)
logger.info(
`Stream message #${messageCount}: type=${msg.type}, subtype=${
(msg as any).subtype
}`
);
if (msg.type === "assistant" && msg.message.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
responseText += block.text;
logger.debug(`Text block received (${block.text.length} chars)`);
events.emit("spec-regeneration:event", {
type: "spec_regeneration_progress",
content: block.text,
projectPath: projectPath,
});
} else if (block.type === "tool_use") {
logger.debug("Tool use:", block.name);
events.emit("spec-regeneration:event", {
type: "spec_tool",
tool: block.name,
input: block.input,
});
if (msg.type === "assistant") {
// Log the full message structure to debug
logger.info(`Assistant msg keys: ${Object.keys(msg).join(", ")}`);
const msgAny = msg as any;
if (msgAny.message) {
logger.info(
`msg.message keys: ${Object.keys(msgAny.message).join(", ")}`
);
if (msgAny.message.content) {
logger.info(
`msg.message.content length: ${msgAny.message.content.length}`
);
for (const block of msgAny.message.content) {
logger.info(
`Block keys: ${Object.keys(block).join(", ")}, type: ${
block.type
}`
);
if (block.type === "text") {
responseText += block.text;
logger.info(
`Text block received (${block.text.length} chars), total now: ${responseText.length} chars`
);
logger.info(`Text preview: ${block.text.substring(0, 200)}...`);
events.emit("spec-regeneration:event", {
type: "spec_regeneration_progress",
content: block.text,
projectPath: projectPath,
});
} else if (block.type === "tool_use") {
logger.info("Tool use:", block.name);
events.emit("spec-regeneration:event", {
type: "spec_tool",
tool: block.name,
input: block.input,
});
}
}
} else {
logger.warn("msg.message.content is falsy");
}
} else {
logger.warn("msg.message is falsy");
// Log full message to see structure
logger.info(
`Full assistant msg: ${JSON.stringify(msg).substring(0, 1000)}`
);
}
} else if (msg.type === "result" && (msg as any).subtype === "success") {
logger.debug("Received success result");
responseText = (msg as any).result || responseText;
logger.info("Received success result");
logger.info(`Result value: "${(msg as any).result}"`);
logger.info(
`Current responseText length before result: ${responseText.length}`
);
// Only use result if it has content, otherwise keep accumulated text
if ((msg as any).result && (msg as any).result.length > 0) {
logger.info("Using result value as responseText");
responseText = (msg as any).result;
} else {
logger.info("Result is empty, keeping accumulated responseText");
}
} else if (msg.type === "result") {
// Handle all result types
const subtype = (msg as any).subtype;
logger.info(`Result message: subtype=${subtype}`);
if (subtype === "error_max_turns") {
logger.error(
"❌ Hit max turns limit! Claude used too many tool calls."
);
logger.info(`responseText so far: ${responseText.length} chars`);
}
} else if ((msg as { type: string }).type === "error") {
logger.error("❌ Received error message from stream:");
logger.error("Error message:", JSON.stringify(msg, null, 2));
} else if (msg.type === "user") {
// Log user messages (tool results)
logger.info(
`User message (tool result): ${JSON.stringify(msg).substring(0, 500)}`
);
}
}
} catch (streamError) {
@@ -147,16 +199,31 @@ ${getAppSpecFormatInstruction()}`;
logger.info(`Stream iteration complete. Total messages: ${messageCount}`);
logger.info(`Response text length: ${responseText.length} chars`);
logger.info("========== FINAL RESPONSE TEXT ==========");
logger.info(responseText || "(empty)");
logger.info("========== END RESPONSE TEXT ==========");
if (!responseText || responseText.trim().length === 0) {
logger.error("❌ WARNING: responseText is empty! Nothing to save.");
}
// Save spec
const specDir = path.join(projectPath, ".automaker");
const specPath = path.join(specDir, "app_spec.txt");
logger.info("Saving spec to:", specPath);
logger.info(`Content to save (${responseText.length} chars)`);
await fs.mkdir(specDir, { recursive: true });
await fs.writeFile(specPath, responseText);
// Verify the file was written
const savedContent = await fs.readFile(specPath, "utf-8");
logger.info(`Verified saved file: ${savedContent.length} chars`);
if (savedContent.length === 0) {
logger.error("❌ File was saved but is empty!");
}
logger.info("Spec saved successfully");
// Emit spec completion event

View File

@@ -14,23 +14,32 @@ export async function parseAndCreateFeatures(
content: string,
events: EventEmitter
): Promise<void> {
logger.debug("========== parseAndCreateFeatures() started ==========");
logger.debug("Content length:", `${content.length} chars`);
logger.info("========== parseAndCreateFeatures() started ==========");
logger.info(`Content length: ${content.length} chars`);
logger.info("========== CONTENT RECEIVED FOR PARSING ==========");
logger.info(content);
logger.info("========== END CONTENT ==========");
try {
// Extract JSON from response
logger.debug("Extracting JSON from response...");
logger.info("Extracting JSON from response...");
logger.info(`Looking for pattern: /{[\\s\\S]*"features"[\\s\\S]*}/`);
const jsonMatch = content.match(/\{[\s\S]*"features"[\s\S]*\}/);
if (!jsonMatch) {
logger.error("❌ No valid JSON found in response");
logger.error("Content preview:", content.substring(0, 500));
logger.error("Full content received:");
logger.error(content);
throw new Error("No valid JSON found in response");
}
logger.debug(`JSON match found (${jsonMatch[0].length} chars)`);
logger.info(`JSON match found (${jsonMatch[0].length} chars)`);
logger.info("========== MATCHED JSON ==========");
logger.info(jsonMatch[0]);
logger.info("========== END MATCHED JSON ==========");
const parsed = JSON.parse(jsonMatch[0]);
logger.info(`Parsed ${parsed.features?.length || 0} features`);
logger.info("Parsed features:", JSON.stringify(parsed.features, null, 2));
const featuresDir = path.join(projectPath, ".automaker", "features");
await fs.mkdir(featuresDir, { recursive: true });

View File

@@ -2,9 +2,10 @@
* Business logic for generating suggestions
*/
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { EventEmitter } from "../../lib/events.js";
import { createLogger } from "../../lib/logger.js";
import { createSuggestionsOptions } from "../../lib/sdk-options.js";
const logger = createLogger("Suggestions");
@@ -54,14 +55,10 @@ Format your response as JSON:
content: `Starting ${suggestionType} analysis...\n`,
});
const options: Options = {
model: "claude-opus-4-5-20251101",
maxTurns: 5,
const options = createSuggestionsOptions({
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "acceptEdits",
abortController,
};
});
const stream = query({ prompt, options });
let responseText = "";

View File

@@ -11,7 +11,7 @@ import { ProviderFactory } from "../providers/provider-factory.js";
import type { ExecuteOptions } from "../providers/types.js";
import { readImageAsBase64 } from "../lib/image-handler.js";
import { buildPromptWithImages } from "../lib/prompt-builder.js";
import { getEffectiveModel } from "../lib/model-resolver.js";
import { createChatOptions } from "../lib/sdk-options.js";
import { isAbortError } from "../lib/error-handler.js";
interface Message {
@@ -176,8 +176,19 @@ export class AgentService {
await this.saveSession(sessionId, session.messages);
try {
// Use session model, parameter model, or default
const effectiveModel = getEffectiveModel(model, session.model);
// Build SDK options using centralized configuration
const sdkOptions = createChatOptions({
cwd: workingDirectory || session.workingDirectory,
model: model,
sessionModel: session.model,
systemPrompt: this.getSystemPrompt(),
abortController: session.abortController!,
});
// Extract model, maxTurns, and allowedTools from SDK options
const effectiveModel = sdkOptions.model!;
const maxTurns = sdkOptions.maxTurns;
const allowedTools = sdkOptions.allowedTools as string[] | undefined;
// Get provider for this model
const provider = ProviderFactory.getProviderForModel(effectiveModel);
@@ -192,17 +203,8 @@ export class AgentService {
model: effectiveModel,
cwd: workingDirectory || session.workingDirectory,
systemPrompt: this.getSystemPrompt(),
maxTurns: 20,
allowedTools: [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash",
"WebSearch",
"WebFetch",
],
maxTurns: maxTurns,
allowedTools: allowedTools,
abortController: session.abortController!,
conversationHistory:
conversationHistory.length > 0 ? conversationHistory : undefined,

View File

@@ -9,16 +9,16 @@
* - Verification and merge workflows
*/
import { AbortError } from "@anthropic-ai/claude-agent-sdk";
import { ProviderFactory } from "../providers/provider-factory.js";
import type { ExecuteOptions } from "../providers/types.js";
import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import fs from "fs/promises";
import type { EventEmitter, EventType } from "../lib/events.js";
import type { EventEmitter } from "../lib/events.js";
import { buildPromptWithImages } from "../lib/prompt-builder.js";
import { resolveModelString, DEFAULT_MODELS } from "../lib/model-resolver.js";
import { createAutoModeOptions } from "../lib/sdk-options.js";
import { isAbortError, classifyError } from "../lib/error-handler.js";
const execAsync = promisify(exec);
@@ -1085,7 +1085,18 @@ When done, summarize what you implemented and any notes for the developer.`;
imagePaths?: string[],
model?: string
): Promise<void> {
const finalModel = resolveModelString(model, DEFAULT_MODELS.claude);
// Build SDK options using centralized configuration for feature implementation
const sdkOptions = createAutoModeOptions({
cwd: workDir,
model: model,
abortController,
});
// Extract model, maxTurns, and allowedTools from SDK options
const finalModel = sdkOptions.model!;
const maxTurns = sdkOptions.maxTurns;
const allowedTools = sdkOptions.allowedTools as string[] | undefined;
console.log(
`[AutoMode] runAgent called for feature ${featureId} with model: ${finalModel}`
);
@@ -1108,9 +1119,9 @@ When done, summarize what you implemented and any notes for the developer.`;
const options: ExecuteOptions = {
prompt: promptContent,
model: finalModel,
maxTurns: 50,
maxTurns: maxTurns,
cwd: workDir,
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
allowedTools: allowedTools,
abortController,
};