mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-05 09:33:07 +00:00
fix: adress pr comments
This commit is contained in:
@@ -274,49 +274,60 @@ function detectSpecFallback(text: string): boolean {
|
|||||||
* 4. **Problem**: or **Problem Statement**: section (spec/full modes)
|
* 4. **Problem**: or **Problem Statement**: section (spec/full modes)
|
||||||
* 5. **Solution**: section as fallback
|
* 5. **Solution**: section as fallback
|
||||||
*
|
*
|
||||||
|
* Note: Uses last match for each pattern to avoid stale summaries
|
||||||
|
* when agent output accumulates across multiple runs.
|
||||||
|
*
|
||||||
* @param text - The text content to extract summary from
|
* @param text - The text content to extract summary from
|
||||||
* @returns The extracted summary string, or null if no summary found
|
* @returns The extracted summary string, or null if no summary found
|
||||||
*/
|
*/
|
||||||
function extractSummary(text: string): string | null {
|
function extractSummary(text: string): string | null {
|
||||||
// Check for explicit <summary> tags first
|
// Helper to truncate content to first paragraph with max length
|
||||||
const summaryMatch = text.match(/<summary>([\s\S]*?)<\/summary>/);
|
const truncate = (content: string, maxLength: number): string => {
|
||||||
|
const firstPara = content.split(/\n\n/)[0];
|
||||||
|
return firstPara.length > maxLength ? `${firstPara.substring(0, maxLength)}...` : firstPara;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to get last match from matchAll results
|
||||||
|
const getLastMatch = (matches: IterableIterator<RegExpMatchArray>): RegExpMatchArray | null => {
|
||||||
|
const arr = [...matches];
|
||||||
|
return arr.length > 0 ? arr[arr.length - 1] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for explicit <summary> tags first (use last match to avoid stale summaries)
|
||||||
|
const summaryMatches = text.matchAll(/<summary>([\s\S]*?)<\/summary>/g);
|
||||||
|
const summaryMatch = getLastMatch(summaryMatches);
|
||||||
if (summaryMatch) {
|
if (summaryMatch) {
|
||||||
return summaryMatch[1].trim();
|
return summaryMatch[1].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for ## Summary section
|
// Check for ## Summary section (use last match)
|
||||||
const sectionMatch = text.match(/##\s*Summary\s*\n+([\s\S]*?)(?=\n##|\n\*\*|$)/i);
|
const sectionMatches = text.matchAll(/##\s*Summary\s*\n+([\s\S]*?)(?=\n##|\n\*\*|$)/gi);
|
||||||
|
const sectionMatch = getLastMatch(sectionMatches);
|
||||||
if (sectionMatch) {
|
if (sectionMatch) {
|
||||||
const content = sectionMatch[1].trim();
|
return truncate(sectionMatch[1].trim(), 500);
|
||||||
// Take first paragraph or up to 500 chars
|
|
||||||
const firstPara = content.split(/\n\n/)[0];
|
|
||||||
return firstPara.length > 500 ? firstPara.substring(0, 500) + '...' : firstPara;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for **Goal**: section (lite mode)
|
// Check for **Goal**: section (lite mode, use last match)
|
||||||
const goalMatch = text.match(/\*\*Goal\*\*:\s*(.+?)(?:\n|$)/i);
|
const goalMatches = text.matchAll(/\*\*Goal\*\*:\s*(.+?)(?:\n|$)/gi);
|
||||||
|
const goalMatch = getLastMatch(goalMatches);
|
||||||
if (goalMatch) {
|
if (goalMatch) {
|
||||||
return goalMatch[1].trim();
|
return goalMatch[1].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for **Problem**: or **Problem Statement**: section (spec/full modes)
|
// Check for **Problem**: or **Problem Statement**: section (spec/full modes, use last match)
|
||||||
const problemMatch = text.match(
|
const problemMatches = text.matchAll(
|
||||||
/\*\*Problem(?:\s*Statement)?\*\*:\s*([\s\S]*?)(?=\n\d+\.|\n\*\*|$)/i
|
/\*\*Problem(?:\s*Statement)?\*\*:\s*([\s\S]*?)(?=\n\d+\.|\n\*\*|$)/gi
|
||||||
);
|
);
|
||||||
|
const problemMatch = getLastMatch(problemMatches);
|
||||||
if (problemMatch) {
|
if (problemMatch) {
|
||||||
const content = problemMatch[1].trim();
|
return truncate(problemMatch[1].trim(), 500);
|
||||||
// Take first paragraph or up to 500 chars
|
|
||||||
const firstPara = content.split(/\n\n/)[0];
|
|
||||||
return firstPara.length > 500 ? firstPara.substring(0, 500) + '...' : firstPara;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for **Solution**: section as fallback
|
// Check for **Solution**: section as fallback (use last match)
|
||||||
const solutionMatch = text.match(/\*\*Solution\*\*:\s*([\s\S]*?)(?=\n\d+\.|\n\*\*|$)/i);
|
const solutionMatches = text.matchAll(/\*\*Solution\*\*:\s*([\s\S]*?)(?=\n\d+\.|\n\*\*|$)/gi);
|
||||||
|
const solutionMatch = getLastMatch(solutionMatches);
|
||||||
if (solutionMatch) {
|
if (solutionMatch) {
|
||||||
const content = solutionMatch[1].trim();
|
return truncate(solutionMatch[1].trim(), 300);
|
||||||
// Take first paragraph or up to 300 chars
|
|
||||||
const firstPara = content.split(/\n\n/)[0];
|
|
||||||
return firstPara.length > 300 ? firstPara.substring(0, 300) + '...' : firstPara;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -4008,6 +4019,168 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
|
|||||||
);
|
);
|
||||||
}, STREAM_HEARTBEAT_MS);
|
}, STREAM_HEARTBEAT_MS);
|
||||||
|
|
||||||
|
// RECOVERY PATH: If we have an approved plan with persisted tasks, skip spec generation
|
||||||
|
// and directly execute the remaining tasks
|
||||||
|
if (existingApprovedPlan && persistedTasks && persistedTasks.length > 0) {
|
||||||
|
logger.info(
|
||||||
|
`Recovery: Resuming task execution for feature ${featureId} with ${persistedTasks.length} tasks`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get customized prompts for task execution
|
||||||
|
const taskPrompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
|
||||||
|
const approvedPlanContent = existingApprovedPlan.content || '';
|
||||||
|
|
||||||
|
// Execute each task with a separate agent
|
||||||
|
for (let taskIndex = 0; taskIndex < persistedTasks.length; taskIndex++) {
|
||||||
|
const task = persistedTasks[taskIndex];
|
||||||
|
|
||||||
|
// Skip tasks that are already completed
|
||||||
|
if (task.status === 'completed') {
|
||||||
|
logger.info(`Skipping already completed task ${task.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for abort
|
||||||
|
if (abortController.signal.aborted) {
|
||||||
|
throw new Error('Feature execution aborted');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark task as in_progress immediately (even without TASK_START marker)
|
||||||
|
await this.updateTaskStatus(projectPath, featureId, task.id, 'in_progress');
|
||||||
|
|
||||||
|
// Emit task started
|
||||||
|
logger.info(`Starting task ${task.id}: ${task.description}`);
|
||||||
|
this.emitAutoModeEvent('auto_mode_task_started', {
|
||||||
|
featureId,
|
||||||
|
projectPath,
|
||||||
|
branchName,
|
||||||
|
taskId: task.id,
|
||||||
|
taskDescription: task.description,
|
||||||
|
taskIndex,
|
||||||
|
tasksTotal: persistedTasks.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update planSpec with current task
|
||||||
|
await this.updateFeaturePlanSpec(projectPath, featureId, {
|
||||||
|
currentTaskId: task.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build focused prompt for this specific task
|
||||||
|
const taskPrompt = this.buildTaskPrompt(
|
||||||
|
task,
|
||||||
|
persistedTasks,
|
||||||
|
taskIndex,
|
||||||
|
approvedPlanContent,
|
||||||
|
taskPrompts.taskExecution.taskPromptTemplate,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute task with dedicated agent
|
||||||
|
const taskStream = provider.executeQuery({
|
||||||
|
prompt: taskPrompt,
|
||||||
|
model: bareModel,
|
||||||
|
maxTurns: Math.min(maxTurns || 100, 50),
|
||||||
|
cwd: workDir,
|
||||||
|
allowedTools: allowedTools,
|
||||||
|
abortController,
|
||||||
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
|
credentials,
|
||||||
|
claudeCompatibleProvider,
|
||||||
|
});
|
||||||
|
|
||||||
|
let taskOutput = '';
|
||||||
|
let taskCompleteDetected = false;
|
||||||
|
|
||||||
|
// Process task stream
|
||||||
|
for await (const msg of taskStream) {
|
||||||
|
if (msg.type === 'assistant' && msg.message?.content) {
|
||||||
|
for (const block of msg.message.content) {
|
||||||
|
if (block.type === 'text') {
|
||||||
|
const text = block.text || '';
|
||||||
|
taskOutput += text;
|
||||||
|
responseText += text;
|
||||||
|
this.emitAutoModeEvent('auto_mode_progress', {
|
||||||
|
featureId,
|
||||||
|
branchName,
|
||||||
|
content: text,
|
||||||
|
});
|
||||||
|
scheduleWrite();
|
||||||
|
|
||||||
|
// Detect [TASK_COMPLETE] marker
|
||||||
|
if (!taskCompleteDetected) {
|
||||||
|
const completeTaskId = detectTaskCompleteMarker(taskOutput);
|
||||||
|
if (completeTaskId) {
|
||||||
|
taskCompleteDetected = true;
|
||||||
|
logger.info(`[TASK_COMPLETE] detected for ${completeTaskId}`);
|
||||||
|
await this.updateTaskStatus(
|
||||||
|
projectPath,
|
||||||
|
featureId,
|
||||||
|
completeTaskId,
|
||||||
|
'completed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (block.type === 'tool_use') {
|
||||||
|
this.emitAutoModeEvent('auto_mode_tool', {
|
||||||
|
featureId,
|
||||||
|
branchName,
|
||||||
|
tool: block.name,
|
||||||
|
input: block.input,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (msg.type === 'error') {
|
||||||
|
throw new Error(msg.error || `Error during task ${task.id}`);
|
||||||
|
} else if (msg.type === 'result' && msg.subtype === 'success') {
|
||||||
|
taskOutput += msg.result || '';
|
||||||
|
responseText += msg.result || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no [TASK_COMPLETE] marker was detected, still mark as completed
|
||||||
|
if (!taskCompleteDetected) {
|
||||||
|
await this.updateTaskStatus(projectPath, featureId, task.id, 'completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit task completed
|
||||||
|
logger.info(`Task ${task.id} completed for feature ${featureId}`);
|
||||||
|
this.emitAutoModeEvent('auto_mode_task_complete', {
|
||||||
|
featureId,
|
||||||
|
projectPath,
|
||||||
|
branchName,
|
||||||
|
taskId: task.id,
|
||||||
|
tasksCompleted: taskIndex + 1,
|
||||||
|
tasksTotal: persistedTasks.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update planSpec with progress
|
||||||
|
await this.updateFeaturePlanSpec(projectPath, featureId, {
|
||||||
|
tasksCompleted: taskIndex + 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Recovery: All tasks completed for feature ${featureId}`);
|
||||||
|
|
||||||
|
// Extract and save final summary
|
||||||
|
const summary = extractSummary(responseText);
|
||||||
|
if (summary) {
|
||||||
|
await this.saveFeatureSummary(projectPath, featureId, summary);
|
||||||
|
this.emitAutoModeEvent('auto_mode_summary', {
|
||||||
|
featureId,
|
||||||
|
projectPath,
|
||||||
|
summary,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final write and cleanup
|
||||||
|
clearInterval(streamHeartbeat);
|
||||||
|
if (writeTimeout) {
|
||||||
|
clearTimeout(writeTimeout);
|
||||||
|
}
|
||||||
|
await writeToFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap stream processing in try/finally to ensure timeout cleanup on any error/abort
|
// Wrap stream processing in try/finally to ensure timeout cleanup on any error/abort
|
||||||
try {
|
try {
|
||||||
streamLoop: for await (const msg of stream) {
|
streamLoop: for await (const msg of stream) {
|
||||||
@@ -4359,6 +4532,9 @@ After generating the revised spec, output:
|
|||||||
throw new Error('Feature execution aborted');
|
throw new Error('Feature execution aborted');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark task as in_progress immediately (even without TASK_START marker)
|
||||||
|
await this.updateTaskStatus(projectPath, featureId, task.id, 'in_progress');
|
||||||
|
|
||||||
// Emit task started
|
// Emit task started
|
||||||
logger.info(`Starting task ${task.id}: ${task.description}`);
|
logger.info(`Starting task ${task.id}: ${task.description}`);
|
||||||
this.emitAutoModeEvent('auto_mode_task_started', {
|
this.emitAutoModeEvent('auto_mode_task_started', {
|
||||||
|
|||||||
@@ -179,9 +179,6 @@ export function AddFeatureDialog({
|
|||||||
// Model selection state
|
// Model selection state
|
||||||
const [modelEntry, setModelEntry] = useState<PhaseModelEntry>({ model: 'claude-opus' });
|
const [modelEntry, setModelEntry] = useState<PhaseModelEntry>({ model: 'claude-opus' });
|
||||||
|
|
||||||
// All models support planning mode via marker-based instructions in prompts
|
|
||||||
const modelSupportsPlanningMode = true;
|
|
||||||
|
|
||||||
// Planning mode state
|
// Planning mode state
|
||||||
const [planningMode, setPlanningMode] = useState<PlanningMode>('skip');
|
const [planningMode, setPlanningMode] = useState<PlanningMode>('skip');
|
||||||
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
|
const [requirePlanApproval, setRequirePlanApproval] = useState(false);
|
||||||
@@ -562,41 +559,13 @@ export function AddFeatureDialog({
|
|||||||
|
|
||||||
<div className="grid gap-3 grid-cols-2">
|
<div className="grid gap-3 grid-cols-2">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label
|
<Label className="text-xs text-muted-foreground">Planning</Label>
|
||||||
className={cn(
|
<PlanningModeSelect
|
||||||
'text-xs text-muted-foreground',
|
mode={planningMode}
|
||||||
!modelSupportsPlanningMode && 'opacity-50'
|
onModeChange={setPlanningMode}
|
||||||
)}
|
testIdPrefix="add-feature-planning"
|
||||||
>
|
compact
|
||||||
Planning
|
/>
|
||||||
</Label>
|
|
||||||
{modelSupportsPlanningMode ? (
|
|
||||||
<PlanningModeSelect
|
|
||||||
mode={planningMode}
|
|
||||||
onModeChange={setPlanningMode}
|
|
||||||
testIdPrefix="add-feature-planning"
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div>
|
|
||||||
<PlanningModeSelect
|
|
||||||
mode="skip"
|
|
||||||
onModeChange={() => {}}
|
|
||||||
testIdPrefix="add-feature-planning"
|
|
||||||
compact
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Planning modes are only available for Claude Provider</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Options</Label>
|
<Label className="text-xs text-muted-foreground">Options</Label>
|
||||||
@@ -620,20 +589,14 @@ export function AddFeatureDialog({
|
|||||||
id="add-feature-require-approval"
|
id="add-feature-require-approval"
|
||||||
checked={requirePlanApproval}
|
checked={requirePlanApproval}
|
||||||
onCheckedChange={(checked) => setRequirePlanApproval(!!checked)}
|
onCheckedChange={(checked) => setRequirePlanApproval(!!checked)}
|
||||||
disabled={
|
disabled={planningMode === 'skip' || planningMode === 'lite'}
|
||||||
!modelSupportsPlanningMode ||
|
|
||||||
planningMode === 'skip' ||
|
|
||||||
planningMode === 'lite'
|
|
||||||
}
|
|
||||||
data-testid="add-feature-require-approval-checkbox"
|
data-testid="add-feature-require-approval-checkbox"
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="add-feature-require-approval"
|
htmlFor="add-feature-require-approval"
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs font-normal',
|
'text-xs font-normal',
|
||||||
!modelSupportsPlanningMode ||
|
planningMode === 'skip' || planningMode === 'lite'
|
||||||
planningMode === 'skip' ||
|
|
||||||
planningMode === 'lite'
|
|
||||||
? 'cursor-not-allowed text-muted-foreground'
|
? 'cursor-not-allowed text-muted-foreground'
|
||||||
: 'cursor-pointer'
|
: 'cursor-pointer'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -119,9 +119,6 @@ export function EditFeatureDialog({
|
|||||||
reasoningEffort: feature?.reasoningEffort || 'none',
|
reasoningEffort: feature?.reasoningEffort || 'none',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// All models support planning mode via marker-based instructions in prompts
|
|
||||||
const modelSupportsPlanningMode = true;
|
|
||||||
|
|
||||||
// Track the source of description changes for history
|
// Track the source of description changes for history
|
||||||
const [descriptionChangeSource, setDescriptionChangeSource] = useState<
|
const [descriptionChangeSource, setDescriptionChangeSource] = useState<
|
||||||
{ source: 'enhance'; mode: EnhancementMode } | 'edit' | null
|
{ source: 'enhance'; mode: EnhancementMode } | 'edit' | null
|
||||||
@@ -454,41 +451,13 @@ export function EditFeatureDialog({
|
|||||||
|
|
||||||
<div className="grid gap-3 grid-cols-2">
|
<div className="grid gap-3 grid-cols-2">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label
|
<Label className="text-xs text-muted-foreground">Planning</Label>
|
||||||
className={cn(
|
<PlanningModeSelect
|
||||||
'text-xs text-muted-foreground',
|
mode={planningMode}
|
||||||
!modelSupportsPlanningMode && 'opacity-50'
|
onModeChange={setPlanningMode}
|
||||||
)}
|
testIdPrefix="edit-feature-planning"
|
||||||
>
|
compact
|
||||||
Planning
|
/>
|
||||||
</Label>
|
|
||||||
{modelSupportsPlanningMode ? (
|
|
||||||
<PlanningModeSelect
|
|
||||||
mode={planningMode}
|
|
||||||
onModeChange={setPlanningMode}
|
|
||||||
testIdPrefix="edit-feature-planning"
|
|
||||||
compact
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div>
|
|
||||||
<PlanningModeSelect
|
|
||||||
mode="skip"
|
|
||||||
onModeChange={() => {}}
|
|
||||||
testIdPrefix="edit-feature-planning"
|
|
||||||
compact
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Planning modes are only available for Claude Provider</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<Label className="text-xs text-muted-foreground">Options</Label>
|
<Label className="text-xs text-muted-foreground">Options</Label>
|
||||||
@@ -514,20 +483,14 @@ export function EditFeatureDialog({
|
|||||||
id="edit-feature-require-approval"
|
id="edit-feature-require-approval"
|
||||||
checked={requirePlanApproval}
|
checked={requirePlanApproval}
|
||||||
onCheckedChange={(checked) => setRequirePlanApproval(!!checked)}
|
onCheckedChange={(checked) => setRequirePlanApproval(!!checked)}
|
||||||
disabled={
|
disabled={planningMode === 'skip' || planningMode === 'lite'}
|
||||||
!modelSupportsPlanningMode ||
|
|
||||||
planningMode === 'skip' ||
|
|
||||||
planningMode === 'lite'
|
|
||||||
}
|
|
||||||
data-testid="edit-feature-require-approval-checkbox"
|
data-testid="edit-feature-require-approval-checkbox"
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="edit-feature-require-approval"
|
htmlFor="edit-feature-require-approval"
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs font-normal',
|
'text-xs font-normal',
|
||||||
!modelSupportsPlanningMode ||
|
planningMode === 'skip' || planningMode === 'lite'
|
||||||
planningMode === 'skip' ||
|
|
||||||
planningMode === 'lite'
|
|
||||||
? 'cursor-not-allowed text-muted-foreground'
|
? 'cursor-not-allowed text-muted-foreground'
|
||||||
: 'cursor-pointer'
|
: 'cursor-pointer'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import type { WorkMode } from '../shared';
|
|||||||
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector';
|
||||||
import { isCursorModel, type PhaseModelEntry } from '@automaker/types';
|
import { isCursorModel, type PhaseModelEntry } from '@automaker/types';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
||||||
|
|
||||||
interface MassEditDialogProps {
|
interface MassEditDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -236,8 +235,6 @@ export function MassEditDialog({
|
|||||||
const hasAnyApply = Object.values(applyState).some(Boolean);
|
const hasAnyApply = Object.values(applyState).some(Boolean);
|
||||||
const isCurrentModelCursor = isCursorModel(model);
|
const isCurrentModelCursor = isCursorModel(model);
|
||||||
const modelAllowsThinking = !isCurrentModelCursor && modelSupportsThinking(model);
|
const modelAllowsThinking = !isCurrentModelCursor && modelSupportsThinking(model);
|
||||||
// All models support planning mode via marker-based instructions in prompts
|
|
||||||
const modelSupportsPlanningMode = true;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={(open) => !open && onClose()}>
|
<Dialog open={open} onOpenChange={(open) => !open && onClose()}>
|
||||||
@@ -277,64 +274,30 @@ export function MassEditDialog({
|
|||||||
<div className="border-t border-border" />
|
<div className="border-t border-border" />
|
||||||
|
|
||||||
{/* Planning Mode */}
|
{/* Planning Mode */}
|
||||||
{modelSupportsPlanningMode ? (
|
<FieldWrapper
|
||||||
<FieldWrapper
|
label="Planning Mode"
|
||||||
label="Planning Mode"
|
isMixed={mixedValues.planningMode || mixedValues.requirePlanApproval}
|
||||||
isMixed={mixedValues.planningMode || mixedValues.requirePlanApproval}
|
willApply={applyState.planningMode || applyState.requirePlanApproval}
|
||||||
willApply={applyState.planningMode || applyState.requirePlanApproval}
|
onApplyChange={(apply) =>
|
||||||
onApplyChange={(apply) =>
|
setApplyState((prev) => ({
|
||||||
setApplyState((prev) => ({
|
...prev,
|
||||||
...prev,
|
planningMode: apply,
|
||||||
planningMode: apply,
|
requirePlanApproval: apply,
|
||||||
requirePlanApproval: apply,
|
}))
|
||||||
}))
|
}
|
||||||
}
|
>
|
||||||
>
|
<PlanningModeSelect
|
||||||
<PlanningModeSelect
|
mode={planningMode}
|
||||||
mode={planningMode}
|
onModeChange={(newMode) => {
|
||||||
onModeChange={(newMode) => {
|
setPlanningMode(newMode);
|
||||||
setPlanningMode(newMode);
|
// Auto-suggest approval based on mode, but user can override
|
||||||
// Auto-suggest approval based on mode, but user can override
|
setRequirePlanApproval(newMode === 'spec' || newMode === 'full');
|
||||||
setRequirePlanApproval(newMode === 'spec' || newMode === 'full');
|
}}
|
||||||
}}
|
requireApproval={requirePlanApproval}
|
||||||
requireApproval={requirePlanApproval}
|
onRequireApprovalChange={setRequirePlanApproval}
|
||||||
onRequireApprovalChange={setRequirePlanApproval}
|
testIdPrefix="mass-edit-planning"
|
||||||
testIdPrefix="mass-edit-planning"
|
/>
|
||||||
/>
|
</FieldWrapper>
|
||||||
</FieldWrapper>
|
|
||||||
) : (
|
|
||||||
<TooltipProvider>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'p-3 rounded-lg border transition-colors border-border bg-muted/20 opacity-50 cursor-not-allowed'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox checked={false} disabled className="opacity-50" />
|
|
||||||
<Label className="text-sm font-medium text-muted-foreground">
|
|
||||||
Planning Mode
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="opacity-50 pointer-events-none">
|
|
||||||
<PlanningModeSelect
|
|
||||||
mode="skip"
|
|
||||||
onModeChange={() => {}}
|
|
||||||
testIdPrefix="mass-edit-planning"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>Planning modes are only available for Claude Provider</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Priority */}
|
{/* Priority */}
|
||||||
<FieldWrapper
|
<FieldWrapper
|
||||||
|
|||||||
11
apps/ui/src/types/electron.d.ts
vendored
11
apps/ui/src/types/electron.d.ts
vendored
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
import type { ClaudeUsageResponse, CodexUsageResponse } from '@/store/app-store';
|
||||||
|
import type { ParsedTask } from '@automaker/types';
|
||||||
|
|
||||||
export interface ImageAttachment {
|
export interface ImageAttachment {
|
||||||
id?: string; // Optional - may not be present in messages loaded from server
|
id?: string; // Optional - may not be present in messages loaded from server
|
||||||
@@ -339,14 +340,8 @@ export type AutoModeEvent =
|
|||||||
featureId: string;
|
featureId: string;
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
status: ParsedTask['status'];
|
||||||
tasks: Array<{
|
tasks: ParsedTask[];
|
||||||
id: string;
|
|
||||||
description: string;
|
|
||||||
filePath?: string;
|
|
||||||
phase?: string;
|
|
||||||
status: 'pending' | 'in_progress' | 'completed' | 'failed';
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'auto_mode_summary';
|
type: 'auto_mode_summary';
|
||||||
|
|||||||
Reference in New Issue
Block a user