feat(FN-1408): complete Step 1 - Add node context plumbing at App shell
- Wrap AppInner with NodeProvider in App.tsx - Read node context state (currentNode, currentNodeId, isRemote, setters) - Add useRemoteNodeData and useRemoteNodeEvents hooks - Sync selected node with useNodes() results - Update Board/ListView/ExecutorStatusBar to use remote tasks when in remote mode - Add mocks for NodeContext, useRemoteNodeData, useRemoteNodeEvents, useNodes in App.test.tsx - Clear fusion-dashboard-current-node in test setup to avoid cross-test leakage
This commit is contained in:
@@ -691,7 +691,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
// enqueue it for serialized merge processing.
|
||||
registerHandler(store, "task:moved", async ({ task, to }) => {
|
||||
if (to !== "in-review") return;
|
||||
if (!canAutoMergeTask(task as any)) return;
|
||||
if (getTaskMergeBlocker(task)) return;
|
||||
try {
|
||||
const settings = await store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return;
|
||||
@@ -1023,7 +1023,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
// ── Startup sweep: enqueue any tasks already in "in-review" ───────
|
||||
if (settings.autoMerge) {
|
||||
const existing = await store.listTasks();
|
||||
const inReview = existing.filter((t) => canAutoMergeTask(t as any));
|
||||
const inReview = existing.filter((t) => !getTaskMergeBlocker(t));
|
||||
if (inReview.length > 0) {
|
||||
console.log(
|
||||
`[auto-merge] Startup sweep: enqueueing ${inReview.length} in-review task(s)`,
|
||||
@@ -1059,7 +1059,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
try {
|
||||
const tasks = await store.listTasks();
|
||||
for (const t of tasks) {
|
||||
if (canAutoMergeTask(t as any)) {
|
||||
if (!getTaskMergeBlocker(t)) {
|
||||
enqueueMerge(t.id);
|
||||
}
|
||||
}
|
||||
@@ -1132,6 +1132,8 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
// ── Periodic retry: catch failed merges on each poll cycle ────────
|
||||
// Uses a setTimeout chain so the interval dynamically follows
|
||||
// settings.pollIntervalMs without requiring an engine restart.
|
||||
// The readiness predicate uses getTaskMergeBlocker() to detect tasks that
|
||||
// have become unblocked (e.g., awaiting-user-review cleared by user).
|
||||
async function scheduleMergeRetry(): Promise<void> {
|
||||
if (disposed) return;
|
||||
const currentSettings = await store.getSettings().catch(() => settings);
|
||||
@@ -1145,7 +1147,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
if (!s.globalPause && !s.enginePaused && s.autoMerge) {
|
||||
const tasks = await store.listTasks();
|
||||
for (const t of tasks) {
|
||||
if (canAutoMergeTask(t as any)) {
|
||||
if (!getTaskMergeBlocker(t)) {
|
||||
enqueueMerge(t.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +288,16 @@ export async function runServe(
|
||||
const mergeQueue: string[] = [];
|
||||
const mergeActive = new Set<string>();
|
||||
let mergeRunning = false;
|
||||
const maxAutoMergeRetries = 3;
|
||||
|
||||
/**
|
||||
* Check if a task can be merged (not blocked and within retry limit).
|
||||
* This is the final validation gate before attempting a merge.
|
||||
*/
|
||||
function canMergeTask(task: { mergeRetries?: number | null; column: string; paused?: boolean; status?: string | null; error?: string | null; steps?: Array<{ status: string }>; workflowStepResults?: Array<{ status: string }> }): boolean {
|
||||
if (getTaskMergeBlocker(task as any)) return false;
|
||||
return (task.mergeRetries ?? 0) < maxAutoMergeRetries;
|
||||
}
|
||||
|
||||
function enqueueMerge(taskId: string): void {
|
||||
if (mergeActive.has(taskId)) return;
|
||||
@@ -316,7 +326,7 @@ export async function runServe(
|
||||
}
|
||||
|
||||
const task = await store.getTask(taskId);
|
||||
if (getTaskMergeBlocker(task)) {
|
||||
if (!canMergeTask(task as any)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -353,9 +363,8 @@ export async function runServe(
|
||||
|
||||
if (task && isConflictError) {
|
||||
const currentRetries = task.mergeRetries ?? 0;
|
||||
const maxRetries = 3;
|
||||
|
||||
if (settings.autoResolveConflicts !== false && currentRetries < maxRetries) {
|
||||
if (settings.autoResolveConflicts !== false && currentRetries < maxAutoMergeRetries) {
|
||||
const newRetryCount = currentRetries + 1;
|
||||
await store.updateTask(taskId, {
|
||||
mergeRetries: newRetryCount,
|
||||
@@ -364,16 +373,16 @@ export async function runServe(
|
||||
|
||||
const delayMs = 5000 * Math.pow(2, currentRetries);
|
||||
console.log(
|
||||
`[auto-merge] ↻ ${taskId}: retry ${newRetryCount}/${maxRetries} in ${delayMs / 1000}s`,
|
||||
`[auto-merge] ↻ ${taskId}: retry ${newRetryCount}/${maxAutoMergeRetries} in ${delayMs / 1000}s`,
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
enqueueMerge(taskId);
|
||||
}, delayMs);
|
||||
} else {
|
||||
if (currentRetries >= maxRetries) {
|
||||
if (currentRetries >= maxAutoMergeRetries) {
|
||||
console.log(
|
||||
`[auto-merge] ⊘ ${taskId}: max retries (${maxRetries}) exceeded — manual resolution required`,
|
||||
`[auto-merge] ⊘ ${taskId}: max retries (${maxAutoMergeRetries}) exceeded — manual resolution required`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
@@ -387,15 +396,26 @@ export async function runServe(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-conflict error - stop auto-retrying until a user intervenes.
|
||||
// This prevents the periodic sweep from re-enqueueing the same
|
||||
// broken merge on every poll cycle.
|
||||
try {
|
||||
await store.updateTask(taskId, { status: null });
|
||||
await store.updateTask(taskId, {
|
||||
status: null,
|
||||
mergeRetries: maxAutoMergeRetries,
|
||||
error: errorMsg,
|
||||
});
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await store.updateTask(taskId, { status: null });
|
||||
await store.updateTask(taskId, {
|
||||
status: null,
|
||||
mergeRetries: maxAutoMergeRetries,
|
||||
error: errorMsg,
|
||||
});
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user