feat(FN-3019): merge fusion/fn-3019
Commits merged: - feat(FN-3019): complete Step 4 — document gridlock cooldown semantics - fix(FN-3019): normalize legacy custom provider payloads for typecheck - test(FN-3019): complete Step 2 — add gridlock cooldown regression coverage - fix(FN-3019): repair notifier class structure after cooldown refactor - feat(FN-3019): complete Step 1 — add gridlock notification cooldown Files changed: docs/architecture.md | 3 +- docs/settings-reference.md | 4 +- .../app/components/CustomProvidersSection.tsx | 25 ++++++---- .../engine/src/__tests__/gridlock-detector.test.ts | 5 +- packages/engine/src/__tests__/notifier.test.ts | 47 ++++++++++++++++-- packages/engine/src/gridlock-detector.ts | 16 +++++-- packages/engine/src/notifier.ts | 55 ++++++++++++---------- packages/engine/src/project-engine.ts | 1 + 8 files changed, 109 insertions(+), 47 deletions(-) Fusion-Task-Id: FN-3019
This commit is contained in:
@@ -15,6 +15,7 @@ export interface GridlockDetectorOptions {
|
||||
pollIntervalMs?: number;
|
||||
missionStore?: MissionStore;
|
||||
onGridlock?: (event: GridlockEvent) => void;
|
||||
onGridlockCleared?: () => void;
|
||||
}
|
||||
|
||||
export class GridlockDetector {
|
||||
@@ -22,6 +23,7 @@ export class GridlockDetector {
|
||||
private readonly pollIntervalMs: number;
|
||||
private readonly missionStore?: MissionStore;
|
||||
private readonly onGridlock?: (event: GridlockEvent) => void;
|
||||
private readonly onGridlockCleared?: () => void;
|
||||
private lastGridlockKey: string | null = null;
|
||||
|
||||
constructor(
|
||||
@@ -31,6 +33,7 @@ export class GridlockDetector {
|
||||
this.pollIntervalMs = options.pollIntervalMs ?? 30_000;
|
||||
this.missionStore = options.missionStore;
|
||||
this.onGridlock = options.onGridlock;
|
||||
this.onGridlockCleared = options.onGridlockCleared;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
@@ -65,13 +68,13 @@ export class GridlockDetector {
|
||||
});
|
||||
|
||||
if (schedulable.length === 0) {
|
||||
this.lastGridlockKey = null;
|
||||
this.clearGridlockState();
|
||||
return null;
|
||||
}
|
||||
|
||||
const active = tasks.filter((task) => task.column === "in-progress" || (task.column === "in-review" && Boolean(task.worktree)));
|
||||
if (active.length === 0) {
|
||||
this.lastGridlockKey = null;
|
||||
this.clearGridlockState();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -117,7 +120,7 @@ export class GridlockDetector {
|
||||
|
||||
const blockedTaskIds = Object.keys(reasons).sort();
|
||||
if (blockedTaskIds.length !== schedulable.length) {
|
||||
this.lastGridlockKey = null;
|
||||
this.clearGridlockState();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -138,6 +141,13 @@ export class GridlockDetector {
|
||||
return event;
|
||||
}
|
||||
|
||||
private clearGridlockState(): void {
|
||||
if (this.lastGridlockKey !== null) {
|
||||
this.lastGridlockKey = null;
|
||||
this.onGridlockCleared?.();
|
||||
}
|
||||
}
|
||||
|
||||
private isMissionBlocked(task: Task): boolean {
|
||||
if (!this.missionStore || !task.sliceId) return false;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user