Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/frontend/src/main/file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ export class FileWatcher extends EventEmitter {
return this.watchers.has(taskId);
}

/**
* Get the spec directory currently being watched for a task
*/
getWatchedSpecDir(taskId: string): string | null {
const watcherInfo = this.watchers.get(taskId);
if (!watcherInfo) return null;
return path.dirname(watcherInfo.planPath);
}

/**
* Get current plan state for a task
*/
Expand Down
35 changes: 31 additions & 4 deletions apps/frontend/src/main/ipc-handlers/agent-events-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,23 @@ export function registerAgenteventsHandlers(

// Send final plan state to renderer BEFORE unwatching
// This ensures the renderer has the final subtask data (fixes 0/0 subtask bug)
const finalPlan = fileWatcher.getCurrentPlan(taskId);
// Try the file watcher's current path first, then fall back to worktree path
let finalPlan = fileWatcher.getCurrentPlan(taskId);
if (!finalPlan && exitTask && exitProject) {
// File watcher may have been watching the wrong path (main vs worktree)
// Try reading directly from the worktree
const worktreePath = findTaskWorktree(exitProject.path, exitTask.specId);
if (worktreePath) {
const specsBaseDir = getSpecsDir(exitProject.autoBuildPath);
const worktreePlanPath = path.join(worktreePath, specsBaseDir, exitTask.specId, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
try {
const content = readFileSync(worktreePlanPath, 'utf-8');
finalPlan = JSON.parse(content);
} catch {
// Worktree plan file not readable - not critical
}
}
}
if (finalPlan) {
safeSendToRenderer(
getMainWindow,
Expand Down Expand Up @@ -211,15 +227,26 @@ export function registerAgenteventsHandlers(
const worktreePath = findTaskWorktree(project.path, task.specId);
if (worktreePath) {
const specsBaseDir = getSpecsDir(project.autoBuildPath);
const worktreeSpecDir = path.join(worktreePath, specsBaseDir, task.specId);
const worktreePlanPath = path.join(
worktreePath,
specsBaseDir,
task.specId,
worktreeSpecDir,
AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN
);
if (existsSync(worktreePlanPath)) {
persistPlanPhaseSync(worktreePlanPath, progress.phase, project.id);
}

// Re-watch the worktree path if the file watcher is still watching the main project path.
// This handles the case where the task started before the worktree existed:
// the initial watch fell back to the main project spec dir, but now the worktree
// is available and implementation_plan.json is being written there.
const currentWatchDir = fileWatcher.getWatchedSpecDir(taskId);
if (currentWatchDir && currentWatchDir !== worktreeSpecDir && existsSync(worktreePlanPath)) {
console.warn(`[agent-events-handlers] Re-watching worktree path for ${taskId}: ${worktreeSpecDir}`);
fileWatcher.watch(taskId, worktreeSpecDir).catch((err) => {
console.error(`[agent-events-handlers] Failed to re-watch worktree for ${taskId}:`, err);
});
}
}
} else if (xstateInTerminalState && progress.phase) {
console.debug(`[agent-events-handlers] Skipping persistPlanPhaseSync for ${taskId}: XState in '${currentXState}', not overwriting with phase '${progress.phase}'`);
Expand Down
39 changes: 28 additions & 11 deletions apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ async function ensureProfileManagerInitialized(): Promise<
}
}

/**
* Get the spec directory for file watching, preferring the worktree path if it exists.
* When a task runs in a worktree, implementation_plan.json is written there,
* not in the main project's spec directory.
*/
function getSpecDirForWatcher(projectPath: string, specsBaseDir: string, specId: string): string {
const worktreePath = findTaskWorktree(projectPath, specId);
if (worktreePath) {
return path.join(worktreePath, specsBaseDir, specId);
}
return path.join(projectPath, specsBaseDir, specId);
}

/**
* Register task execution handlers (start, stop, review, status management, recovery)
*/
Expand Down Expand Up @@ -205,15 +218,14 @@ export function registerTaskExecutionHandlers(
}

// Start file watcher for this task
// Use worktree path if it exists, since the backend writes implementation_plan.json there
const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specDir = path.join(
project.path,
specsBaseDir,
task.specId
);
fileWatcher.watch(taskId, specDir);
const watchSpecDir = getSpecDirForWatcher(project.path, specsBaseDir, task.specId);
fileWatcher.watch(taskId, watchSpecDir);

// Check if spec.md exists (indicates spec creation was already done or in progress)
// Check main project path for spec file (spec is created before worktree)
const specDir = path.join(project.path, specsBaseDir, task.specId);
const specFilePath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE);
const hasSpec = existsSync(specFilePath);

Expand Down Expand Up @@ -710,7 +722,9 @@ export function registerTaskExecutionHandlers(
}

// Start file watcher for this task
fileWatcher.watch(taskId, specDir);
// Use worktree path if it exists, since the backend writes implementation_plan.json there
const watchSpecDir = getSpecDirForWatcher(project.path, specsBaseDir, task.specId);
fileWatcher.watch(taskId, watchSpecDir);

// Check if spec.md exists
const specFilePath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE);
Expand Down Expand Up @@ -1146,12 +1160,15 @@ export function registerTaskExecutionHandlers(

// Start the task execution
// Start file watcher for this task
// Use worktree path if it exists, since the backend writes implementation_plan.json there
const specsBaseDir = getSpecsDir(project.autoBuildPath);
const specDirForWatcher = path.join(project.path, specsBaseDir, task.specId);
fileWatcher.watch(taskId, specDirForWatcher);
const watchSpecDir = getSpecDirForWatcher(project.path, specsBaseDir, task.specId);
fileWatcher.watch(taskId, watchSpecDir);

// Check if spec.md exists to determine whether to run spec creation or task execution
const specFilePath = path.join(specDirForWatcher, AUTO_BUILD_PATHS.SPEC_FILE);
// Check main project path for spec file (spec is created before worktree)
const mainSpecDir = path.join(project.path, specsBaseDir, task.specId);
const specFilePath = path.join(mainSpecDir, AUTO_BUILD_PATHS.SPEC_FILE);
const hasSpec = existsSync(specFilePath);
const needsSpecCreation = !hasSpec;

Expand All @@ -1162,7 +1179,7 @@ export function registerTaskExecutionHandlers(
// No spec file - need to run spec_runner.py to create the spec
const taskDescription = task.description || task.title;
console.warn(`[Recovery] Starting spec creation for: ${task.specId}`);
agentManager.startSpecCreation(taskId, project.path, taskDescription, specDirForWatcher, task.metadata, baseBranchForRecovery, project.id);
agentManager.startSpecCreation(taskId, project.path, taskDescription, mainSpecDir, task.metadata, baseBranchForRecovery, project.id);
} else {
// Spec exists - run task execution
console.warn(`[Recovery] Starting task execution for: ${task.specId}`);
Expand Down
Loading