-
Notifications
You must be signed in to change notification settings - Fork 578
Improve auto-loop event emission and add ntfy notifications #821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3996cb8
53b7dfa
39ba381
34508c7
14c87eb
0c449db
958ec7b
8cc5420
52ace4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,12 @@ const logger = createLogger('AutoLoopCoordinator'); | |
| const CONSECUTIVE_FAILURE_THRESHOLD = 3; | ||
| const FAILURE_WINDOW_MS = 60000; | ||
|
|
||
| // Sleep intervals for the auto-loop (in milliseconds) | ||
| const SLEEP_INTERVAL_CAPACITY_MS = 5000; | ||
| const SLEEP_INTERVAL_IDLE_MS = 10000; | ||
| const SLEEP_INTERVAL_NORMAL_MS = 2000; | ||
| const SLEEP_INTERVAL_ERROR_MS = 5000; | ||
|
|
||
| export interface AutoModeConfig { | ||
| maxConcurrency: number; | ||
| useWorktrees: boolean; | ||
|
|
@@ -169,20 +175,32 @@ export class AutoLoopCoordinator { | |
| // presence is accounted for when deciding whether to dispatch new auto-mode tasks. | ||
| const runningCount = await this.getRunningCountForWorktree(projectPath, branchName); | ||
| if (runningCount >= projectState.config.maxConcurrency) { | ||
| await this.sleep(5000, projectState.abortController.signal); | ||
| await this.sleep(SLEEP_INTERVAL_CAPACITY_MS, projectState.abortController.signal); | ||
| continue; | ||
| } | ||
| const pendingFeatures = await this.loadPendingFeaturesFn(projectPath, branchName); | ||
| if (pendingFeatures.length === 0) { | ||
| if (runningCount === 0 && !projectState.hasEmittedIdleEvent) { | ||
| this.eventBus.emitAutoModeEvent('auto_mode_idle', { | ||
| message: 'No pending features - auto mode idle', | ||
| // Double-check that we have no features in 'in_progress' state that might | ||
| // have been released from the concurrency manager but not yet updated to | ||
| // their final status. This prevents auto_mode_idle from firing prematurely | ||
| // when features are transitioning states (e.g., during status update). | ||
| const hasInProgressFeatures = await this.hasInProgressFeaturesForWorktree( | ||
| projectPath, | ||
| branchName, | ||
| }); | ||
| projectState.hasEmittedIdleEvent = true; | ||
| branchName | ||
| ); | ||
|
|
||
| // Only emit auto_mode_idle if we're truly done with all features | ||
| if (!hasInProgressFeatures) { | ||
| this.eventBus.emitAutoModeEvent('auto_mode_idle', { | ||
| message: 'No pending features - auto mode idle', | ||
| projectPath, | ||
| branchName, | ||
| }); | ||
| projectState.hasEmittedIdleEvent = true; | ||
| } | ||
| } | ||
| await this.sleep(10000, projectState.abortController.signal); | ||
| await this.sleep(SLEEP_INTERVAL_IDLE_MS, projectState.abortController.signal); | ||
| continue; | ||
| } | ||
|
|
||
|
|
@@ -228,10 +246,10 @@ export class AutoLoopCoordinator { | |
| } | ||
| }); | ||
| } | ||
| await this.sleep(2000, projectState.abortController.signal); | ||
| await this.sleep(SLEEP_INTERVAL_NORMAL_MS, projectState.abortController.signal); | ||
| } catch { | ||
| if (projectState.abortController.signal.aborted) break; | ||
| await this.sleep(5000, projectState.abortController.signal); | ||
| await this.sleep(SLEEP_INTERVAL_ERROR_MS, projectState.abortController.signal); | ||
| } | ||
| } | ||
| projectState.isRunning = false; | ||
|
|
@@ -462,4 +480,48 @@ export class AutoLoopCoordinator { | |
| signal?.addEventListener('abort', onAbort); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a feature belongs to the current worktree based on branch name. | ||
| * For main worktree (branchName === null or 'main'): includes features with no branchName or branchName === 'main'. | ||
| * For feature worktrees (branchName !== null and !== 'main'): only includes features with matching branchName. | ||
| */ | ||
| private featureBelongsToWorktree(feature: Feature, branchName: string | null): boolean { | ||
| const isMainWorktree = branchName === null || branchName === 'main'; | ||
| if (isMainWorktree) { | ||
| // Main worktree: include features with no branchName or branchName === 'main' | ||
| return !feature.branchName || feature.branchName === 'main'; | ||
| } else { | ||
| // Feature worktree: only include exact branch match | ||
| return feature.branchName === branchName; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Check if there are features in 'in_progress' status for the current worktree. | ||
| * This prevents auto_mode_idle from firing prematurely when features are | ||
| * transitioning states (e.g., during status update from in_progress to completed). | ||
| */ | ||
| private async hasInProgressFeaturesForWorktree( | ||
| projectPath: string, | ||
| branchName: string | null | ||
| ): Promise<boolean> { | ||
| if (!this.loadAllFeaturesFn) { | ||
| return false; | ||
| } | ||
|
|
||
| try { | ||
| const allFeatures = await this.loadAllFeaturesFn(projectPath); | ||
| return allFeatures.some( | ||
| (f) => f.status === 'in_progress' && this.featureBelongsToWorktree(f, branchName) | ||
| ); | ||
| } catch (error) { | ||
| const errorInfo = classifyError(error); | ||
| logger.warn( | ||
| `Failed to load all features for idle check (projectPath=${projectPath}, branchName=${branchName}): ${errorInfo.message}`, | ||
| error | ||
| ); | ||
| return false; | ||
| } | ||
|
Comment on lines
+518
to
+525
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the } catch (error) {
const errorInfo = classifyError(error);
logger.warn(
`Failed to load all features for idle check (projectPath=${projectPath}, branchName=${branchName}): ${errorInfo.message}`,
error
);
// Return true to be pessimistic and prevent premature idle event on transient error.
return true;
} |
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.