feat(engine): preserve branches on auto-requeue + add fn_run_verification

Three coordinated fixes for the FN-2978 incident class — auto-requeues
that orphaned committed work and watchdog kills on long verification runs.

**Auto-requeue branch reuse** (executor.ts, worktree-pool.ts)
- executor.ts:1782 now uses `task.branch || fusion/<id>` so persisted
  branches are honored on requeue. Previously the hardcoded fallback
  always tried to re-create the original branch, hit a conflict with
  the prior run's ref, and got suffix -2/-3. Other call sites already
  honor task.branch — this aligns the worktree-acquisition path.
- worktree-pool.ts:181 prepareForTask now probes existing branches with
  `git rev-parse --verify` and checks them out as-is. Falls through to
  suffixed creation only when the branch is genuinely in use by another
  live worktree. Previously force-reset with `checkout -B`, destroying
  prior commits.
- New private reconcileStepsFromGitHistory walks `git log
  baseCommitSha..HEAD` for `feat(FN-X): complete Step N` commits and
  marks matching steps[] as done so resumes don't redo committed work.

**Manual reset endpoint + UI** (dashboard)
- POST /api/tasks/:id/reset (requires `confirm: true`) — clears worktree,
  branch, all retry counters, resets steps[] to pending, moves to todo.
  Distinct from /retry which is the soft-resume path.
- Reset button alongside Retry in TaskDetailModal with confirm dialog,
  wired through useTasks → AppModals → API.

**fn_run_verification tool** (run-verification-tool.ts, executor.ts)
- New custom tool wrapping test/lint/build commands with a heartbeat
  callback (per-line + 60s synthetic), 200KB head+tail output cap, hard
  timeout with SIGTERM→SIGKILL escalation, and auto-bootstrap detection
  for missing node_modules. Prevents the inactivity watchdog from
  killing sessions during long compiles.
- Cross-platform via `shell: true` (Node picks /bin/sh on POSIX,
  cmd.exe on Windows). Prompt section in EXECUTOR_SYSTEM_PROMPT and
  EXECUTOR_PROMPT_TEXT instructs agents to prefer package-scoped
  verification first and reserve workspace-scoped runs for final
  integration.

**Tests** (64 passing)
- detect-pseudo-pause.test.ts (27 tests) — covers all 7 regex patterns,
  structural fallback, FN-2978 regression text.
- reconcile-step-regex.test.ts (25 tests) — pins the commit-message
  regex against a wide variant set.
- run-verification-command.test.ts (12 tests) — basic execution, output
  capture, heartbeat callbacks, timeout, error handling. POSIX-specific
  cases (multi-cmd `;`, `>&2`, `\$USER`) gated behind itPosix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-30 10:19:54 -07:00
parent abf5dac4a1
commit 400be4487f
13 changed files with 1441 additions and 7 deletions

View File

@@ -348,6 +348,10 @@ export function useTasks(options?: UseTasksOptions) {
return normalizeTask(await api.retryTask(id, projectId));
}, [projectId]);
const resetTask = useCallback(async (id: string): Promise<Task> => {
return normalizeTask(await api.resetTask(id, projectId));
}, [projectId]);
const duplicateTask = useCallback(async (id: string): Promise<Task> => {
const task = normalizeTask(await api.duplicateTask(id, projectId));
setTasks((prev) => {
@@ -457,5 +461,5 @@ export function useTasks(options?: UseTasksOptions) {
lastFetchTimeMs.current = Date.now();
}, []);
return { tasks, createTask, moveTask, pauseTask, deleteTask, mergeTask, retryTask, duplicateTask, updateTask, archiveTask, unarchiveTask, archiveAllDone, loadArchivedTasks, includeArchived, ingestCreatedTasks, lastFetchTimeMs: lastFetchTimeMs.current };
return { tasks, createTask, moveTask, pauseTask, deleteTask, mergeTask, retryTask, resetTask, duplicateTask, updateTask, archiveTask, unarchiveTask, archiveAllDone, loadArchivedTasks, includeArchived, ingestCreatedTasks, lastFetchTimeMs: lastFetchTimeMs.current };
}