This commit is contained in:
SuperComboGamer
2025-12-17 21:29:36 -05:00
parent 0d1138dfcf
commit 160bd8bfc7
8 changed files with 502 additions and 182 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { cn } from "@/lib/utils";
import { CheckCircle2, Circle, Loader2, ChevronDown, ChevronRight } from "lucide-react";
import { getElectronAPI } from "@/lib/electron";
@@ -16,15 +16,65 @@ interface TaskInfo {
interface TaskProgressPanelProps {
featureId: string;
projectPath?: string;
className?: string;
}
export function TaskProgressPanel({ featureId, className }: TaskProgressPanelProps) {
export function TaskProgressPanel({ featureId, projectPath, className }: TaskProgressPanelProps) {
const [tasks, setTasks] = useState<TaskInfo[]>([]);
const [isExpanded, setIsExpanded] = useState(true);
const [currentTaskId, setCurrentTaskId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Listen to task events
// Load initial tasks from feature's planSpec
const loadInitialTasks = useCallback(async () => {
if (!projectPath) {
setIsLoading(false);
return;
}
try {
const api = getElectronAPI();
if (!api?.features) {
setIsLoading(false);
return;
}
const result = await api.features.get(projectPath, featureId);
if (result.success && result.feature?.planSpec?.tasks) {
const planTasks = result.feature.planSpec.tasks;
const currentId = result.feature.planSpec.currentTaskId;
const completedCount = result.feature.planSpec.tasksCompleted || 0;
// Convert planSpec tasks to TaskInfo with proper status
const initialTasks: TaskInfo[] = planTasks.map((t: any, index: number) => ({
id: t.id,
description: t.description,
filePath: t.filePath,
phase: t.phase,
status: index < completedCount
? "completed" as const
: t.id === currentId
? "in_progress" as const
: "pending" as const,
}));
setTasks(initialTasks);
setCurrentTaskId(currentId || null);
}
} catch (error) {
console.error("Failed to load initial tasks:", error);
} finally {
setIsLoading(false);
}
}, [featureId, projectPath]);
// Load initial state on mount
useEffect(() => {
loadInitialTasks();
}, [loadInitialTasks]);
// Listen to task events for real-time updates
useEffect(() => {
const api = getElectronAPI();
if (!api?.autoMode) return;
@@ -69,24 +119,25 @@ export function TaskProgressPanel({ featureId, className }: TaskProgressPanelPro
t.id === taskEvent.taskId ? { ...t, status: "completed" as const } : t
)
);
// Clear current task if it was completed
if (currentTaskId === taskEvent.taskId) {
setCurrentTaskId(null);
}
setCurrentTaskId(null);
}
break;
}
});
return unsubscribe;
}, [featureId, currentTaskId]);
}, [featureId]);
// Calculate progress
const completedCount = tasks.filter((t) => t.status === "completed").length;
const totalCount = tasks.length;
const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
// Don't render if no tasks
// Don't render if loading or no tasks
if (isLoading) {
return null;
}
if (tasks.length === 0) {
return null;
}

View File

