feat(FN-3895): add event-driven unblock for scheduler startup recovery when

Adds an event-driven unblock path that automatically releases agents when their blocked-by dependencies complete, including startup recovery logic for any tasks still blocked at engine initialization; covered by scheduler and self-healing test suites with a minor CSS fix for the compact mobile impor

Fusion-Task-Id: FN-3895
This commit is contained in:
Fusion
2026-05-09 18:58:13 -07:00
committed by gsxdsm
parent 54a475b9f0
commit c5c70bd220
7 changed files with 156 additions and 4 deletions

View File

@@ -232,7 +232,7 @@ export class Scheduler {
* Also handles mission auto-advance: when a linked task completes,
* update feature status and potentially activate next pending slice.
*/
this.store.on("task:moved", ({ task, from, to }) => {
this.store.on("task:moved", async ({ task, from, to }) => {
// PR Monitoring
if (this.options.prMonitor) {
if (to === "in-review" && task.prInfo) {
@@ -279,6 +279,34 @@ export class Scheduler {
}
}
// FN-3895: complement periodic stale-blockedBy self-healing with immediate
// unblock when a blocker reaches a terminal completion column.
if (to === "done" || to === "archived") {
try {
const settings = await this.store.getSettings();
if (!settings.globalPause && !settings.enginePaused) {
const todoTasks = await this.store.listTasks({ column: "todo", slim: true });
for (const dependent of todoTasks) {
if (dependent.blockedBy !== task.id) continue;
try {
await this.store.updateTask(dependent.id, { blockedBy: null, status: null });
await this.store.logEntry(
dependent.id,
`Auto-unblocked: blocker ${task.id} reached ${to}`,
);
} catch (error) {
schedulerLog.error(
`Failed to auto-unblock dependent ${dependent.id} for blocker ${task.id}`,
error,
);
}
}
}
} catch (error) {
schedulerLog.error(`Failed event-driven unblock pass for blocker ${task.id}`, error);
}
}
// Event-driven scheduling: when a task moves to "done" (completion) or "todo" (retry/manual move),
// trigger scheduling immediately so waiting tasks can start without waiting
// for the next poll interval (up to 15 seconds).