fix(merge): re-enqueue stale merges + rebase new worktrees onto remote

Stale-merge recovery now calls back into ProjectEngine's auto-merge queue
directly instead of waiting on the 15s polling sweep — wired via a new
InProcessRuntime.setMergeEnqueuer hook so SelfHealingManager can re-enqueue
without leaking engine internals.

createWorktree mirrors the merge-time rebase: when worktreeRebaseBeforeMerge
is enabled, the new task branch is rebased onto <remote>/<defaultBranch>
right after creation, so executors start from origin's tip with local main
replayed on top. Best-effort — fetch/rebase failures abort cleanly and
leave the merge-time rebase as the backstop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-28 12:34:40 -07:00
parent 58e227e97c
commit 577ec0a66f
4 changed files with 142 additions and 1 deletions

View File

@@ -106,6 +106,12 @@ export class InProcessRuntime
private ephemeralCleanupTimers = new Map<string, ReturnType<typeof setTimeout>>();
/** Listener for agent:stateChanged events to clean up terminated ephemeral agents */
private ephemeralTerminationListener?: (agentId: string, from: import("@fusion/core").AgentState, to: import("@fusion/core").AgentState) => void;
/**
* Optional callback the runtime forwards to SelfHealingManager so that
* stale-merge recovery can re-enqueue tasks immediately. Set by ProjectEngine
* before `start()` via `setMergeEnqueuer`.
*/
private mergeEnqueuer?: (taskId: string) => void;
/**
* @param config - Runtime configuration
@@ -675,6 +681,7 @@ export class InProcessRuntime
recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
getPlanningTaskIds: () => this.triageProcessor?.getProcessingTaskIds() ?? new Set<string>(),
evictStaleTriageProcessing: () => this.triageProcessor?.evictStaleProcessing() ?? new Set<string>(),
enqueueMerge: this.mergeEnqueuer ? (taskId: string) => this.mergeEnqueuer?.(taskId) : undefined,
});
this.selfHealingManager.start();
this.stuckTaskDetector.start();
@@ -921,6 +928,15 @@ export class InProcessRuntime
return this.status;
}
/**
* Register a callback used by SelfHealingManager to re-enqueue tasks for
* auto-merge after clearing a stale `merging` status. Must be called before
* `start()` because SelfHealingManager is constructed during startup.
*/
setMergeEnqueuer(enqueueMerge: (taskId: string) => void): void {
this.mergeEnqueuer = enqueueMerge;
}
/**
* Get the project's TaskStore instance.
* @throws Error if runtime has not been started