@@ -218,6 +218,13 @@ export function AgentOutputModal({
// Show when plan is auto-approved
newContent = `\n✅ Plan auto-approved - continuing to implementation...\n`;
break;
case "plan_revision_requested":
// Show when user requests plan revision
if ("planVersion" in event) {
const revisionEvent = event as Extract<AutoModeEvent, { type: "plan_revision_requested" }>;
newContent = `\n🔄 Revising plan based on your feedback (v${revisionEvent.planVersion})...\n`;
}
break;
case "auto_mode_task_started":
// Show when a task starts
if ("taskId" in event && "taskDescription" in event) {
@@ -362,7 +369,11 @@ export function AgentOutputModal({
</DialogHeader>
{/* Task Progress Panel - shows when tasks are being executed */}
<TaskProgressPanel featureId={featureId} className="flex-shrink-0 mx-1" />
<TaskProgressPanel
featureId={featureId}
projectPath={projectPath}
className="flex-shrink-0 mx-1"
/>
{viewMode === "changes" ? (
<div className="flex-1 min-h-[400px] max-h-[60vh] overflow-y-auto scrollbar-visible">

View File

@@ -14,7 +14,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Markdown } from "@/components/ui/markdown";
import { Label } from "@/components/ui/label";
import { Feature } from "@/store/app-store";
import { Check, X, Edit2, Eye, Loader2 } from "lucide-react";
import { Check, RefreshCw, Edit2, Eye, Loader2 } from "lucide-react";
interface PlanApprovalDialogProps {
open: boolean;
@@ -143,18 +143,21 @@ export function PlanApprovalDialog({
)}
</div>
{/* Reject Feedback Section - Only show when not in viewOnly mode */}
{/* Revision Feedback Section - Only show when not in viewOnly mode */}
{showRejectFeedback && !viewOnly && (
<div className="mt-4 space-y-2">
<Label htmlFor="reject-feedback">Feedback (optional)</Label>
<Label htmlFor="reject-feedback">What changes would you like?</Label>
<Textarea
id="reject-feedback"
value={rejectFeedback}
onChange={(e) => setRejectFeedback(e.target.value)}
placeholder="Provide feedback on why this plan is being rejected..."
placeholder="Describe the changes you'd like to see in the plan..."
className="min-h-[80px]"
disabled={isLoading}
/>
<p className="text-xs text-muted-foreground">
Leave empty to cancel the feature, or provide feedback to regenerate the plan.
</p>
</div>
)}
</div>
@@ -171,30 +174,30 @@ export function PlanApprovalDialog({
onClick={handleCancelReject}
disabled={isLoading}
>
Cancel
Back
</Button>
<Button
variant="destructive"
variant="secondary"
onClick={handleReject}
disabled={isLoading}
>
{isLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<X className="w-4 h-4 mr-2" />
<RefreshCw className="w-4 h-4 mr-2" />
)}
Confirm Reject
{rejectFeedback.trim() ? "Revise Plan" : "Cancel Feature"}
</Button>
</>
) : (
<>
<Button
variant="destructive"
variant="outline"
onClick={handleReject}
disabled={isLoading}
>
<X className="w-4 h-4 mr-2" />
Reject
<RefreshCw className="w-4 h-4 mr-2" />
Request Changes
</Button>
<Button
onClick={handleApprove}

View File

@@ -12,6 +12,15 @@ import { cn } from "@/lib/utils";
export type PlanningMode = 'skip' | 'lite' | 'spec' | 'full';
// Parsed task from spec (for spec and full planning modes)
export interface ParsedTask {
id: string; // e.g., "T001"
description: string; // e.g., "Create user model"
filePath?: string; // e.g., "src/models/user.ts"
phase?: string; // e.g., "Phase 1: Foundation" (for full mode)
status: 'pending' | 'in_progress' | 'completed' | 'failed';
}
export interface PlanSpec {
status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected';
content?: string;
@@ -21,6 +30,8 @@ export interface PlanSpec {
reviewedByUser: boolean;
tasksCompleted?: number;
tasksTotal?: number;
currentTaskId?: string; // ID of the task currently being worked on
tasks?: ParsedTask[]; // Parsed tasks from the spec
}
interface PlanningModeSelectorProps {

View File

@@ -294,6 +294,20 @@ export function useAutoMode() {
}
break;
case "plan_revision_requested":
// Log when user requests plan revision with feedback
if (event.featureId) {
const revisionEvent = event as Extract<AutoModeEvent, { type: "plan_revision_requested" }>;
console.log(`[AutoMode] Plan revision requested for ${event.featureId} (v${revisionEvent.planVersion})`);
addAutoModeActivity({
featureId: event.featureId,
type: "planning",
message: `Revising plan based on feedback (v${revisionEvent.planVersion})...`,
phase: "planning",
});
}
break;
case "auto_mode_task_started":
// Task started - show which task is being worked on
if (event.featureId && "taskId" in event && "taskDescription" in event) {

View File

@@ -305,6 +305,15 @@ export interface Feature {
requirePlanApproval?: boolean; // Whether to pause and require manual approval before implementation
}
// Parsed task from spec (for spec and full planning modes)
export interface ParsedTask {
id: string; // e.g., "T001"
description: string; // e.g., "Create user model"
filePath?: string; // e.g., "src/models/user.ts"
phase?: string; // e.g., "Phase 1: Foundation" (for full mode)
status: 'pending' | 'in_progress' | 'completed' | 'failed';
}
// PlanSpec status for feature planning/specification
export interface PlanSpec {
status: 'pending' | 'generating' | 'generated' | 'approved' | 'rejected';
@@ -315,6 +324,8 @@ export interface PlanSpec {
reviewedByUser: boolean; // True if user has seen the spec
tasksCompleted?: number;
tasksTotal?: number;
currentTaskId?: string; // ID of the task currently being worked on
tasks?: ParsedTask[]; // Parsed tasks from the spec
}
// File tree node for project analysis

View File

@@ -244,6 +244,7 @@ export type AutoModeEvent =
projectPath?: string;
planContent: string;
planningMode: "lite" | "spec" | "full";
planVersion?: number;
}
| {
type: "plan_auto_approved";
@@ -257,6 +258,7 @@ export type AutoModeEvent =
featureId: string;
projectPath?: string;
hasEdits: boolean;
planVersion?: number;
}
| {
type: "plan_rejected";
@@ -264,6 +266,14 @@ export type AutoModeEvent =
projectPath?: string;
feedback?: string;
}
| {
type: "plan_revision_requested";
featureId: string;
projectPath?: string;
feedback?: string;
hasEdits?: boolean;
planVersion?: number;
}
| {
type: "planning_started";
featureId: string;

View File

@@ -1671,7 +1671,8 @@ When done, summarize what you implemented and any notes for the developer.`;
const planContent = responseText.substring(0, markerIndex).trim();
// Parse tasks from the generated spec (for spec and full modes)
const parsedTasks = parseTasksFromSpec(planContent);
// Use let since we may need to update this after plan revision
let parsedTasks = parseTasksFromSpec(planContent);
const tasksTotal = parsedTasks.length;
console.log(`[AutoMode] Parsed ${tasksTotal} tasks from spec for feature ${featureId}`);
@@ -1693,60 +1694,173 @@ When done, summarize what you implemented and any notes for the developer.`;
let approvedPlanContent = planContent;
let userFeedback: string | undefined;
let currentPlanContent = planContent;
let planVersion = 1;
// Only pause for approval if requirePlanApproval is true
if (requiresApproval) {
console.log(`[AutoMode] Spec generated for feature ${featureId}, waiting for approval`);
// ========================================
// PLAN REVISION LOOP
// Keep regenerating plan until user approves
// ========================================
let planApproved = false;
// CRITICAL: Register pending approval BEFORE emitting event
// This prevents race condition where frontend tries to approve before
// the approval is registered in pendingApprovals Map
const approvalPromise = this.waitForPlanApproval(featureId, projectPath);
while (!planApproved) {
console.log(`[AutoMode] Spec v${planVersion} generated for feature ${featureId}, waiting for approval`);
// NOW emit plan_approval_required event (approval is already registered)
this.emitAutoModeEvent('plan_approval_required', {
featureId,
projectPath,
planContent,
planningMode,
});
// CRITICAL: Register pending approval BEFORE emitting event
const approvalPromise = this.waitForPlanApproval(featureId, projectPath);
// Wait for user approval
try {
const approvalResult = await approvalPromise;
if (!approvalResult.approved) {
// User rejected the plan - abort feature execution
console.log(`[AutoMode] Plan rejected for feature ${featureId}`);
throw new Error('Plan rejected by user');
}
console.log(`[AutoMode] Plan approved for feature ${featureId}, continuing with implementation`);
// If user provided an edited plan, update the planSpec content
if (approvalResult.editedPlan) {
approvedPlanContent = approvalResult.editedPlan;
await this.updateFeaturePlanSpec(projectPath, featureId, {
content: approvalResult.editedPlan,
});
}
// Capture user feedback if provided
userFeedback = approvalResult.feedback;
// Emit event to notify implementation is starting
this.emitAutoModeEvent('plan_approved', {
// Emit plan_approval_required event
this.emitAutoModeEvent('plan_approval_required', {
featureId,
projectPath,
hasEdits: !!approvalResult.editedPlan,
planContent: currentPlanContent,
planningMode,
planVersion,
});
} catch (error) {
if ((error as Error).message.includes('cancelled')) {
throw error; // Re-throw cancellation errors
// Wait for user response
try {
const approvalResult = await approvalPromise;
if (approvalResult.approved) {
// User approved the plan
console.log(`[AutoMode] Plan v${planVersion} approved for feature ${featureId}`);
planApproved = true;
// If user provided edits, use the edited version
if (approvalResult.editedPlan) {
approvedPlanContent = approvalResult.editedPlan;
await this.updateFeaturePlanSpec(projectPath, featureId, {
content: approvalResult.editedPlan,
});
} else {
approvedPlanContent = currentPlanContent;
}
// Capture any additional feedback for implementation
userFeedback = approvalResult.feedback;
// Emit approval event
this.emitAutoModeEvent('plan_approved', {
featureId,
projectPath,
hasEdits: !!approvalResult.editedPlan,
planVersion,
});
} else {
// User rejected - check if they provided feedback for revision
const hasFeedback = approvalResult.feedback && approvalResult.feedback.trim().length > 0;
const hasEdits = approvalResult.editedPlan && approvalResult.editedPlan.trim().length > 0;
if (!hasFeedback && !hasEdits) {
// No feedback or edits = explicit cancel
console.log(`[AutoMode] Plan rejected without feedback for feature ${featureId}, cancelling`);
throw new Error('Plan cancelled by user');
}
// User wants revisions - regenerate the plan
console.log(`[AutoMode] Plan v${planVersion} rejected with feedback for feature ${featureId}, regenerating...`);
planVersion++;
// Emit revision event
this.emitAutoModeEvent('plan_revision_requested', {
featureId,
projectPath,
feedback: approvalResult.feedback,
hasEdits: !!hasEdits,
planVersion,
});
// Build revision prompt
let revisionPrompt = `The user has requested revisions to the plan/specification.
## Previous Plan (v${planVersion - 1})
${hasEdits ? approvalResult.editedPlan : currentPlanContent}
## User Feedback
${approvalResult.feedback || 'Please revise the plan based on the edits above.'}
## Instructions
Please regenerate the specification incorporating the user's feedback.
Keep the same format with the \`\`\`tasks block for task definitions.
After generating the revised spec, output:
"[SPEC_GENERATED] Please review the revised specification above."
`;
// Update status to regenerating
await this.updateFeaturePlanSpec(projectPath, featureId, {
status: 'generating',
version: planVersion,
});
// Make revision call
const revisionStream = provider.executeQuery({
prompt: revisionPrompt,
model: finalModel,
maxTurns: maxTurns || 100,
cwd: workDir,
allowedTools: allowedTools,
abortController,
});
let revisionText = "";
for await (const msg of revisionStream) {
if (msg.type === "assistant" && msg.message?.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
revisionText += block.text || "";
this.emitAutoModeEvent("auto_mode_progress", {
featureId,
content: block.text,
});
}
}
} else if (msg.type === "error") {
throw new Error(msg.error || "Error during plan revision");
} else if (msg.type === "result" && msg.subtype === "success") {
revisionText += msg.result || "";
}
}
// Extract new plan content
const markerIndex = revisionText.indexOf('[SPEC_GENERATED]');
if (markerIndex > 0) {
currentPlanContent = revisionText.substring(0, markerIndex).trim();
} else {
currentPlanContent = revisionText.trim();
}
// Re-parse tasks from revised plan
const revisedTasks = parseTasksFromSpec(currentPlanContent);
console.log(`[AutoMode] Revised plan has ${revisedTasks.length} tasks`);
// Update planSpec with revised content
await this.updateFeaturePlanSpec(projectPath, featureId, {
status: 'generated',
content: currentPlanContent,
version: planVersion,
tasks: revisedTasks,
tasksTotal: revisedTasks.length,
tasksCompleted: 0,
});
// Update parsedTasks for implementation
parsedTasks = revisedTasks;
responseText += revisionText;
}
} catch (error) {
if ((error as Error).message.includes('cancelled')) {
throw error;
}
throw new Error(`Plan approval failed: ${(error as Error).message}`);
}
throw new Error(`Plan approval failed: ${(error as Error).message}`);
}
} else {
// Auto-approve: requirePlanApproval is false, just continue without pausing
console.log(`[AutoMode] Spec generated for feature ${featureId}, auto-approving (requirePlanApproval=false)`);
@@ -1758,6 +1872,8 @@ When done, summarize what you implemented and any notes for the developer.`;
planContent,
planningMode,
});
approvedPlanContent = planContent;
}
// CRITICAL: After approval, we need to make a second call to continue implementation
@@ -1771,142 +1887,163 @@ When done, summarize what you implemented and any notes for the developer.`;
reviewedByUser: requiresApproval,
});
// Build continuation prompt
let continuationPrompt = `The plan/specification has been approved. `;
if (userFeedback) {
continuationPrompt += `\n\nUser feedback: ${userFeedback}\n\n`;
}
continuationPrompt += `Now proceed with the implementation as specified in the plan:\n\n${approvedPlanContent}\n\nImplement the feature now.`;
// ========================================
// MULTI-AGENT TASK EXECUTION
// Each task gets its own focused agent call
// ========================================
// Make continuation call
const continuationStream = provider.executeQuery({
prompt: continuationPrompt,
model: finalModel,
maxTurns: maxTurns,
cwd: workDir,
allowedTools: allowedTools,
abortController,
});
if (parsedTasks.length > 0) {
console.log(`[AutoMode] Starting multi-agent execution: ${parsedTasks.length} tasks for feature ${featureId}`);
// Track task progress for events
let startedTaskIds: string[] = [];
let completedTaskIds: string[] = [];
let currentPhaseNum = 1;
let currentTaskId: string | null = null;
// Execute each task with a separate agent
for (let taskIndex = 0; taskIndex < parsedTasks.length; taskIndex++) {
const task = parsedTasks[taskIndex];
// Process continuation stream with task progress tracking
for await (const contMsg of continuationStream) {
if (contMsg.type === "assistant" && contMsg.message?.content) {
for (const contBlock of contMsg.message.content) {
if (contBlock.type === "text") {
responseText += contBlock.text || "";
// Check for abort
if (abortController.signal.aborted) {
throw new Error('Feature execution aborted');
}
// Check for [TASK_START] markers - detect when task begins
const taskStartMatches = responseText.match(/\[TASK_START\]\s*(T\d{3})/g);
if (taskStartMatches) {
for (const match of taskStartMatches) {
const taskIdMatch = match.match(/T\d{3}/);
if (taskIdMatch && !startedTaskIds.includes(taskIdMatch[0])) {
const taskId = taskIdMatch[0];
startedTaskIds.push(taskId);
currentTaskId = taskId;
// Emit task started
console.log(`[AutoMode] Starting task ${task.id}: ${task.description}`);
this.emitAutoModeEvent("auto_mode_task_started", {
featureId,
projectPath,
taskId: task.id,
taskDescription: task.description,
taskIndex,
tasksTotal: parsedTasks.length,
});
// Find task details from parsed tasks
const taskInfo = parsedTasks.find(t => t.id === taskId);
const taskDescription = taskInfo?.description || 'Working on task';
// Update planSpec with current task
await this.updateFeaturePlanSpec(projectPath, featureId, {
currentTaskId: task.id,
});
console.log(`[AutoMode] Task ${taskId} started for feature ${featureId}: ${taskDescription}`);
// Build focused prompt for this specific task
const taskPrompt = this.buildTaskPrompt(task, parsedTasks, taskIndex, approvedPlanContent, userFeedback);
// Emit task started event
this.emitAutoModeEvent("auto_mode_task_started", {
featureId,
projectPath,
taskId,
taskDescription,
taskIndex: startedTaskIds.length - 1,
tasksTotal: parsedTasks.length,
});
// Execute task with dedicated agent
const taskStream = provider.executeQuery({
prompt: taskPrompt,
model: finalModel,
maxTurns: Math.min(maxTurns || 100, 50), // Limit turns per task
cwd: workDir,
allowedTools: allowedTools,
abortController,
});
// Update planSpec with current task
await this.updateFeaturePlanSpec(projectPath, featureId, {
currentTaskId: taskId,
});
}
}
}
let taskOutput = "";
// Check for [TASK_COMPLETE] markers
const taskCompleteMatches = responseText.match(/\[TASK_COMPLETE\]\s*(T\d{3})/g);
if (taskCompleteMatches) {
for (const match of taskCompleteMatches) {
const taskIdMatch = match.match(/T\d{3}/);
if (taskIdMatch && !completedTaskIds.includes(taskIdMatch[0])) {
const taskId = taskIdMatch[0];
completedTaskIds.push(taskId);
console.log(`[AutoMode] Task ${taskId} completed for feature ${featureId}`);
// Emit task completion event
this.emitAutoModeEvent("auto_mode_task_complete", {
featureId,
projectPath,
taskId,
tasksCompleted: completedTaskIds.length,
tasksTotal: parsedTasks.length,
});
// Update planSpec with task progress
await this.updateFeaturePlanSpec(projectPath, featureId, {
tasksCompleted: completedTaskIds.length,
currentTaskId: taskId,
});
}
}
}
// Check for [PHASE_COMPLETE] markers (for full mode)
const phaseCompleteMatch = contBlock.text?.match(/\[PHASE_COMPLETE\]\s*Phase\s*(\d+)/i);
if (phaseCompleteMatch) {
const phaseNum = parseInt(phaseCompleteMatch[1], 10);
if (phaseNum > currentPhaseNum) {
currentPhaseNum = phaseNum;
console.log(`[AutoMode] Phase ${phaseNum} completed for feature ${featureId}`);
this.emitAutoModeEvent("auto_mode_phase_complete", {
// 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") {
taskOutput += block.text || "";
responseText += block.text || "";
this.emitAutoModeEvent("auto_mode_progress", {
featureId,
projectPath,
phaseNumber: phaseNum,
content: block.text,
});
} else if (block.type === "tool_use") {
this.emitAutoModeEvent("auto_mode_tool", {
featureId,
tool: block.name,
input: block.input,
});
}
}
this.emitAutoModeEvent("auto_mode_progress", {
featureId,
content: contBlock.text,
});
} else if (contBlock.type === "tool_use") {
this.emitAutoModeEvent("auto_mode_tool", {
featureId,
tool: contBlock.name,
input: contBlock.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 || "";
}
}
} else if (contMsg.type === "error") {
throw new Error(contMsg.error || "Unknown error during implementation");
} else if (contMsg.type === "result" && contMsg.subtype === "success") {
responseText += contMsg.result || "";
// Emit task completed
console.log(`[AutoMode] Task ${task.id} completed for feature ${featureId}`);
this.emitAutoModeEvent("auto_mode_task_complete", {
featureId,
projectPath,
taskId: task.id,
tasksCompleted: taskIndex + 1,
tasksTotal: parsedTasks.length,
});
// Update planSpec with progress
await this.updateFeaturePlanSpec(projectPath, featureId, {
tasksCompleted: taskIndex + 1,
});
// Check for phase completion (group tasks by phase)
if (task.phase) {
const nextTask = parsedTasks[taskIndex + 1];
if (!nextTask || nextTask.phase !== task.phase) {
// Phase changed, emit phase complete
const phaseMatch = task.phase.match(/Phase\s*(\d+)/i);
if (phaseMatch) {
this.emitAutoModeEvent("auto_mode_phase_complete", {
featureId,
projectPath,
phaseNumber: parseInt(phaseMatch[1], 10),
});
}
}
}
}
console.log(`[AutoMode] All ${parsedTasks.length} tasks completed for feature ${featureId}`);
} else {
// No parsed tasks - fall back to single-agent execution
console.log(`[AutoMode] No parsed tasks, using single-agent execution for feature ${featureId}`);
const continuationPrompt = `The plan/specification has been approved. Now implement it.
${userFeedback ? `\n## User Feedback\n${userFeedback}\n` : ''}
## Approved Plan
${approvedPlanContent}
## Instructions
Implement all the changes described in the plan above.`;
const continuationStream = provider.executeQuery({
prompt: continuationPrompt,
model: finalModel,
maxTurns: maxTurns,
cwd: workDir,
allowedTools: allowedTools,
abortController,
});
for await (const msg of continuationStream) {
if (msg.type === "assistant" && msg.message?.content) {
for (const block of msg.message.content) {
if (block.type === "text") {
responseText += block.text || "";
this.emitAutoModeEvent("auto_mode_progress", {
featureId,
content: block.text,
});
} else if (block.type === "tool_use") {
this.emitAutoModeEvent("auto_mode_tool", {
featureId,
tool: block.name,
input: block.input,
});
}
}
} else if (msg.type === "error") {
throw new Error(msg.error || "Unknown error during implementation");
} else if (msg.type === "result" && msg.subtype === "success") {
responseText += msg.result || "";
}
}
}
// Mark all tasks as completed when implementation finishes
if (parsedTasks.length > 0) {
await this.updateFeaturePlanSpec(projectPath, featureId, {
tasksCompleted: parsedTasks.length,
});
}
console.log(`[AutoMode] Implementation completed for feature ${featureId} (${completedTaskIds.length}/${parsedTasks.length} tasks tracked)`);
console.log(`[AutoMode] Implementation completed for feature ${featureId}`);
// Exit the original stream loop since continuation is done
break streamLoop;
}
@@ -1969,6 +2106,78 @@ Review the previous work and continue the implementation. If the feature appears
return this.executeFeature(projectPath, featureId, useWorktrees, false, prompt);
}
/**
* Build a focused prompt for executing a single task.
* Each task gets minimal context to keep the agent focused.
*/
private buildTaskPrompt(
task: ParsedTask,
allTasks: ParsedTask[],
taskIndex: number,
planContent: string,
userFeedback?: string
): string {
const completedTasks = allTasks.slice(0, taskIndex);
const remainingTasks = allTasks.slice(taskIndex + 1);
let prompt = `# Task Execution: ${task.id}
You are executing a specific task as part of a larger feature implementation.
## Your Current Task
**Task ID:** ${task.id}
**Description:** ${task.description}
${task.filePath ? `**Primary File:** ${task.filePath}` : ''}
${task.phase ? `**Phase:** ${task.phase}` : ''}
## Context
`;
// Show what's already done
if (completedTasks.length > 0) {
prompt += `### Already Completed (${completedTasks.length} tasks)
${completedTasks.map(t => `- [x] ${t.id}: ${t.description}`).join('\n')}
`;
}
// Show remaining tasks
if (remainingTasks.length > 0) {
prompt += `### Coming Up Next (${remainingTasks.length} tasks remaining)
${remainingTasks.slice(0, 3).map(t => `- [ ] ${t.id}: ${t.description}`).join('\n')}
${remainingTasks.length > 3 ? `... and ${remainingTasks.length - 3} more tasks` : ''}
`;
}
// Add user feedback if any
if (userFeedback) {
prompt += `### User Feedback
${userFeedback}
`;
}
// Add relevant excerpt from plan (just the task-related part to save context)
prompt += `### Reference: Full Plan
<details>
${planContent}
</details>
## Instructions
1. Focus ONLY on completing task ${task.id}: "${task.description}"
2. Do not work on other tasks
3. Use the existing codebase patterns
4. When done, summarize what you implemented
Begin implementing task ${task.id} now.`;
return prompt;
}
/**
* Emit an auto-mode event wrapped in the correct format for the client.
* All auto-mode events are sent as type "auto-mode:event" with the actual